【问题标题】:Json.NET Recursive Serialisation: Custom converter attribute seems to be being ignoredJson.NET 递归序列化:自定义转换器属性似乎被忽略
【发布时间】: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


    【解决方案1】:

    像往常一样,尽管被卡住了几个小时,但我在发布此内容后取得了很大进步。

    似乎我对堆栈溢出的结构非常错误。它根本不是因为 PirateShip.nemesis 而无限循环,它无限循环仅仅是因为 JObject.FromObject 开始尝试反序列化 PirateShip 本身. this thread 中的最高答案明确警告这种无限递归将会发生。

    为了解决这个问题,我在我的自定义哈希转换器中使用了 CanWrite 属性,针对一个操作暂时禁用它,然后立即重新启用它。这支持相互引用的可引用对象,以及在自己的范围内定义其他可引用对象的可引用对象。它似乎工作正常。

    /// <summary>
    

    /// 一个专门为任何被Referable属性修饰的对象的转换器,表明它需要用它来序列化它的hashcode。 /// 公共类 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;
            }
        }
    }
    

    }

    【讨论】:

      猜你喜欢
      • 2023-04-07
      • 2016-08-27
      • 2014-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多