使用Newtonsoft.Json创建自定义对象来反序列化一个动态类型



假设我有一个:

List<IInterface> list;

已与TypeNameHandling.Auto序列化,因此它具有"动态";类型信息。我可以把它反序列化成Newtonsoft。Json可以从$type中识别类型,Json可以使用正确的构造函数。到目前为止一切顺利。

现在假设我想用一个方法覆盖创建转换器:

CustomCreationConverter<IInterface>

覆盖对象的创建:

public override IInterface Create(Type objectType)

此时objectType将始终是IInterface而不是派生实现,因此我无法创建正确的对象。$type的元信息现在丢失了。

有一个优雅的方法来解决这个问题吗?

这将是一个不成功的尝试:

public class CustomConverter : CustomCreationConverter<Example.IInterface> {
public override Example.IInterface Create(Type objectType) {
return Example.MakeObject(objectType); // this won't work, objectType will always be IInterface
}
}
public class Example {
public interface IInterface { };
public class A : IInterface { public int content; };
public class B : IInterface { public float data; };
public static IInterface MakeObject(Type t) {
if (t == typeof(IInterface)) {
throw new Exception();
}
return t == typeof(A) ? new A() : new B();
}
public static void Serialize() {
var settings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.Auto
};
JsonSerializer serializer = JsonSerializer.Create(settings);
// serializer.Converters.Add(new CustomConverter()); // ?? can't have both, either CustomConverter or $type
List<IInterface> list = new() { MakeObject(typeof(A)), MakeObject(typeof(B)) };
using (StreamWriter sw = new("example.json")) {
serializer.Serialize(sw, list);
}
// Now read back example.json into a List<IInterface> using MakeObject
// Using CustomConverter won't work
using (JsonTextReader rd = new JsonTextReader(new StreamReader("example.json"))) {
List<IInterface> list2 = serializer.Deserialize<List<IInterface>>(rd);
}
}
}

一旦为某个类型提供了自定义转换器(如CustomCreationConverter<T>),该转换器将负责所有反序列化逻辑,包括通常由TypeNameHandling实现的类型选择逻辑。如果你只想注入一个自定义的工厂创建方法,而其余的反序列化逻辑保持不变,你可以创建自己的自定义契约解析器,并以JsonContract.DefaultCreator的形式注入工厂方法。

要实现这一点,首先定义以下工厂接口和契约解析器:

public interface IObjectFactory<out T>
{
bool CanCreate(Type type);
T Create(Type type);
}
public class ObjectFactoryContractResolver : DefaultContractResolver
{
readonly IObjectFactory<object> factory;
public ObjectFactoryContractResolver(IObjectFactory<object> factory) => this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (factory.CanCreate(objectType))
{
contract.DefaultCreator = () => factory.Create(objectType);
contract.DefaultCreatorNonPublic = false;
}
return contract;
}
}

接下来,重构IInterface类层次结构,以使用IObjectFactory作为对象创建工厂:

public class InterfaceFactory : IObjectFactory<IInterface>
{
public InterfaceFactory(string runtimeId) => this.RuntimeId = runtimeId; // Some value to inject into the constructor
string RuntimeId { get; }

public bool CanCreate(Type type) => !type.IsAbstract && typeof(IInterface).IsAssignableFrom(type);
public IInterface Create(Type type) => type switch
{
var t when t == typeof(A) => new A(RuntimeId),
var t when t == typeof(B) => new B(RuntimeId),
_ => throw new NotImplementedException(type.ToString()),
};
}
public interface IInterface
{
public string RuntimeId { get; }
}
public class A : IInterface
{
[JsonIgnore] public string RuntimeId { get; }
internal A(string id) => this.RuntimeId = id;
public int content { get; set; }
}
public class B : IInterface
{
[JsonIgnore] public string RuntimeId { get; }
internal B(string id) => this.RuntimeId = id;
public float data { get; set; }
}

(这里的RuntimeId是在对象创建时需要注入的值)

现在您将能够按照如下方式构建您的列表:

var valueToInject = "some value to inject";
var factory = new InterfaceFactory(valueToInject);
List<IInterface> list = new()  { factory.Create(typeof(A)), factory.Create(typeof(B)) };

序列化和反序列化如下:

var resolver = new ObjectFactoryContractResolver(factory)
{
// Set any necessary properties e.g.
NamingStrategy = new CamelCaseNamingStrategy(),
};
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
TypeNameHandling = TypeNameHandling.Auto,
};
var json = JsonConvert.SerializeObject(list, Formatting.Indented, settings);
var list2 = JsonConvert.DeserializeObject<List<IInterface>>(json, settings);

指出:

  • Newtosoft建议您缓存和重用您的契约解析器以获得最佳性能。

  • Newtonsoft还建议

    当应用程序从外部源反序列化JSON时,应谨慎使用

    TypeNameHandling。当使用非None的值进行反序列化时,应该使用自定义SerializationBinder验证传入类型。

    为什么,参见例如TypeNameHandling警告Newtonsoft Json或External Json易受Json攻击。. Net TypeNameHandling auto .

此处演示小提琴

最新更新