I have a class, with a property that is an interface.
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
Attempting to deserializing the Foo
class using Json.NET gives me an error message like:
Could not create an instance of type ISomething. Type is an interface or abstract class and cannot be instantiated.
From a similar question, I can see that using TypeNameHandling = TypeNameHandling.Objects
will resolve the error by allowing Json.NET to include the .NET type name when serialising, and thus knowing which concrete type it needs to deserialize the object into afterwards.
However, it seems that there is caution advised when using a TypeNameHandling
value other than TypeNameHandling.None
to deserialise JSON from an external source.
TypeNameHandling.Objects
clearly falls into this category.
Is there a way to deserialise concrete object types (which implement an interface) without introducing any security risks?
Considering you have no field to determine which concrete type to convert to, your only solution is as you state, TypeNameHandling = TypeNameHandling.Objects
.
However, you can mitigate the security vulnerability.
Use a custom ISerializationBinder
to validate incoming type names as they resolve .NET types to type names during serialization & type names to .NET types during deserialization:
ConsoleAppSerializationBinder.cs
public class ConsoleAppSerializationBinder : ISerializationBinder
{
public Type BindToType(string? assemblyName, string typeName)
{
var resolvedTypeName = $"{typeName}, {assemblyName}";
return Type.GetType(resolvedTypeName, true);
}
public void BindToName(Type serializedType, out string? assemblyName, out string? typeName)
{
assemblyName = null;
typeName = serializedType.AssemblyQualifiedName;
}
}
Set the SerializationBinder
property on your JsonSerializerSettings
object to an instance of your binder, and deserialise with a binder of the same type used to serialise the data in the first place.
var jsonSerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = new ConsoleAppSerializationBinder()
};
Here's some demo working code to demonstrate usage:
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var jsonSerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = new ConsoleAppSerializationBinder()
};
var something1Input = new Foo
{
Number = 1,
Thing = new Something1
{
RandomNumber = 1,
RandomString = "test"
}
};
var something2Input = new Foo
{
Number = 1,
Thing = new Something2
{
RandomNumber = 2,
RandomBool = true
}
};
var something1Json = JsonConvert.SerializeObject(something1Input, jsonSerializerSettings);
var something2Json = JsonConvert.SerializeObject(something2Input, jsonSerializerSettings);
var something1 = JsonConvert.DeserializeObject<Foo>(something1Json, jsonSerializerSettings);
var something2 = JsonConvert.DeserializeObject<Foo>(something2Json, jsonSerializerSettings);
Console.WriteLine(something1.Thing.GetType());
Console.WriteLine(something2.Thing.GetType());
}
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
public interface ISomething
{
public int RandomNumber { get; set; }
}
public class Something1 : ISomething
{
public int RandomNumber { get; set; }
public string RandomString { get; set; }
}
public class Something2 : ISomething
{
public int RandomNumber { get; set; }
public bool RandomBool { get; set; }
}
public class ConsoleAppSerializationBinder : ISerializationBinder
{
public Type BindToType(string? assemblyName, string typeName)
{
var resolvedTypeName = $"{typeName}, {assemblyName}";
return Type.GetType(resolvedTypeName, true);
}
public void BindToName(Type serializedType, out string? assemblyName, out string? typeName)
{
assemblyName = null;
typeName = serializedType.AssemblyQualifiedName;
}
}
}
}
Output:
ConsoleApp.Program+Something1
ConsoleApp.Program+Something2