I've been building a serialisation system that works by assigning a special ReferenceJConverter
or DefinitionJConverter
based on what attributes an object has been decorated with. These custom JsonConverter
s are assigned by logic in a custom ContractResolver
, instead of the more orthodox technique of determining what can be converted with the CanConvert
function. The custom resolver overrides the CreateProperty
function, like this:
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (member is FieldInfo field)
{
// Does this field implement IList? This Utility function gets all member interfaces and checks if any of them equal the argued interface.
if (field.FieldType.ImplementsInterface<IList>())
{
// Are the elements of this IList decorated with [Ref]?
if (field.FieldType.GetGenericArguments()[0].HasAttribute<RefAttribute>())
{
// Does this collection field have [Def]?
if (field.HasAttribute<DefAttribute>())
{
// This is a collection of definitions.
property.Converter = new DefCollectionJConverter();
}
else
{
// This is a collection of references.
property.Converter = new RefCollectionJConverter();
}
}
}
else
{
// If the field's type class has been decorated with Ref.
if (field.FieldType.HasAttribute<RefAttribute>())
{
if (member.HasAttribute<DefAttribute>())
{
// If this particular field also has Def, it's a definition.
property.Converter = new DefinitionJConverter();
}
else
{
// Else it's a reference.
property.Converter = new ReferenceJConverter();
}
}
}
}
return property;
}
As you can see, it's also designed to handle collections, but I'm currently not testing that.
These custom converters either serialise the object in a regular manner, if it's being defined, or else they serialise it as either a number or a legible string-name, if it's a reference.
What's annoying is that serialisation works fine. I get the Json output I want:
{
"wineofTheDayRef": "Merlot_WIN",
"wineOfTheDay": {
"tag": {
"literalTag": "Merlot_WIN"
},
},
}
It's deserialisation that breaks. Json.NET seems to loop through CreateProperty
and assign all the correct converters, yet these converters don't ever seem to be called. Instead I get:
Newtonsoft.Json.JsonSerializationException : Error converting value "Merlot_WIN" to type 'OrthrusStudios.Serialisation.PirateExample.Wine'. Path 'wineofTheDayRef', line 2, position 33.
----> System.ArgumentException : Could not cast or convert from System.String to OrthrusStudios.Serialisation.Examples.Wine.
... warning me that the reference string "Merlot_WIN" cannot be converted to the type "Wine"; I have a custom converter that can do exactly this, simply by looking up a deserialised object by its string-name, or else putting in a request for said object if its definition hasn't yet been deserialised. Why is my code even getting here?
My custom converters worked perfectly before I switched to a ContractResolver
approach. In theory, shouldn't the logic be the same?
The error comes from JsonSerializerInternalReader.EnsureType()
.