自定义参考循环处理



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

预期成果

 { 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)的序列化程序会再次调用我的转换器。

仅当对象已序列化时,我才尝试替换序列化结果,否则使用默认序列化行为。

首先,我想提一下,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 就没有办法知道如何对NestedObject进行特殊的序列化处理! 相反,它只会被省略,因为我们将被迫将ReferenceLoopHandling设置为Ignore以避免错误。 所以你看,你有一个第 22 条军规。

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

  1. 如果我们遇到已经看到的形式,我们只想输出Id .
  2. 否则,将表单添加到我们看到的表单列表中,然后输出 IdFieldNestedObject

请注意,在这两种情况下我们都想输出 Id ,因此我们可以将逻辑简化为:

  1. 始终输出 ID
  2. 如果我们遇到我们尚未看到的表单,请将该表单添加到我们见过的表单列表中,然后输出FieldNestedObject

为了简化操作,我们可以使用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方法的外观:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
    Form f = (Form)value;
    JObject jo = new JObject();
    if (serializedForms.Add(f))
    {
        foreach (PropertyInfo prop in value.GetType().GetProperties())
        {
            if (prop.CanRead)
            {
                object propVal = prop.GetValue(value);
                if (propVal != null)
                {
                    jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
                }
            }
        }
    }
    else
    {
        jo.Add("Id", f.Id);
    }
    jo.WriteTo(writer);
}

相关内容

  • 没有找到相关文章

最新更新