问题
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