using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Runtime.Serialization;
public interface IApplicationData { }
public class ApplicationData : IApplicationData {
public string ExampleData { get; set; }
public override string ToString() {
IApplicationData Data { get; }
public abstract class Task : ITask, ICloneable {
protected Task(IApplicationData data = null) {
public IApplicationData Data { get; }
var settings = new JsonSerializerSettings() {
TypeNameHandling = TypeNameHandling.All
settings.Converters.Add(new TaskCreator(Data));
var json = JsonConvert.SerializeObject(this, 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 class DataTask : Task {
public DataTask(IApplicationData data) : base(data) { }
public class TaskCreator : JsonConverter<ITask> {
private readonly IApplicationData _data;
public TaskCreator(IApplicationData data) {
public override ITask ReadJson(JsonReader reader, Type objectType, ITask existingValue, bool hasExistingValue, JsonSerializer serializer) {
if (reader.TokenType == JsonToken.Null) {
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);
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);
public override void WriteJson(JsonWriter writer, ITask value, JsonSerializer serializer) {
throw new NotSupportedException($"{ nameof(TaskCreator) } should only be used while deserializing.");
public override bool CanWrite => false;
public List<ITask> Tasks { get; set; }
public static void Main()
var data = new ApplicationData { ExampleData = "Hello World" };
var dataTask = new DataTask(data);
var dataTaskCloneData = ((DataTask)dataTask.Clone()).Data;
Console.WriteLine($"{nameof(dataTaskCloneData)}: {dataTaskCloneData}");
var project = new Project {
Tasks = new List<ITask> {
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;
Console.WriteLine($"{nameof(projectCopyTask2Data)}: {projectCopyTask2Data}");