I have such classes
class Holder {
public int ObjType { get; set; }
public List<Base> Objects { get; set; }
}
abstract class Base {
// ... doesn't matter
}
class DerivedType1 : Base {
// ... doesn't matter
}
class DerivedType2 : Base {
// ... doesn't matter
}
Using WebAPI I want to receive the object Holder
and deserialize it correctly. Based on the ObjType
value I need Objects
property to be deserialized either as List<DerivedType1>
(ObjType == 1
) or List<DerivedType2>
(ObjType == 2
).
At the moment I searched SO and internet for best approach, but the best I've found is this answer https://stackoverflow.com/a/8031283/1038496. The problem of this solution is, that it loses context of parent object, so I cannot find out the value of ObjType
. OK, I could solve it by creating custom JsonConverter
for Holder
and remebering the ObjType
value, but still I'm afraid of this line:
serializer.Populate(jObject.CreateReader(), target);
as the comment below this answer says:
The new JsonReader created in the ReadJson method does not inherit any of the original reader's configuration values (Culture, DateParseHandling, DateTimeZoneHandling, FloatParseHandling, etc...). These values should be copied over before using the new JsonReader in serializer.Populate().
which is a problem for me and copying these values by myself doesn't seem clean to me (what if I miss something?)
So the question is: Is there a better approach I've missed to deserialize abstract object property based on parent property value?
You are on the right track. You do need to implement a custom JsonConverter
for your Holder
class to handle this situation, as you suggested. But, don't worry, it is possible to write the converter in such a way that you can use the original reader and serializer instances passed to the converter, without ever needing to copy the settings over to new instances. Here is how I would write it:
class HolderConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Holder));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
Holder holder = new Holder();
holder.ObjType = (int)jo["ObjType"];
holder.Objects = new List<Base>();
foreach (JObject obj in jo["Objects"])
{
if (holder.ObjType == 1)
holder.Objects.Add(obj.ToObject<DerivedType1>(serializer));
else
holder.Objects.Add(obj.ToObject<DerivedType2>(serializer));
}
return holder;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a quick demo:
class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""ObjType"" : 1,
""Objects"" :
[
{ ""Id"" : 1, ""Foo"" : ""One"" },
{ ""Id"" : 2, ""Foo"" : ""Two"" },
]
},
{
""ObjType"" : 2,
""Objects"" :
[
{ ""Id"" : 3, ""Bar"" : ""Three"" },
{ ""Id"" : 4, ""Bar"" : ""Four"" },
]
},
]";
List<Holder> list = JsonConvert.DeserializeObject<List<Holder>>(json);
foreach (Holder holder in list)
{
if (holder.ObjType == 1)
{
foreach (DerivedType1 obj in holder.Objects)
{
Console.WriteLine("Id: " + obj.Id + " Foo: " + obj.Foo);
}
}
else
{
foreach (DerivedType2 obj in holder.Objects)
{
Console.WriteLine("Id: " + obj.Id + " Bar: " + obj.Bar);
}
}
}
}
}
[JsonConverter(typeof(HolderConverter))]
class Holder
{
public int ObjType { get; set; }
public List<Base> Objects { get; set; }
}
abstract class Base
{
public int Id { get; set; }
}
class DerivedType1 : Base
{
public string Foo { get; set; }
}
class DerivedType2 : Base
{
public string Bar { get; set; }
}
Output:
Id: 1 Foo: One
Id: 2 Foo: Two
Id: 3 Bar: Three
Id: 4 Bar: Four