Search code examples
c#castingmonotype-conversiondynamic-data

How to Dynamically convert JSON to right DTO class?


I have JSON data which I want to convert to correct type and then handle it. I'm using MONO and NewtonSoft's JSON library. I.E. JSON and object must match properties 1:1 to convert to right DTO. DTO's have unique properties always.

Both Activator.CreateInstance() and Convert.ChangeType() doesn't seem to compile.

DTOs:

class JSONDTO
{

}

class JSONCommandDTO : JSONDTO
{
    public string cmd;
}

class JSONProfileDTO : JSONDTO
{
    public string nick;
    public string name;
    public string email;
}

class JSONMessageDTO : JSONDTO
{
    public string msg;
}

Server:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;

class Server
{
    protected static List<JSONDTO> DTOList; 

    static void Main()
    {
        DTOList = new List<JSONDTO>();

        DTOList.Add(new JSONProfileDTO());
        DTOList.Add(new JSONCommandDTO());
        DTOList.Add(new JSONMessageDTO());

        // ...

    }

    protected static void OnMessage (string message)
    {
        dynamic rawObject;

        try
        {
            // Or try to convert to right DTO here somehow?
            rawObject = JsonConvert.DeserializeObject<dynamic>(message);
        } catch (JsonReaderException ex) {
            // Invalid JSON
            return;
        }

        int dtoCount = DTOList.ToArray().Length;
        int errCount = 0;

        JSONDTO DTOObject;

        foreach (var dto in DTOList.ToList()) {
            try {
                // Doesn't compile:

                // DTOObject = Activator.CreateInstance(dto.GetType(), rawObject);
                // DTOObject = Convert.ChangeType(rawObject, dto.GetType());
                break; // Match found!
            } catch (Exception ex) {
                // Didn't match
                errCount++;
            }
        }

        if (errCount == dtoCount) {
            // Right DTO was not found
            return;
        }


        if (DTOObject is JSONProfileDTO) {
            AssignProfile((JSONProfileDTO) DTOObject);
        }
        else if (DTOObject is JSONCommandDTO)
        {
            RunCommand((JSONCommandDTO) DTOObject);
        }
        // etc ..

    }

    protected static void RunCommand (JSONCommandDTO command)
    {
        string cmd = command.cmd;

        Console.WriteLine("command == " + cmd);
    }

    protected static void AssignProfile(JSONProfileDTO profile)
    {
        Console.WriteLine("got profile!");
    }

}

}


Solution

  • I got it to work. I had to add JsonSerializerSettings with MissingMemberHandling.Error so that exception gets thrown if JSON doesn't fit into object. I was also missing Microsoft.CSharp reference.

    class Server
    {
        protected static List<Type> DTOList = new List<Type>(); 
    
        static void Main()
        {
            DTOList.Add(typeof(JSONProfileDTO));
            DTOList.Add(typeof(JSONCommandDTO));
            DTOList.Add(typeof(JSONMessageDTO));
        }
    
        protected static void OnMessage (string rawString)
        {
            dynamic jsonObject = null;
            int DTOCount = DTOList.Count;
            int errors = 0;
    
            var settings = new JsonSerializerSettings ();
    
            // This was important
            // Now exception is thrown when creating invalid instance in the loop
            settings.MissingMemberHandling = MissingMemberHandling.Error;
    
            foreach (Type DTOType in DTOList) {
                try {
                    jsonObject = JsonConvert.DeserializeObject (rawString, DTOType, settings);
                    break;
                } catch (Exception ex) {
                    errors++;
                }
            }
    
            if (null == jsonObject) {
                return;
            }
    
            if (errors == DTOCount) {
                return;
            }
    
            if (jsonObject is JSONProfileDTO) {
                AssignProfile((JSONProfileDTO) jsonObject);
            }
            else if (jsonObject is JSONCommandDTO)
            {
                RunCommand((JSONCommandDTO) jsonObject);
            }
    
        }
    
    }