일부 작업에 정보를 주입해야하는 작업 기반 응용 프로그램이 있습니다. 작업은 복제하거나 저장 파일에 저장할 수 있으며, 각 경우 클래스는 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) { }
}
나는이 구현 CustomCreationConverter
JSON에서 deserialising 때 해당 클래스의 새로운 인스턴스를 생성 (이은에 활용되고 눈치 챘을 수도 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 바이올린 예 - 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;
}
'마법' ReadJson()
은 파생 클래스 ITask
가 json '$ type'에서 추출되고 리플렉션을 사용하여 생성 되는 곳에서 발생합니다 . 이렇게하려면 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 바이올린) 여기 - https://dotnetfiddle.net/Ecrz2S
이 솔루션은 여전히 나에게 약간 '해키'느낌이 들기 때문에 누군가 제안 할 것이 있다면 대체 접근 방식에 대해 여전히 매우 열려 있습니다.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다