So, I've come to find a solution that seems to cover my use case.
I have managed to create a simple JsonSerializer
that copies values. Upon serialization, the goal is to go from the source object to a JsonValue
target. If the source value is either a JsonValue
, a JsonObject
or a JsonArray
instance, the serialization method returns a JsonValue
. Other types throws an ArgumentOutOfRangeException
.
Upon deserialization, the goal is to go from a JsonValue
and back to the requested type. If the requested type is a JsonValue
, the current JsonValue
are returned. If the requested type is a JsonObject
, either the current JsonValue.Object
are returned, or a new JsonObject
if JsonValue.Object
is null. Same goes for JsonArray
: JsonValue.Array
?? new JsonArray
. Other requested types returns null;
The serializer must be registered with the SerializerFactory.AddSerializer(mySerializer)
somewhere. For me it is typically in Global.asax.cs
and in a MSTest [AssemblyInitialize()]
method.
Here is my implementation at this point:
public class JsonValueSerializer : ISerializer
{
public bool ShouldMaintainReferences => false;
private Type[] HandledTypes = {
typeof(JsonValue),
typeof(JsonObject),
typeof(JsonArray)
};
public bool Handles(SerializationContextBase context)
{
return HandledTypes.Contains(context.InferredType);
}
public JsonValue Serialize(SerializationContext context)
{
if (context.Source is JsonValue jsonValue)
return jsonValue;
else if (context.Source is JsonObject jsonObject)
return new JsonValue(jsonObject);
else if (context.Source is JsonArray jsonArray)
return new JsonValue(jsonArray);
else
throw new ArgumentOutOfRangeException();
}
public object Deserialize(DeserializationContext context)
{
if (context.RequestedType == typeof(JsonValue))
{
return context.LocalValue;
}
else if (context.RequestedType == typeof(JsonObject))
{
return context.LocalValue.Object ?? new JsonObject();
}
else if (context.RequestedType == typeof(JsonArray))
{
return context.LocalValue.Array ?? new JsonArray();
}
return null!;
}
}
Weakness of this implementation:
Deserialization can be done with serializer.Deserialize<JsonObject>(myJsonValue)
. A problem with this implementation, is that when I have a complex class with an object property, and that property contains a JsonObject
(or a JsonArray
), it works to serialize the complex type into JsonValue
, but upon deserialization, the previous JsonObject
would be deserialized into a JsonValue
of JsonValueType.Object
. This is different from the original JsonObject
, since JsonValue
can contain a JsonObject
, but JsonObject
is a type in its own right.
Without additional mechanisms, it would be impossible to know if the JsonValue
should be kept or if it should be turned back into a JsonObject
or a JsonArray
. One could decide that on deserialization, all JsonValue.Object
would be constantly set to a JsonObject
type, but that would be wrong when it initially was a JsonValue
of JsonValueType.Object
(same for JsonArray
). Another approach could maybe be to let JsonObject
get a "$type"
parameter with the information, but that wouldn't work with JsonArray
. I have no fix for this behaviour.