Json.NET - 反序列化接口 属性 抛出错误 "type is an interface or abstract class and cannot be instantiated"
Json.NET - Deserialising interface property throws error "type is an interface or abstract class and cannot be instantiated"
我有一个 class,还有一个 属性 是一个接口。
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
尝试使用 Json.NET 反序列化 Foo
class 给我一条错误消息,如:
Could not create an instance of type ISomething. Type is an interface or abstract class and cannot be instantiated.
从类似的 question,我可以看到使用 TypeNameHandling = TypeNameHandling.Objects
将解决错误,方法是允许 Json.NET 在序列化时包含 .NET 类型名称,从而知道哪个具体之后需要反序列化对象的类型。
但是,当使用 TypeNameHandling
值而不是 TypeNameHandling.None
从外部源反序列化 JSON 时,似乎有 caution advised。
TypeNameHandling.Objects
显然属于这一类。
有没有办法在不引入任何安全风险的情况下反序列化具体对象类型(实现接口)?
考虑到您没有确定要转换为哪种具体类型的字段,您唯一的解决方案是如您所述,TypeNameHandling = TypeNameHandling.Objects
。
但是,您可以缓解安全漏洞。
使用自定义 ISerializationBinder
验证传入的类型名称,因为它们在序列化期间将 .NET 类型解析为类型名称,并在反序列化期间将类型名称解析为 .NET 类型:
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;
}
}
将 JsonSerializerSettings
object 上的 SerializationBinder
属性 设置为活页夹实例,并使用与最初用于序列化数据的相同类型的活页夹进行反序列化。
var jsonSerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = new ConsoleAppSerializationBinder()
};
这里有一些演示工作代码来演示用法:
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;
}
}
}
}
输出:
ConsoleApp.Program+Something1
ConsoleApp.Program+Something2
我有一个 class,还有一个 属性 是一个接口。
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
尝试使用 Json.NET 反序列化 Foo
class 给我一条错误消息,如:
Could not create an instance of type ISomething. Type is an interface or abstract class and cannot be instantiated.
从类似的 question,我可以看到使用 TypeNameHandling = TypeNameHandling.Objects
将解决错误,方法是允许 Json.NET 在序列化时包含 .NET 类型名称,从而知道哪个具体之后需要反序列化对象的类型。
但是,当使用 TypeNameHandling
值而不是 TypeNameHandling.None
从外部源反序列化 JSON 时,似乎有 caution advised。
TypeNameHandling.Objects
显然属于这一类。
有没有办法在不引入任何安全风险的情况下反序列化具体对象类型(实现接口)?
考虑到您没有确定要转换为哪种具体类型的字段,您唯一的解决方案是如您所述,TypeNameHandling = TypeNameHandling.Objects
。
但是,您可以缓解安全漏洞。
使用自定义 ISerializationBinder
验证传入的类型名称,因为它们在序列化期间将 .NET 类型解析为类型名称,并在反序列化期间将类型名称解析为 .NET 类型:
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;
}
}
将 JsonSerializerSettings
object 上的 SerializationBinder
属性 设置为活页夹实例,并使用与最初用于序列化数据的相同类型的活页夹进行反序列化。
var jsonSerializerSettings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects,
SerializationBinder = new ConsoleAppSerializationBinder()
};
这里有一些演示工作代码来演示用法:
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;
}
}
}
}
输出:
ConsoleApp.Program+Something1
ConsoleApp.Program+Something2