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