Json.NET递归序列化:自定义转换器属性似乎被忽略

时间:2018-07-19 15:41:13

标签: c# json attributes stack-overflow infinite-loop

我知道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 的上下文有关吗?有没有办法解决这个问题,或者有一种方法可以完全避免呢?

1 个答案:

答案 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;
        }
    }
}

}