c# - 使用 CustomCreationConverter 进行 JSON 反序列化以创建类型并注入数据
问题描述
我有一个基于任务的应用程序,需要将信息注入某些任务。这些任务可以被克隆或存储在一个保存文件中,在每种情况下,该类都被序列化为 JSON。传递给任务的应用程序信息不会被存储,因为它只保留应用程序会话。
public interface IApplicationData { }
public class ApplicationData : IApplicationData { }
public interface ITask {
IApplicationData Data { get; }
}
[DataContract]
public abstract class Task : ITask, ICloneable {
protected Task(IApplicationData data = null) {
Data = data;
}
public IApplicationData Data { get; }
public object Clone() {
var settings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
};
settings.Converters.Add(new TaskCreator(Data));
var json = JsonConvert.SerializeObject(this, settings);
// Reflection equivalent of JsonConvert.DeserializeObject<T>(json, settings);
var expectedParameters = new Type[] { typeof(string), typeof(JsonSerializerSettings) };
var method = typeof(JsonConvert).GetMethods().Where(mi => mi.IsGenericMethod && mi.IsStatic && mi.IsPublic && mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(expectedParameters)).Single();
return method.MakeGenericMethod(this.GetType()).Invoke(null, new object[] { json, settings });
}
}
任务可以“选择加入”或不保留应用程序数据,因此可能如下所示:
public class NoDataTask : Task {
public NoDataTask() { }
}
public class DataTask : Task {
public DataTask(IApplicationData data) : base(data) { }
}
在从 JSON 反序列化时,我实现了一个来创建相关类的新实例(您可能已经注意到在上面显示的基类的实现中CustomCreationConverter
正在使用它。Clone()
Task
public class TaskCreator : CustomCreationConverter<Task> {
//public TaskCreator() { } // uncomment to try using converter with JsonProperty attribute in Project
private readonly IApplicationData _data;
public TaskCreator(IApplicationData data) {
_data = data;
}
public override Task Create(Type objectType) {
var hasDataConstructor = objectType.GetConstructor(new Type[] { typeof(IApplicationData) }) != null;
return hasDataConstructor ? (Task)Activator.CreateInstance(objectType, _data) : (Task)Activator.CreateInstance(objectType);
}
}
这完全按照Clone()
方法的要求工作,objectType
接收到的是 DerivedClass (DataTask
在下面的示例中)
var data = new ApplicationData();
var dataTask = new DataTask(data);
var dataTaskCloneData = ((DataTask)dataTask.Clone()).Data; // still has data intact - excellent
但是,我不确定如何在存储任务的情况下进行这项工作。我目前有一个Project
包含List<ITask>
我序列化/反序列化的类。这对于每个任务中的数据都非常有效,但是我无法将其注入ApplicationData
到反序列化的任务实例中。
[DataContract]
public class Project {
[DataMember]
//[JsonProperty(ItemConverterType = typeof(TaskCreator))] // uncomment to force use of converter
public List<ITask> Tasks { get; set; }
}
var project = new Project {
Tasks = new List<ITask> {
new NoDataTask(),
new DataTask(data)
}
};
var serialiserSettings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All
};
serialiserSettings.Converters.Add(new TaskCreator(data));
var json = JsonConvert.SerializeObject(project, serialiserSettings);
var projectCopy = JsonConvert.DeserializeObject<Project>(json, serialiserSettings);
var projectCopyTask2Data = projectCopy.Tasks[1].Data; // data is null - bad
我发现由于包含List<ITask>
转换器的项目没有使用。我可以添加一个转换器,CustomCreationConverter<ITask>
但无论哪种方式,objectType
传递给转换器的总是类型ITask
,而我需要派生类才能创建适当的新实例。
添加[JsonProperty]
属性为转换器提供了按原样使用的能力,但我不知道有一种方法可以在没有它的情况下使用无参数构造函数应用它,考虑到我的实现,它是无用的,因为它IApplicationData
总是为空。
.NET Fiddle 示例在这里 - https://dotnetfiddle.net/WdyfDv
解决方案
我已经能够通过编写自己的 JsonConverter (主要基于CustomCreationConverter
in Newtonsoft.Json.Converters
- GitHub 链接)来解决我的问题,如下所示:
public class TaskCreator : JsonConverter<ITask> {
private readonly IApplicationData _data;
public TaskCreator(IApplicationData data) {
_data = data;
}
public override ITask ReadJson(JsonReader reader, Type objectType, [AllowNull] ITask existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.Null) {
return null;
}
// Determine and create the task by reading the type in the JSON
var jObj = JObject.Load(reader);
var jsonType = jObj["$type"]?.ToString();
if (string.IsNullOrWhiteSpace(jsonType)) throw new JsonSerializationException("Cannot determine type of task to create.");
var type = Type.GetType(jsonType);
if (type == null) throw new JsonSerializationException($"Could not find the task type {jsonType}");
var value = Create(type);
if (value == null) throw new JsonSerializationException("No object created.");
reader = jObj.CreateReader();
serializer.Populate(reader, value);
return value;
}
/// <summary>
/// Creates an object which will then be populated by the serializer.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>The created object.</returns>
public ITask Create(Type objectType) {
var hasDataConstructor = objectType.GetConstructor(new Type[] { typeof(IApplicationData) }) != null;
return hasDataConstructor ? (ITask)Activator.CreateInstance(objectType, _data) : (ITask)Activator.CreateInstance(objectType);
}
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="JsonWriter"/> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, [AllowNull] ITask value, JsonSerializer serializer) {
throw new NotSupportedException($"{ nameof(TaskCreator) } should only be used while deserializing.");
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value>
/// <c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.
/// </value>
public override bool CanWrite => false;
}
“魔术”发生在从 json '$type' 中提取ReadJson()
派生类并使用反射创建的地方。ITask
这确实需要将 TypeNameHandling 设置为TypeNameHandling.Objects
,这在我的序列化程序设置中。
要使用它,我可以JsonProperty
从类中删除属性Project
并确保JsonSerializerSettings
包含转换器:
var data = new ApplicationData("Hello World");
var project = new Project {
Tasks = new List<ITask> {
new NoDataTask(),
new DataTask(data)
}
};
var serialiserSettings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.All
};
serialiserSettings.Converters.Add(new TaskCreator(data));
var json = JsonConvert.SerializeObject(project, serialiserSettings);
var projectCopy = JsonConvert.DeserializeObject<Project>(json, serialiserSettings);
这里有一个完整的示例(.NET fiddle) - https://dotnetfiddle.net/Ecrz2S
如果有人建议,我仍然非常愿意接受替代方法,因为这个解决方案对我来说仍然有点“hacky”。
推荐阅读
- swig - 如何为 std::set 创建一个 swig 自定义类型映射以由本机 python 集包装?
- javascript - 如何在表单上的 5 个文本框中留下一个错误标签消息时制作一个文本框?
- c++ - 错误:请求“list1”中的成员“size”,它是非类类型“float*”
- c++ - 查找函数的所有局部最大值
- flask-sqlalchemy - Python 3 Flask Rest Api: "request.get_json()" 给出 TypeError: 'NoneType' object is not subscriptable
- angular - 安全地重命名 Angular 项目?
- botframework - 我是否可以自动将测试的话语添加到我的 LUIS 模型中而无需审查?
- php - 为什么在 MAMP 上更改我的 php 版本可以修复我的 Apache 阻止端口 80 错误?
- java - 在java中获取昨天的开始日期和时间以及今天的开始日期和时间
- jenkins - 从 Jenkins 作业执行时,Opentest_cli 无法识别 STAF 进程