Search code examples
c#jsonjson.netjson-deserialization

Newtonsoft Json deserialization into specific types


I have a class Action and many other specific classes that derive from this one like SendCoordinates, MakeCall, etc..

So given a JSON response like this one:

 {
      "action":{
          "type":"SendCoordinates",
          "data": "41°24'12.2 N 2°10'26.5"
       }
}

Now my question is regarding Newtonsoft json library. What is the best way to implement this ? Should I create specific JSON converters for each class like they show here http://www.newtonsoft.com/json/help/html/DeserializeCustomCreationConverter.htm

Or should I go for a entirelly different aproach that I'm not considering ? Can you guys leave me your opinions on this ? Thanks in advance


Solution

  • With Newtonsoft.Json you can deserialise to a type via the non-generic overload DeserializeObject(string value, type type).

    This means you can use the Type property as a hint to which type to deserialize.

    1. deserialise to base type
    2. get type name of actual Action object
    3. get type of type full name
    4. deserialise to derived action type

    See the following example:

    using System;
    using Newtonsoft.Json;
    
    namespace com.example.SO42736347
    {
        public class Action
        {
            public string Type { get; set; }
        }
    
        public class Action1 : Action
        {
            public string Data { get; set; }
        }
    
        public class Program
        {
    
            public const string ACTION1 = @"{
                ""Type"" : ""com.example.Json.Action1"",
                ""Data"" : ""41°24'12.2 N 2°10'26.5""
            }";
    
            public static void Main()
            {
                var action = JsonConvert.DeserializeObject<Action>(ACTION1);
    
                var type = Type.GetType(action.Type);
    
                var action1 = (Action1) JsonConvert.DeserializeObject(ACTION1, type);
            }
    
        }
    }
    

    If you do not want to specify the full qualified type name in the Type field, you can construct the FullName of the type by using custom programme logic (e.g. prefixing it with a base namespace).

    Edit: according to the comments it should be able to deserialise a list of actions (of derived types). Therefore I appended the following example to my answer where you can see, how to deserialise a list of actions by doing the following:

    1. deserialise to generic JArray
    2. for each item in array determine the Type of the Action
    3. deserialise into the specific derived type

    I also added a loop after the conversion to show how to further process the converted actions.

    using System;
    using Newtonsoft.Json;
    using System.Collections.Generic; 
    using Newtonsoft.Json.Linq;
    
    namespace com.example.Json
    {
        public class Action
        {
            public string Type { get; set; }
        }
    
        public class Action1 : Action
        {
            public string Data { get; set; }
        }
    
        public class Action2 : Action
        {
            public string SomeProperty { get; set; }
        }
    
        public class Program
        {
    
            public const string ACTION1 = @"{
            ""Type"" : ""com.example.Json.Action1"",
                ""Data"" : ""41°24'12.2 N 2°10'26.5""
            }";
    
            public const string ACTION2 = @"{
            ""Type"" : ""com.example.Json.Action2"",
                ""SomeProperty"" : ""arbitrary-value""
            }";
    
            public const string ACTIONS =  
                "[" + 
                ACTION1 +
                "," + 
                ACTION2 +
                "]" ;
    
            public static void Main()
            {
    
                var actions = new List<Action>();
    
                JArray jArray = JArray.Parse(ACTIONS);
                foreach(var item in jArray)
                {
                    var json = JsonConvert.SerializeObject(item);
                    var baseAction = JsonConvert.DeserializeObject<Action>(json);
                    var type = Type.GetType(baseAction.Type);
                    var action = (Action) JsonConvert.DeserializeObject(json, type);
                    actions.Add(action);
                }
    
                // now that we have converted all array items into specific derived action objects
                // we can start processing them anyway we want
                // keep in mind that you have to check the runtime type in order to find out what
                // specific kind of action we have
    
                // eg.
                foreach(var action in actions)
                {
                    switch(action.Type)
                    {
                        case "com.example.Json.Action1":
                            // do something
                            Console.WriteLine("found com.example.Json.Action1");
                            Console.WriteLine((action as Action1).Data);
                            break;
                        case "com.example.Json.Action2":
                            // do something
                            Console.WriteLine("found com.example.Json.Action2");
                            Console.WriteLine((action as Action2).SomeProperty);
                            break;
                        default:
                            // do something
                            Console.WriteLine("found something else");
                            break;
                    }
                }
    
            }
    
        }
    }