JSON 使用 CustomCreationConverter 反序列化以创建类型并注入数据
JSON deserialisation with a CustomCreationConverter to create the type and inject data
我有一个基于任务的应用程序需要将信息注入某些任务。任务可以被克隆或存储在一个保存文件中,在每种情况下 class 被序列化为 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 });
}
}
任务可以 'opt-in' 保存或不保存应用程序数据,因此可能如下所示:
public class NoDataTask : Task {
public NoDataTask() { }
}
public class DataTask : Task {
public DataTask(IApplicationData data) : base(data) { }
}
我在从 JSON 反序列化时实现了 CustomCreationConverter
以创建相关 class 的新实例(您可能已经注意到 Clone()
在 Task
基础 class 中实现如上所示。
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
class,其中包含我序列化/反序列化的 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
而我需要派生 class 才能创建一个合适的新实例。
添加 [JsonProperty]
属性提供了按原样使用转换器的能力,但我不知道我可以在没有它的情况下使用无参数构造函数应用它的方法,鉴于我的无参数构造函数这是无用的IApplicationData
的实现始终为 null。
.NET Fiddle 此处示例 - https://dotnetfiddle.net/WdyfDv
我已经能够通过编写自己的 JsonConverter(主要基于 Newtonsoft.Json.Converters
- GitHub link 中的 CustomCreationConverter
)来解决我的问题,如下所示:
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;
}
'magic' 发生在 ReadJson()
中,其中 ITask
的派生 class 从 json '$type' 中提取并使用反射创建.这确实需要将 TypeNameHandling 设置为 TypeNameHandling.Objects
,这在我的序列化程序设置中。
要使用它,我可以从 Project
class 中删除 JsonProperty
属性,并确保 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'。
我有一个基于任务的应用程序需要将信息注入某些任务。任务可以被克隆或存储在一个保存文件中,在每种情况下 class 被序列化为 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 });
}
}
任务可以 'opt-in' 保存或不保存应用程序数据,因此可能如下所示:
public class NoDataTask : Task {
public NoDataTask() { }
}
public class DataTask : Task {
public DataTask(IApplicationData data) : base(data) { }
}
我在从 JSON 反序列化时实现了 CustomCreationConverter
以创建相关 class 的新实例(您可能已经注意到 Clone()
在 Task
基础 class 中实现如上所示。
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
class,其中包含我序列化/反序列化的 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
而我需要派生 class 才能创建一个合适的新实例。
添加 [JsonProperty]
属性提供了按原样使用转换器的能力,但我不知道我可以在没有它的情况下使用无参数构造函数应用它的方法,鉴于我的无参数构造函数这是无用的IApplicationData
的实现始终为 null。
.NET Fiddle 此处示例 - https://dotnetfiddle.net/WdyfDv
我已经能够通过编写自己的 JsonConverter(主要基于 Newtonsoft.Json.Converters
- GitHub link 中的 CustomCreationConverter
)来解决我的问题,如下所示:
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;
}
'magic' 发生在 ReadJson()
中,其中 ITask
的派生 class 从 json '$type' 中提取并使用反射创建.这确实需要将 TypeNameHandling 设置为 TypeNameHandling.Objects
,这在我的序列化程序设置中。
要使用它,我可以从 Project
class 中删除 JsonProperty
属性,并确保 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'。