Deserialize into interface
Not long ago I was teaching a Programming in C# exam prep course. One of the modules involved serialization and a student asked me a question that was quite intriguing:
Given the serialized form of an object, is it possible to deserialize it into an object if we only have a reference to an interface of the original type? Short answer: no. Deserialization involves creating an instance, and you can't create instances of interfaces. But there is a long answer — read on if you're interested.
Deserializing into interfaces
So let's say that you have a JSON like this:
{
"Age": 27,
"Name":"Akos",
"Cars":[{
"LicensePlate":"ABC-123"
},
{
"LicensePlate":"DEF-123"
}]
}
This can be the Json form an instance of this type:
public class Car
{
public string LicensePlate { get; set; }
}
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public IEnumerable<Car> Cars { get; set; }
}
If you have these types, then you can use Newtonsoft.Json to simply deserialize the string into an instance of Person
. But what if you only have something like this:
public interface IPerson
{
int Age { get; set; }
string Name { get; }
IEnumerable<ICar> Cars { get; }
}
public interface ICar
{
string LicensePlate { get; set; }
}
If you only have these interfaces (and in many cases, you only have interfaces, because you know, architecture), how do you deserialize the Json? Of course, the easy way would be to implement the interfaces into you own type and then do the deserialization. Now this might not be that hard, especially with the advanced editing features of Visual Studio, but still, if you only have the interfaces, there's probably a good reason for that.
Generating types at runtime
But then again, if you have the interfaces, you can generate the types yourself using the Reflection.Emit API. Here are some brief outtakes of the code; you can check out the Github page for the full code.
First, you need to define an assembly and a module:
assemblyName = new AssemblyName("<>_ImplementationAssembly");
assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
moduleBuilder = assemblyBuilder.DefineDynamicModule("<>_Implementations");
Then we need to create a type and implement the interface in it. The interface implementation consists of two steps:
- Create a type and add all the properties of the interface to the type.
- Add a constructor to initialize the properties. This is an important step, because there are some properties which do not have setters.
var typeBuilder = moduleBuilder.DefineType(
$"{interfaceType.Name}Impl",
TypeAttributes.Class | TypeAttributes.NotPublic,
typeof(object),
new Type[] { interfaceType }
);
var properties = interfaceType.GetProperties();
var fields = new List<FieldBuilder>();
foreach (var property in properties)
{
GenerateProperty(typeBuilder, fields, property);
}
GenerateConstructor(typeBuilder, fields, properties);
var type = typeBuilder.CreateType();
TypeMap.Add(interfaceType, type);
return type;
This code defines a not public class and adds the interface type to the implementation. Then, for each interface-property a backing field and a property is created. And finally, the constructor is generated (TypeMap is just a dictionary to serve as a cache not to generate implementations for the same type twice).
Generating properties
When generating a property, first a backing field must be defined, then a property, then a getter and a setter method. It's quite simple, actually:
var propertyBuilder = typeBuilder.DefineProperty(
property.Name,
PropertyAttributes.None,
property.PropertyType,
null);
var fieldBuilder = typeBuilder.DefineField(
$"_{property.Name.ToLower()}",
GetFieldTypeForProperty(property),
FieldAttributes.Private);
fields.Add(fieldBuilder);
if (property.GetMethod != null)
{
GenerateGetter(typeBuilder, property, fieldBuilder, propertyBuilder);
}
if (property.SetMethod != null)
{
GenerateSetter(typeBuilder, property, fieldBuilder, propertyBuilder);
}
Generating a getter
Generating the getter of the property is a little more complicated. There is some actual IL code generation, and also the generated method must be marked as an interface-method-implementing-method.
private PropertyBuilder GenerateGetter(TypeBuilder typeBuilder, PropertyInfo property, FieldBuilder fieldBuilder, PropertyBuilder propertyBuilder)
{
MethodBuilder getterBuilder = typeBuilder.DefineMethod(
property.GetMethod.Name,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig |
MethodAttributes.Virtual,
property.PropertyType,
Type.EmptyTypes);
ILGenerator getterIL = getterBuilder.GetILGenerator();
getterIL.Emit(OpCodes.Ldarg_0);
getterIL.Emit(OpCodes.Ldfld, fieldBuilder);
getterIL.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(getterBuilder, property.GetMethod);
propertyBuilder.SetGetMethod(getterBuilder);
return propertyBuilder;
}
Generating a setter involves the same steps, just with different code. You can check out the code in the Github repo.
Generating the constructor
The constuctor that is to be generated must have a parameter to initialize every backing field (again, for properties that only have getters). The rest is just the initialization:
private void GenerateConstructor(TypeBuilder typeBuilder, List<FieldBuilder> fields, PropertyInfo[] properties)
{
var ctor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
properties.Select(p => p.PropertyType).ToArray());
for (int i = 0; i < properties.Length; i++)
{
ctor.DefineParameter(i + 1, ParameterAttributes.None, properties[i].Name);
}
var ctorIL = ctor.GetILGenerator();
for (int i = 0; i < fields.Count; i++)
{
ctorIL.Emit(OpCodes.Ldarg_0);
ctorIL.Emit(OpCodes.Ldarg, i + 1);
ctorIL.Emit(OpCodes.Stfld, fields[i]);
}
ctorIL.Emit(OpCodes.Ret);
}
Adding a JsonConverter
Finally, when the type-building logic is done, all that's needed is a way to plug this in into your deserialization mechanism. For Newtonsoft.Json, this means implementing a JsonConverter:
internal class TypeBuildingJsonConverter : JsonConverter
{
private readonly ImplementationBuilder typeBuilder = new ImplementationBuilder();
public void AddKnownType(Type interfaceType, Type implementationType) =>
typeBuilder.TypeMap.Add(interfaceType, implementationType);
public void AddKnownType<TInterface, TImplementation>() =>
AddKnownType(typeof(TInterface), typeof(TImplementation));
public override bool CanConvert(Type objectType) =>
typeBuilder.CanBuild(objectType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
serializer.Deserialize(reader, typeBuilder.GenerateType(objectType));
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> throw new NotSupportedException();
}
And if you're done, you can use this to "deserialize this into an interface" like this:
IPerson p = JsonConvert.DeserializeObject<IPerson>(jsonString, new TypeBuildingJsonConverter());
I have added some caching, some error messages and some failsafes to the code and uploaded the whole thing to Github. Feel free to use it, comments are welcome.
Note that the whole thing would have a lot easier if I had used Castle.DynamicProxy, I know. But I was going for fun, not easy :)