我知道Json.NET中有许多与递归序列化相关的问题,但我认为它们中的任何一个都不适合我或不能反映我的问题。
我目前正在尝试使用Json.NET自定义转换器在Unity C#中使用序列化系统。目的是创建一个可以支持参考序列化和反序列化的系统。
很显然,有两种类型的序列化。第一个是按值,我要在其中写入给定对象的定义数据(因此,将海盗船称为“皇家河马”的事实序列化,以每小时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的优先级是成员属性,然后是class属性,最后是传递给JsonSerializer的所有转换器。
因此,我的成员属性 [JsonConverter(typeof(RefJConverter))] 应该覆盖 Referable 属性。
相反,当调用 JObject.FromObject 时, HashJConverter 会调用序列化程序。读取PirateShip的成员,其中之一本身就是PirateShip。它不再使用 RefJConverter 进行转换,而是再次使用 HashJConverter ,从而导致无限递归和堆栈溢出。除非我正在进行单元测试,否则Unity会立即崩溃,在这种情况下,它将冻结。
有人知道为什么会这样吗?这与我调用 JObject.FromObject 的上下文有关吗?有没有办法解决这个问题,或者有一种方法可以完全避免呢?
答案 0 :(得分:0)
像往常一样,尽管被困了好几个小时,但发布此书后我还是取得了很大进步。
似乎我对堆栈溢出的结构非常误解。它根本不是因为 PirateShip.nemesis 而无限循环,而是因为 JObject.FromObject 首先尝试反序列化 PirateShip 本身而无限循环。 。 this thread中的最高答案明确警告将发生这种无限递归。
为解决此问题,我在自定义哈希转换器中使用了 CanWrite 属性,暂时仅对一项操作禁用了它,然后立即将其重新启用。这支持相互引用的可引用对象,以及在其自身范围内定义其他可引用对象的可引用对象。似乎工作正常。
/// <summary>
///专门用于任何使用Referable属性修饰的对象的转换器,指示它需要对其哈希码进行序列化。 /// 公共类HashJConverter:JsonConverter { //标记以禁用此转换器的一项操作,从而允许在编辑之前使用默认逻辑对此类进行序列化。 bool skipOverMe = false;
// 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)
{
// Disable this converter for default serialisation, preventing the infinite loop.
skipOverMe = true;
JObject jObj = JObject.FromObject(value); // First, get the JObject.
// FromObject calls CanWrite, so the converter has now been re-enabled.
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>();
}
// Property to automatically re-enable this converter once the default serialisation has been carried out.
public override bool CanWrite
{
get
{
if(skipOverMe)
{
Debug.Log("Reenabling HashConverter.");
skipOverMe = false;
return false;
}
else
{
return true;
}
}
}
}