自定义参考循环处理

时间:2013-11-10 19:40:58

标签: c# .net json serialization json.net

我正在尝试实现自定义引用循环处理。我只需要编写空对象来代替嵌套对象。

预期结果

 { Id:1, Field:"Value", NestedObject:{Id:1}}

我创建了JsonConverter

public class SerializationConverter : JsonConverter
{
    public override bool CanRead { get { return false; } }
    public override bool CanWrite { get { return true; } }


    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Form) || typeof(Form).IsAssignableFrom(objectType);
    }

    private HashSet<Form> serializedForms = new HashSet<Form>();

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
            writer.WriteNull();

        var f = (Form)value;
        if (!serializedForms.Add(f))
            writer.WriteRawValue("{Id:" + f.Id.Value + "}");
        else
            serializer.Serialize(writer, value);
    }
}

但是,正如预期的那样,serializer.Serialize(writer, value)内部调用的串行器会再次调用我的转换器。

我只是在对象已经序列化时才尝试替换序列化结果,否则使用默认的序列化行为。

1 个答案:

答案 0 :(得分:2)

首先,我想提一下Json.Net有一个内置的PreserveReferencesHandling设置,可以自动处理这类事情,而无需特殊的转换器。将PreserveReferencesHandling设置为All后,Json.Net会为每个对象分配内部引用ID,并将特殊$id$ref属性写入JSON以跟踪引用。对于您的示例,JSON输出将如下所示:

{"$id":"1","Id":1,"Field":"Value","NestedObject":{"$ref":"1"}}

您会注意到这与您问题的所需输出非常相似。这样做的另一个好处是,可以很容易地将其反序列化回原始对象图中,同时保留所有引用,同样无需实现任何特殊内容。

但是我们暂时假设您有自己的理由想要实现自定义引用循环处理,并查看代码无效的原因。

当Json.Net遇到对象的JsonConverter时,它假定转换器将处理写入该对象所需的任何JSON。因此,如果您想要包含某些属性,则必须自己编写。您可以使用序列化程序来帮助编写对象的部件,但是您不能只将整个对象传递给序列化程序并说“序列化这个”,因为它最终会重新调用你的转换器。

在大多数情况下,这样做会导致无限循环。在您的情况下,它没有,因为您在第一次调用WriteJson时将表单添加到HashSet。当序列化程序第二次回调时,将采用另一个分支,因为表单已经在集合中。因此,对象的整个JSON最终为{Id:1},而不是您真正想要的。

防止序列化程序回调到转换器的一种方法是在转换器内创建JsonSerializer的新实例,并使用该实例而不是传递给WriteJson方法的实例。新实例将不会引用您的转换器,因此您的Form将正常序列化。

不幸的是,这个想法也不起作用:如果你没有在内部序列化器上引用转换器,那么Json.Net就无法知道如何为{{{{{{{ 1}}!相反,它将被省略,因为我们将被迫将NestedObject设置为ReferenceLoopHandling以避免错误。所以你看,你有一个捕获22。

那么我们怎样才能让它发挥作用?好吧,让我们退后一步,重新定义你真正希望在输出方面发生的事情:

  1. 如果我们遇到表格,我们只想输出Ignore
  2. 否则,请在表单中添加我们看过的表单列表,然后输出IdIdField
  3. 请注意,在这两种情况下,我们都希望输出NestedObject,因此我们可以简化逻辑:

    1. 始终输出Id
    2. 如果我们遇到我们尚未见过的表单,请添加我们看过的表单列表,然后输出IdField
    3. 为了简单起见,我们可以使用NestedObject来收集我们想要输出的属性,然后在最后将其写入JObject

      以下是修订后的代码:

      writer

      现在让我们测试一下:

      public class SerializationConverter : JsonConverter
      {
          public override bool CanRead { get { return false; } }
          public override bool CanWrite { get { return true; } }
      
          public override bool CanConvert(Type objectType)
          {
              return objectType == typeof(Form) || typeof(Form).IsAssignableFrom(objectType);
          }
      
          private HashSet<Form> serializedForms = new HashSet<Form>();
      
          public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
          {
              Form f = (Form)value;
      
              JObject jo = new JObject();
              jo.Add("Id", f.Id);
      
              if (serializedForms.Add(f))
              {
                  jo.Add("Field", f.Field);
                  if (f.NestedObject != null)
                  {
                      jo.Add("NestedObject", JToken.FromObject(f.NestedObject, serializer));
                  }
              }
      
              jo.WriteTo(writer);
          }
      
          public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
          {
              throw new NotImplementedException();
          }
      }
      

      这是输出:

      class Program
      {
          static void Main(string[] args)
          {
              Form form = new Form
              {
                  Id = 1,
                  Field = "Value",
              };
              form.NestedObject = form;
      
              JsonSerializerSettings settings = new JsonSerializerSettings
              {
                  Converters = new List<JsonConverter> { new SerializationConverter() },
                  ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
              };
      
              string json = JsonConvert.SerializeObject(form, settings);
              Console.WriteLine(json);
          }
      } 
      
      class Form
      {
          public int Id { get; set; }
          public string Field { get; set; }
          public Form NestedObject { get; set; }
      }
      

      到目前为止看起来不错。更严格的事情怎么样:

      {"Id":1,"Field":"Value","NestedObject":{"Id":1}}
      

      输出:

      class Program
      {
          static void Main(string[] args)
          {
              List<Form> forms = new List<Form>
              {
                  new Form 
                  { 
                      Id = 1, 
                      Field = "One", 
                      NestedObject = new Form
                      {
                          Id = 2,
                          Field = "Two"
                      }
                  },
                  new Form
                  {
                      Id = 3,
                      Field = "Three"
                  },
                  new Form
                  {
                      Id = 4,
                      Field = "Four"
                  },
                  new Form
                  {
                      Id = 5,
                      Field = "Five"
                  }
              };
      
              forms[0].NestedObject.NestedObject = forms[3];
              forms[1].NestedObject = forms[0].NestedObject;
              forms[2].NestedObject = forms[1];
      
              JsonSerializerSettings settings = new JsonSerializerSettings
              {
                  Converters = new List<JsonConverter> { new SerializationConverter() },
                  ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                  Formatting = Formatting.Indented
              };
      
              string json = JsonConvert.SerializeObject(forms, settings);
              Console.WriteLine(json);
          }
      }
      

      编辑

      如果您的[ { "Id": 1, "Field": "One", "NestedObject": { "Id": 2, "Field": "Two", "NestedObject": { "Id": 5, "Field": "Five" } } }, { "Id": 3, "Field": "Three", "NestedObject": { "Id": 2 } }, { "Id": 4, "Field": "Four", "NestedObject": { "Id": 3 } }, { "Id": 5 } ] 类包含大量字段,则可能需要使用反射而不是在转换器中单独列出属性。以下是使用反射的Form方法:

      WriteJson