Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON.NET: How to deserialize interface property based on parent (holder) object value?

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?

like image 690
Zoka Avatar asked Mar 20 '14 15:03

Zoka


People also ask

How do I deserialize an object in JSON?

A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.

What is JsonSerializer deserialize?

Serialization and deserialization in . NET application, JSON data format conversion to . NET objects and vice versa is very common. Serialization is the process of converting . NET objects such as strings into a JSON format and deserialization is the process of converting JSON data into . NET objects.

What is Jsonconvert?

Provides methods for converting between . NET types and JSON types. Newtonsoft.Json.

Is an interface or abstract class and Cannot be instantiated?

Abstract classes are similar to interfaces. You cannot instantiate them, and they may contain a mix of methods declared with or without an implementation. However, with abstract classes, you can declare fields that are not static and final, and define public, protected, and private concrete methods.


1 Answers

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 
like image 164
Brian Rogers Avatar answered Sep 28 '22 09:09

Brian Rogers