Slava
Slava

Reputation: 6650

How to convert each object in List<ExpandoObject> into its own type?

I have a variable inputs defined as List<ExpandoObject>

This was a result of de-serialization of jsonList, which is a JSON array of objects of different structures:

dynamic inputs = JsonConvert.DeserializeObject<List<ExpandoObject>>(jsonList, converter);

Looping through them I can get a target type of each object, because they all contain a property Type that has a ClassName of a target object.

foreach (dynamic input in inputs)
{
    // Inside that loop I can get the type
    var inputType = Type.GetType(string.Format("WCFService.{0}", input.Type));
    // WCFService is a namespace
    // How can I convert here dynamic **input** object 
    // into an object of type inputType ??
}

Basically I want inside that loop convert input object into corresponding type specified as string in input.Type

Any help is appreciated.

EDIT

Inside that for-each loop I want to do something like this:

var json = JsonConvert.SerializeObject(input);
Type T = Type.GetType(string.Format("WCFService.{0}", input.Type));
T obj = JsonConvert.DeserializeObject<typeof(T)>(json); // this line fails compilation

This way obj will be a strongly-typed object. I use json serialization in order to do deserialization back, this way all json properies will be replicated into strongly-typed obj automatically. But the above code doesnt compile, complaining on T for the last line:

The type or namespace name 'T' could not be found (are you missing a using directive or an assembly reference?)

EDIT2

Just FYI, the incoming jsonList has this structure, each object in that array can have different properties, except of Name and Type:

[
    {
        "Name": "PLAN-A",
        "Type": "CalcInputTypes1",
        "CS": 1.1111,
        "CUSTOM_DATE1": "2015-05-22",
        "CUSTOM_EARN1": 65500.0,
        "GENDER": "Male"
    },
    {
        "Name": "PLAN-B",
        "Type": "CalcInputTypes2",
        "CS": 2.22222,
        "CUSTOM_DATE2": "2015-05-23",
        "CUSTOM_EARN2": 12000.0,
        "PROVINCE": "Ontario"
    }
]

CalcInputTypes1, CalcInputTypes2 and most likely CalcInputTypes3,4,5... are the types of such objects in that array...

SOLUTION

Thanks guys for your help, especially for suggesting using JObject instead of ExpandoObject, that made the solution much easier and simpler: Note: "thing" will never work, since T in this case must be known at compile time, but I neet to determine type at runtime, so the solution will be like that:

    public CalcOutputTypes Calculate2(string jsonList)
    {
        var jobjects = JsonConvert.DeserializeObject<List<JObject>>(jsonList);

        foreach (var jobject in jobjects)
        {
            Type runtimeType = Type.GetType(string.Format("WCFService.{0}", jobject.GetValue("TYPE")));

            var input = jobject.ToObject(runtimeType); // Here we convert JObject to the defined type that just created runtime

            // At this moment you have a strongly typed object "input" (CalcInputTypes1 or CalcInputTypes2 or...)

        }

        return new CalcOutputTypes() { STATUS = "Everything is OK !! (input was: json array of heterogeneous objects)" }; // HERE YOU RETURN CalcOutputTypes OBJECT 
    }

Upvotes: 4

Views: 4638

Answers (3)

Victor
Victor

Reputation: 628

You do not need list of ExpandoObjext's, just use CustomCreationConverter like described in Deserializing heterogenous JSON array into covariant List<> using JSON.NET so all credits to @JimSan

public class Example
{

    [Test]
    public void Test()
    {
        var json =
            "[\r\n    {\r\n        \"Name\": \"PLAN-A\",\r\n        \"Type\": \"CalcInputTypes1\",\r\n        \"CS\": 1.1111,\r\n        \"CUSTOM_DATE1\": \"2015-05-22\",\r\n        \"CUSTOM_EARN1\": 65500.0,\r\n        \"GENDER\": \"Male\"\r\n    },\r\n    {\r\n        \"Name\": \"PLAN-B\",\r\n        \"Type\": \"CalcInputTypes2\",\r\n        \"CS\": 2.22222,\r\n        \"CUSTOM_DATE2\": \"2015-05-23\",\r\n        \"CUSTOM_EARN2\": 12000.0,\r\n        \"PROVINCE\": \"Ontario\"\r\n    }\r\n]";

        var result = JsonConvert.DeserializeObject<List<Item>>(json, new JsonItemConverter());

        Assert.That(result[0], Is.TypeOf<CalcInputTypes1>());
        Assert.That(((CalcInputTypes1)result[0]).Gender, Is.EqualTo("Male"));

        Assert.That(result[1], Is.TypeOf<CalcInputTypes2>());
        Assert.That(((CalcInputTypes2)result[1]).Province, Is.EqualTo("Ontario"));
    }

    public class JsonItemConverter : Newtonsoft.Json.Converters.CustomCreationConverter<Item>
    {
        public override Item Create(Type objectType)
        {
            throw new NotImplementedException();
        }

        public Item Create(Type objectType, JObject jObject)
        {
            var type = (string)jObject.Property("Type");
            switch (type)
            {
                case "CalcInputTypes1":
                    return new CalcInputTypes1();
                case "CalcInputTypes2":
                    return new CalcInputTypes2();
            }

            throw new ApplicationException(String.Format("The given type {0} is not supported!", type));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // Load JObject from stream
            var jObject = JObject.Load(reader);

            // Create target object based on JObject
            var target = Create(objectType, jObject);

            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);

            return target;
        }
    }

    public abstract class Item
    {
        public string Type { get; set; }
    }

    public class CalcInputTypes1 : Item
    {
        [JsonProperty("GENDER")]
        public string Gender { get; set; }
    }

    public class CalcInputTypes2 : Item
    {
        [JsonProperty("PROVINCE")]
        public string Province { get; set; }
    }
}

Upvotes: 1

dbc
dbc

Reputation: 117046

You could avoid using ExpandoObject and instead use LINQ to JSON directly, like so:

        var query = from obj in JsonConvert.DeserializeObject<List<JObject>>(jsonList, converter)
                    let jType = obj["Type"]
                    where jType != null
                    let type = Type.GetType(string.Format("WCFService.{0}", (string)jType))
                    where type != null
                    where obj.Remove("Type") // Assuming this is a synthetic property added during serialization that you want to remove.
                    select obj.ToObject(type);
        var objs = query.ToList();

If you need to pass that converter down to each specific ToObject() call, you can do:

        var settings = new JsonSerializerSettings();
        settings.Converters.Add(converter);
        var serializer = JsonSerializer.Create(settings);

        var query = from obj in JsonConvert.DeserializeObject<List<JObject>>(jsonList, settings)
                    let jType = obj["Type"]
                    where jType != null
                    let type = Type.GetType(string.Format("WCFService.{0}", (string)jType))
                    where type != null
                    where obj.Remove("Type") // Assuming this is a synthetic property added during serialization that you want to remove.
                    select obj.ToObject(type, serializer);
        var objs = query.ToList();

Upvotes: 2

keenthinker
keenthinker

Reputation: 7830

Another possible solution would be as @dbc suggested in a comment to use the Newtonsoft.Json method JsonConvert.DeserializeObject(json, type) like this:

private T convertTo<T>(string json)
{
    return (T)JsonConvert.DeserializeObject(json, typeof(T));
}

var json = // some serialized json ...
var o = convertTo<MyCustomType>(json);

where MyCustomType is the type from your input (var inputType = Type.GetType(string.Format("WCFService.{0}", input.Type));.

Don't forget the implicit cast (T)!

Another possibility is to write your own type converter using the .NET framework build-in TypeConverter class.

Upvotes: 1

Related Questions