I've got some inheritance going, requiring a custom JsonConverter
for deserialization. I'm using a very straightforward approach for now where I determine the type based on the existence of certain properties.
Important note: in my actual code I cannot touch the DeserializeObject
calls, i.e. I cannot add custom convertors there. I know this is therefor to some degree an XY-problem, and realize as such my answer might be that what I want is not possible. As far as I can tell this makes my question slightly different from this question.
Here's a repro of my situation:
abstract class Mammal { }
class Cat : Mammal { public int Lives { get; set; } }
class Dog : Mammal { public bool Drools { get; set; } }
class Person
{
[JsonConverter(typeof(PetConverter))]
public Mammal FavoritePet { get; set; }
[JsonConverter(typeof(PetConverter))]
public List<Mammal> OtherPets { get; set; }
}
And this is the custom converter:
public class PetConverter : JsonConverter
{
public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
JObject jsonObject = JObject.Load(reader);
if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer);
if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer);
return null;
}
}
This works fine for the FavoritePet
, but not so much for the OtherPets
because it's a list. Here's a way to reproduce my problem with NUnit tests:
[TestFixture]
class MyTests
{
[Test]
public void CanSerializeAndDeserializeSingleItem()
{
var person = new Person { FavoritePet = new Cat { Lives = 9 } };
var json = JsonConvert.SerializeObject(person);
var actual = JsonConvert.DeserializeObject<Person>(json);
Assert.That(actual.FavoritePet, Is.InstanceOf<Cat>());
}
[Test]
public void CanSerializeAndDeserializeList()
{
var person = new Person { OtherPets = new List<Mammal> { new Cat { Lives = 9 } } };
var json = JsonConvert.SerializeObject(person);
var actual = JsonConvert.DeserializeObject<Person>(json);
Assert.That(actual.OtherPets.Single(), Is.InstanceOf<Cat>());
}
}
The latter test is red because:
Newtonsoft.Json.JsonReaderException : Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path 'OtherPets', line 1, position 33.
I've also tried without the custom converter on OtherPets
, which results in:
Newtonsoft.Json.JsonSerializationException : Could not create an instance of type JsonConverterLists.Mammal. Type is an interface or abstract class and cannot be instantiated. Path 'OtherPets[0].Lives', line 1, position 42.
I understand what's going on, I even know that I could fix it with:
var actual = JsonConvert.DeserializeObject<Person>(json, new PetConverter());
But repeating the note from above: I can't change the DeserializeObject
call as it's wrapped inside a function in a library I cannot currently change.
Is there a way to do the same with a attribute-based approach, e.g. is there a built-in converter for lists where each entry takes in a custom converter? Or do I have to roll my own, seperate converter for this too?
Footnote, how to reproduce:
Install-Package Newtonsoft.Json -Version 7.0.1
Install-Package nunit -Version 2.6.4
You can just drop the above three code blocks in your fresh namespace and run the NUnit tests, seeing the second one fail.
The JSON contains a list of objects which is abstract and it's not possible to deserialize it.
DeserializeObject<T>(String,JsonConverter[]) Deserializes the JSON to the specified . NET type using a collection of JsonConverter.
tweaked the converter class a little bit. hope it's good -
public class PetConverter : JsonConverter
{
public override bool CanConvert(Type objectType) { return objectType == typeof(Mammal); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
if (reader.TokenType == JsonToken.StartArray)
{
var li = new List<Mammal>();
var arr = JArray.Load(reader);
foreach (JObject obj in arr)
{
if (obj["Drools"] != null)
{
var k = obj.ToObject<Dog>(serializer);
li.Add(k);
}
}
return li;
}
JObject jsonObject = JObject.Load(reader);
if (jsonObject["Lives"] != null) return jsonObject.ToObject<Cat>(serializer);
//if (jsonObject["Drools"] != null) return jsonObject.ToObject<Dog>(serializer);
return null;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With