I have a simple use case where I want to deserialize a JSON that is basically an array of items, items are not identical but they all share a base class.
UPDATE: my technical limitations:
Type
property is the only information to do the correspondence between the derived class that I want to deserialize todynamic
onesHere is the code:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
var json = @" [{""Type"":0},{""Name"":""Derived"",""Type"":1}]";
var deserializedInstances = JsonConvert.DeserializeObject<List<BaseClass>>(json);
foreach(var e in deserializedInstances) {
if(e is BaseClass baseClass)
{
Console.WriteLine("Base Class , Type = {0}", baseClass.Type);
}else if(e is DerviedClass derivedClass)
{
Console.WriteLine("Derived Class , Type = {0}, Name = {1}", derivedClass.Type, derivedClass.Name);
}
}
// Output
// Base Class , Type = 0
// Base Class , Type = 0
}
public class BaseClass
{
public virtual int Type =>0;
}
public class DerviedClass: BaseClass
{
public string Name {get; set;}
public override int Type =>1;
}
}
so this code will produce this output:
// Base Class , Type = 0
// Base Class , Type = 0
but in my case, I want to have the instance of the derived class.
// Base Class , Type = 0
// Base Class , Type = 1, Name = "Derived"
What is the best way to achieve this in terms of performance?
Taking a detour over dynamic
to strongly typed:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
public static void Main()
{
var a = new BaseClass(){ Type = 0 };
var b = new DerivedClass(){ Type = 1, Name = "Hello" };
var list = new List<BaseClass>(){a,b};
var json = JsonConvert.SerializeObject(list);
Console.WriteLine(json);
var intermediate = JsonConvert.DeserializeObject<List<dynamic>>(json);
var result = new List<object>();
foreach( dynamic item in intermediate )
{
// Of course, you surely could optimize the conversion:
if( item.Type == 0 ) result.Add( new BaseClass(){Type = item.Type});
if( item.Type == 1 ) result.Add( new DerivedClass(){Type= item.Type, Name= item.Name});
}
Console.WriteLine($"[{string.Join(", ",result)}]");
}
}
public class BaseClass
{
public int Type {get; set;}
}
public class DerivedClass: BaseClass
{
public string Name {get; set;}
}
produces output on fiddle:
[{"Type":0},{"Name":"Hello","Type":1}] [BaseClass, DerviedClass]
Mind, that this is just a proof of concept. Of course, you'd need to fortify and find some decent algorithm to get from dynamic to your desired strong type.
This fiddle shows some possibilities to improve on effort for many Derived Classes: https://dotnetfiddle.net/zECBx5
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public class Program
{
public static void Main()
{
var a = new BaseClass(){ Type = 0 };
var b = new DerivedClass(){ Type = 1, Name = "Hello" };
var list = new List<BaseClass>(){a,b};
var json = JsonConvert.SerializeObject(list);
Console.WriteLine(json);
var intermediate = JsonConvert.DeserializeObject<List<dynamic>>(json);
var result = Construct( intermediate );
Console.WriteLine($"[{string.Join(", ",result.Select(x => x?.ToString() ?? "NULL"))}]");
}
public static List<object> Construct( List<dynamic> items )
{
var result = new List<object>();
Console.WriteLine( $"Processing {items.Count} dynamic items" );
foreach( dynamic item in items )
{
result.Add(Construct( item ));
}
return result;
}
private static Dictionary<int, Func<dynamic, object>> factoryMap = new () {
{0 , Construct<BaseClass>},
{1 , Construct<DerivedClass>},
};
public static object Construct( dynamic item )
{
Console.WriteLine($"Item Type = {item.Type}");
object result = null;
result = factoryMap[(int)item.Type](item);
return result;
}
public static TResult Construct<TResult>( dynamic item ) where TResult: class, new()
{
Console.WriteLine($"Constructing a {typeof(TResult).ToString()}");
TResult result = new();
foreach( var property in result.GetType().GetProperties() )
{
JObject jo = item as JObject;
var propVal = jo.Property(property.Name).ToObject(property.PropertyType);
Console.WriteLine($"Setting property {property.Name} to value {propVal}");
property.SetValue( result, propVal );
}
return result;
}
}
public class BaseClass
{
public int Type {get; set;}
}
public class DerivedClass: BaseClass
{
public string Name {get; set;}
}