I have a hierarchical class ConversationModel
that needs to be serialized/deserialized to/from JSON
.
Note that the ConversationNode
class does NOT have a parameterless constructor. The only non-default
constructor is also marked as internal
. All class properties are get-only
. This is deliberate to keep the class immutable, stateless, and idiot-proof.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
public class ConversationModel
{
public ConversationNode Node { get; }
public ConversationModel ()
=> this.Node = new ConversationNode(this, null);
public static JsonSerializerSettings JsonSerializerSettings
=> new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.All,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Full,
Converters = new [] { new ConversationModelJsonConverter(), },
};
public string Serialize ()
=> JsonConvert.SerializeObject(this, this.GetType(), ConversationModel.JsonSerializerSettings);
public static ConversationModel FromJson (string json)
=> JsonConvert.DeserializeObject<ConversationModel>(json, ConversationModel.JsonSerializerSettings);
}
public class ConversationNode
{
public ConversationModel Model { get; }
public ConversationNode Parent { get; }
public ConversationNodeList Nodes { get; }
internal ConversationNode (ConversationModel model, ConversationNode parent)
{
this.Model = model ?? throw (new ArgumentNullException(nameof(model)));
this.Parent = parent ?? throw (new ArgumentNullException(nameof(parent)));
this.Nodes = new ConversationNodeList (model, parent);
}
}
public class ConversationNodeList:
List<ConversationNode>
{
public ConversationModel Model { get; }
public ConversationNode Parent { get; }
public ConversationNodeList (ConversationModel model, ConversationNode parent)
{
this.Model = model;
this.Parent = parent;
}
}
The JsonSerializer
, of course, expects a public, default, parameterless constructor
. How can I get it to deserialize the model while keeping parent objects referenced properly as they are currently settable only through the constructor?
public sealed class ConversationModelJsonConverter:
JsonConverter<ConversationModel>
{
public override void WriteJson (JsonWriter writer, [AllowNull] ConversationModel value, JsonSerializer serializer)
{
// ???
}
public override ConversationModel ReadJson (JsonReader reader, Type objectType, [AllowNull] ConversationModel existingValue, bool hasExistingValue, JsonSerializer serializer)
{
// ???
return (null);
}
}
The question is:
- Is it possible to write a custom
JsonConverter
to achieve this? If so, how? - Is there an easier way than having to implement a custom
JsonConverter
?