Custom inheritance JsonConverter fails when JsonConverterAttribute is used

戏子无情 提交于 2019-12-22 11:04:12

问题


I am trying to deserialize derived type, and I want to use a custom property Type to distinguish between derived types.

[
  {
    "Type": "a",
    "Height": 100
  },
  {
    "Type": "b",
    "Name": "Joe"
  }
]

The solution I came to was to create a custom JsonConverter. On ReadJson I read the Type property and instantiate that type through the ToObject<T> function. Everything works fine until I use a JsonConverterAttribute. The ReadJson method loops infinitely because the attribute is applied on subtypes too.

How do I prevent this attribute from being applied to the subtypes?

[JsonConverter(typeof(TypeSerializer))]
public abstract class Base
{
    private readonly string type;

    public Base(string type)
    {
        this.type = type;
    }

    public string Type { get { return type; } }
}

public class AType : Base
{
    private readonly int height;

    public AType(int height)
        : base("a")
    {
        this.height = height;
    }

    public int Height { get { return height; } }
}

public class BType : Base
{
    private readonly string name;

    public BType(string name)
        : base("b")
    {
        this.name = name;
    }

    public string Name { get { return name; } }
}

public class TypeSerializer : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Base);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var j = JObject.Load(reader);

        var type = j["Type"].ToObject<string>();

        if (type == "a")
            // Infinite Loop! StackOverflowException
            return j.ToObject<AType>(); 
        if (type == "b")
            return j.ToObject<BType>();

        throw new NotImplementedException(type);
    }
}

[TestFixture]
public class InheritanceSerializeTests
{
    [Test]
    public void Deserialize()
    {
        var json = @"{""Type"":""a"", ""Height"":100}";
        JObject.Parse(json).ToObject<Base>(); // Crash
    }
}

回答1:


I had a very similar problem with a project that I am currently working on: I wanted to make a custom JsonConverter and map it to my entities via attributes, but then the code got trapped in an infinite loop.

What did the trick in my case was using serializer.Populate instead of JObject.ToObject (I couldn't use .ToObject even if I wanted to; I am using version 3.5.8, in which this function does not exist). Below is my ReadJson method as an example:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    JContainer lJContainer = default(JContainer);

    if (reader.TokenType == JsonToken.StartObject)
    {
        lJContainer = JObject.Load(reader);
        existingValue = Convert.ChangeType(existingValue, objectType);
        existingValue = Activator.CreateInstance(objectType);

        serializer.Populate(lJContainer.CreateReader(), existingValue);
    }

    return existingValue;
}



回答2:


Remove the [JsonConverter(typeof(TypeSerializer))] attribute from the Base class and in the Deserialize test replace the following line:

JObject.Parse(json).ToObject<Base>(); // Crash

with this one:

var obj = JsonConvert.DeserializeObject<Base>(json, new TypeSerializer());

UPDATE 1 This update matches the comment from the asker of the question:

Leave the [JsonConverter(typeof(TypeSerializer))] attribute to the Base class. Use the following line for deserialization:

var obj = JsonConvert.DeserializeObject<Base>(json);

and modify the ReadJson method like this:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var j = JObject.Load(reader);

    if (j["Type"].ToString() == "a")
        return new AType(int.Parse(j["Height"].ToString()));

    return new BType(j["Name"].ToString());
}



回答3:


I had a similar problem to this and encountered the infinite loop.

The Api I was consuming could return an error response or the expected type. I got around the problem in the same way as others have highlighted with using serializer.Populate.

public class Error{
   public string error_code { get; set; }
   public string message { get; set; }
}

[JsonConverter(typeof(CustomConverter<Success>))]
public class Success{
   public Guid Id { get; set; }
}

public class CustomConverter<T> : JsonConverter where T : new() {
   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
   {
      JObject jObject = JObject.Load(reader);
      if (jObject.ContainsKey("error_code")) {
          return jObject.ToObject(typeof(ProvisoErrorResponse));
      }

      var instance = new T();
      serializer.Populate(jObject.CreateReader(), instance);
      return instance;
   }
}

Then used by HttpClient like this:

using (var response = await _httpClient.GetAsync(url))
{
   return await response.Content.ReadAsAsync<Success>();
}

Why does this loop occur? I think we're assuming a fallacy. I initially tried calling base.ReadJson thinking that I was overriding existing functionality when in fact there are many JsonConverters and our custom converter isnt overriding anything as the base class has no real methods. It would be better to treat the base class like an interface instead. The loop occurs because the converter registered by us is the converter that the engine considers most applicable to the type to be converted. Unless we can remove our own converter from the converter list at runtime, calling on the engine to deserialize while within our custom converter will create infinite recursion.



来源:https://stackoverflow.com/questions/25404202/custom-inheritance-jsonconverter-fails-when-jsonconverterattribute-is-used

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!