【发布时间】:2018-07-19 15:41:13
【问题描述】:
我知道有很多关于 Json.NET 中递归序列化的相关问题,但我认为其中没有一个真正适合我或反映我的问题。
我目前正在使用 C# Unity 中的序列化系统进行试验,该系统使用 Json.NET 自定义转换器。目的是创建一个可以支持引用序列化和反序列化的系统。
显然,有两种类型的序列化。第一个是按值,我想在其中写入给定对象的定义数据(因此将海盗船称为“皇家河马”的事实序列化,以 20f km/h 的速度行驶,并悬挂红旗) .在其他地方,我可能会参考皇家河马,甚至可能来自另一艘海盗船(如果这些船追踪了他们的克星船怎么办?);在这些情况下,我想序列化作为参考。我当然可以使用字符串,或哈希码,甚至 Json.NET 的本机引用系统来完成此操作。
我的计划是使用哈希码来序列化引用。所以我需要两个自定义转换器来完成这个:一个识别可以引用的对象,因此需要将其哈希码写入其 json 文件,另一个识别对象何时被引用而不是定义,从而将其序列化为仅其哈希码。
将彼此作为克星的两艘船的 Json 文件应如下所示:
{
"pirateShips": [
{
"HashCode": 123
"name": "The Seagull"
"flagCol": {
"a": 1.0,
"r": 1.0,
"g": 0.921568632,
"b": 0.0156862754
},
"speed": 20.0,
"nemesis": 456,
},
{
"HashCode": 456
"name": "The Beagle"
"flagCol": {
"a": 1.0,
"r": 0.0,
"g": 0.0,
"b": 1.0
},
"speed": 30.0,
"nemesis": 123
}
]
}
为了指定哪些类需要包含用于反序列化的哈希码,我创建了一个名为 Referable 的简单属性并制作了一个自定义转换器 - HashJConverter -瞄准它。
/// <summary>
/// A converter specifically for any object that is decorated with the Referable attribute, indicating that it needs to have its hashcode serialised with it.
/// </summary>
public class HashJConverter : JsonConverter
{
// Does the object have the Referable attribute?
public override bool CanConvert(Type objectType)
{
return objectType.HasAttribute<ReferableAttribute>();
}
// When serialising, put the hashcode into the Json object.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject jObj = JObject.FromObject(value, serializer); // First, get the JObject.
jObj.Add("HashCode", value.GetHashCode()); // Then add a one-off hashcode field.
jObj.WriteTo(writer);
}
// Worry about this later ...
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return objectType.HasAttribute<ReferableAttribute>();
}
}
还有 PirateShip 类,装饰有Referable 属性:
/// <summary>
/// This is a dummy class used to describe a pirate-ship as a serialisation example.
/// </summary>
[Referable]
public class PirateShip
{
// Serialise these normally.
[SerializeField]
public string name = "";
[SerializeField]
public Color flagCol = new Color();
[SerializeField]
public float speed = 10f;
// This is a reference, not a definition. The RefjConverter ensures that this reference is serialised as its hashcode.
[SerializeField]
[JsonConverter(typeof(RefJConverter))]
public PirateShip nemesis = null;
public PirateShip(Color _flagCol, float _speed)
{
flagCol = _flagCol;
speed = _speed;
}
}
最后是RefJConverter的定义:
/// <summary>
/// The custom converter used to handle references. A reference should be serialised as a hashcode.
/// </summary>
public class RefJConverter : JsonConverter
{
// This Converter is invoked via an explicit Json.NET attribute, so simply return always true for CanConvert.
public override bool CanConvert(Type objectType)
{
return true;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Write the hashcode.
if(value.GetType().HasAttribute<ReferableAttribute>())
{
writer.WriteValue(value.GetHashCode());
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
}
}
现在,您可能会注意到,在这种情况下,两个自定义转换器对于 PirateShip.nemesis 字段都是可行的转换器。但是根据Json.NET documentation,
使用JsonConverter的优先级是成员属性,然后是类属性,最后是传递给JsonSerializer的任何转换器。
所以我的成员属性 [JsonConverter(typeof(RefJConverter))] 应该覆盖 Referable 属性。
当 JObject.FromObject 被调用时,HashJConverter 会调用序列化程序。这会读取 PirateShip 的成员,其中一个本身就是 PirateShip。它没有使用 RefJConverter 进行转换,而是再次使用 HashJConverter,导致无限递归和堆栈溢出。 Unity 会立即崩溃,除非我在进行单元测试,在这种情况下它会冻结。
有人知道为什么会这样吗?这与我调用 JObject.FromObject 的上下文有关吗?有没有办法解决这个问题,或者完全避免它的方法?
【问题讨论】:
标签: c# json attributes stack-overflow infinite-loop