1

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 JsonConverters 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().

Thomas Slade
  • 129
  • 9

0 Answers0