自定义JsonConverter自引用循环

时间:2016-08-04 15:23:46

标签: c# json.net

有没有办法可以创建自己的自定义JsonConverter,它在写出Json之前修改数据,它与嵌套的父子结构一起使用?每当我尝试此刻时,我最终都会遇到自我引用循环错误。我已经尝试过寻找解决方案,但我找不到匹配的东西。

我已经创建了一个简单的解决方案来证明这个问题。

我有以下型号和转换器

public class NestedModel
{
    public int Id { get; set; }

    public string Forename { get; set; }

    public string Surname { get; set; }

    public string Custom { get; set; }


    public List<SimpleModel> Children { get; set; }
}

public class NestedModelJsonConverter : JsonConverter
{

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var nestedModel = value as NestedModel;
        nestedModel.Custom = "Modified by Json Converter";

        //This causes a self referencing loop error
        serializer.Serialize(writer, value);

        //This resolves the self referencing loop error, but it does not call my custom Json Converter for any of the Children, and instead uses the default serialization
        //var jo = JObject.FromObject(value);
        //jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        NestedModel target = new NestedModel();

        // Populate the object properties
        StringWriter writer = new StringWriter();
        serializer.Serialize(writer, jObject);
        using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString())))
        {
            newReader.Culture = reader.Culture;
            newReader.DateParseHandling = reader.DateParseHandling;
            newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
            newReader.FloatParseHandling = reader.FloatParseHandling;
            serializer.Populate(newReader, target);
        }

        return target;
    }

    public override bool CanRead { get { return true; } }

    public override bool CanWrite { get { return true; } }

    public override bool CanConvert(Type objectType) { return typeof(NestedModel).IsAssignableFrom(objectType); }
}

以及使用它的测试代码

string sourceJson = @"{
    ""Id"": 1,
    ""Forename"": ""John"",
    ""Surname"": ""Smith"",
    ""Children"":
    [
        {
            ""Id"": 2,
            ""Forename"": ""Joe"",
            ""Surname"": ""Bloggs"",
            ""Children"": null
        }
    ]
}";

var settings = new JsonSerializerSettings()
{
    Converters = new List<JsonConverter>()
    {
        new NestedModelJsonConverter()
    },
    Formatting = Formatting.Indented
};
var nestedModel = JsonConvert.DeserializeObject<NestedModel>(sourceJson, settings);

string outputJson = JsonConvert.SerializeObject(nestedModel, settings);

然而,当它试图编写Json时,它会给出一个自引用循环错误,大概是当它试图处理List of Children时。 我可以通过使用JObject进行转换来防止该错误,但这会阻止我的自定义转换器用于子元素。我希望为该结构的每个级别触发自定义WriteJson方法,以便在编写之前修改一些数据。

有没有办法解决自我引用循环错误?

1 个答案:

答案 0 :(得分:1)

经过一些工作,我找到了一种方法,所以我想我会把它发布给其他有类似问题的人。我必须确保在序列化NestedModel时,我使用反射单独序列化每个属性(如果该属性是NestedModel的列表,则为每个属性再次调用我的序列化器)。

这是模型/自定义转换器

public class NestedModel
{
    public int Id { get; set; }

    public string Forename { get; set; }

    public string Surname { get; set; }

    public string Custom { get; set; }


    public List<NestedModel> Children { get; set; }
}

public class NestedModelJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var nestedModel = value as NestedModel;
        nestedModel.Custom = "Modified by Json Converter";

        JObject jo = new JObject();
        Type type = nestedModel.GetType();

        foreach (PropertyInfo prop in type.GetProperties())
        {
            if (prop.CanRead)
            {
                object propVal = prop.GetValue(nestedModel, null);
                if (propVal != null)
                {
                    jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
                }
            }
        }
        jo.WriteTo(writer);
    }


    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;

        // Load JObject from stream
        JObject jObject = JObject.Load(reader);

        // Create target object based on JObject
        NestedModel target = new NestedModel();


        // Populate the object properties
        StringWriter writer = new StringWriter();
        serializer.Serialize(writer, jObject);
        using (JsonTextReader newReader = new JsonTextReader(new StringReader(writer.ToString())))
        {
            newReader.Culture = reader.Culture;
            newReader.DateParseHandling = reader.DateParseHandling;
            newReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
            newReader.FloatParseHandling = reader.FloatParseHandling;
            serializer.Populate(newReader, target);
        }

        return target;
    }

    public override bool CanRead { get { return true; } }

    public override bool CanWrite { get { return true; } }

    public override bool CanConvert(Type objectType) 
    { 
        return typeof(NestedModel).IsAssignableFrom(objectType); 
    }
}

和测试代码

string sourceJson = @"{
    ""Id"": 1,
    ""Forename"": ""John"",
    ""Surname"": ""Smith"",
    ""Children"":
    [
        {
            ""Id"": 2,
            ""Forename"": ""Joe"",
            ""Surname"": ""Bloggs"",
            ""Children"": null
        }
    ]
}";

var settings = new JsonSerializerSettings()
{
    Converters = new List<JsonConverter>()
    {
        new NestedModelJsonConverter()
    },
    Formatting = Formatting.Indented,
};
var nestedModel = JsonConvert.DeserializeObject<NestedModel>(sourceJson, settings);

string outputJson = JsonConvert.SerializeObject(nestedModel, settings);

创建的Json然后是

{
  "Id": 1,
  "Forename": "John",
  "Surname": "Smith",
  "Custom": "Modified by Json Converter",
  "Children": [
    {
      "Id": 2,
      "Forename": "Joe",
      "Surname": "Bloggs",
      "Custom": "Modified by Json Converter"
    }
  ]
}