3

I would like to know how to implement a custom serializer/deserializer for the following class:

[JsonConverter(typeof(UnderlyingTypeConverter))]
public class ZoneProgramInput
{
    public string Name { get; set; }
    public Subject<object> InputSubject { get; }
    private IDisposable InputDisposable { get; set; }
    public Type Type { get; set; }
    public object Value { get; set; }
}

The requirement is that I would like to serialize/deserialize the property Value (of type object) with the type stored in the property Type and not with the type object. So if I have the following code:

var zpi = new ZoneProgramInput() { Type = typeof(System.Drawing.Color), Value = System.Drawing.Color.Red };
var serializedZpi = JsonConvert.SerializeObject(zpi);
var deserializedZpi = JsonConvert.DeserializeObject<ZoneProgramInput>(serializedZpi);

The variable deserializedZpi contains a deserialized instance of zpi, and the deserialized.Value should be of type System.Drawing.Color. Without a custom converter, it deserializes as a string rather than a System.Drawing.Color. As a note, I just chose System.Drawing.Color arbitrarily. This type can be anything.

I have a converter class called UnderlyingTypeConverter (which is set as the converter for ZoneProgramInput in the above code):

public class UnderlyingTypeConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {

    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {

    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ZoneProgramInput);
    }
}

What should I fill into the ReadJson/WriteJson method to make sure that the Value property serializes and deserializes with the type stored in the Type property? I've tried looking around Google and StackOverflow for examples for ReadJson/WriteJson, but I haven't found anything that can help me find the type in this manner. Thank you for your help in advance.

PS: I know I could possibly use generics, but I already tried that. Making ZoneProgramInput take a generic type parameter and making Value of that type still serializes/deserializes Value as a string. I also tried using C# dynamic keyword and it's the same result. Also TypeNameHandling apparently doesn't work with things that are defined as object types. It just serializes them as strings instead of objects.

15
  • What is your problem as a summary? serialization/deserialization of Color ? Commented Dec 8, 2014 at 20:34
  • Not quite. The type of the Value property can be anything, but the type is stored in the property Type. I would like to deserialize the Value property with the type stored in the Type property. Does that make sense? Commented Dec 8, 2014 at 20:35
  • 1
    Maybe you don't need that Type property. Have you tried new JsonSerializerSettings(){ TypeNameHandling = Newtonsoft.Json.TypeNameHandling.All} Commented Dec 8, 2014 at 20:38
  • When you say making Value of that type still serialized/deserializes Value as a string, what exactly do you mean? In other words, how is this solution going to perform differently than that? Serializing a System.Drawing.Color instance is going to work the same way whether you store the type in another property or make Value the actual type Commented Dec 8, 2014 at 20:41
  • @L.B I just added a final comment about that in the question. When a property is defined as an object, TypeNameHandling = All does NOT save the type, even if the underlying type is not an object type. So in this case, it saves the Value property as: "Value": "Red". It should be saving it as "Value" : { "Name": "Red", "A":"255", "R":"255", "G":"0", ... }. Commented Dec 8, 2014 at 20:41

2 Answers 2

2

It's kind of a shame that you need to do this, but I don't really see a way around it. Here's a converter that should work:

public class UnderlyingTypeConverter : JsonConverter
{
    public override void WriteJson(
        JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(
        JsonReader reader,
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        var result = new ZoneProgramInput();

        // Deserialize into a temporary JObject
        JObject obj = serializer.Deserialize<JObject>(reader);

        // Populate the ZoneProgramInput object with the contents
        serializer.Populate(obj.CreateReader(), result);

        // Overwrite the "Value" property with the correct value based on the 
        // "Type" property.
        result.Value = 
            obj.GetValue("value", StringComparison.OrdinalIgnoreCase)
               .ToObject(result.Type, serializer);

        return result;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(ZoneProgramInput);
    }

    public override bool CanWrite
    {
        get { return false; }
    }
}

Example: https://dotnetfiddle.net/Zv57R8

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you so much. This works perfectly. JObject seems to be the most common way of doing things in ReadJson. I had never worked with it before so I was having troubles figuring out how to "overwrite" the property Value using serializer and JObject. If you'd like to know why I needed this, it's because originally I wanted to have a List<ZoneProgramInput<T>> with T being able to take multiple types (such that I can have a ZoneProgramInput<string> and ZoneProgramInput<int> in the list). But since C# doesn't allow multiple types in a generic parameter, I had to use runtime type-checking.
...And because the type-checking is being done during runtime, the serialization/deserialization also needed to be done at runtime using what you provided. Thanks again for the solution.
Oh and thanks for enlightening me about dotnetfiddle. It's going to be very useful to me in the future.
@Anshul: No problem, generics would normally be a good solution I think, but since at some level you have to know the generic type parameter to deserialize, it's not going to play nicely with JSON.NET. I'll keep on the lookout for a better option though.
0

I'm using this way for serializing System.Object that can be a different type (it can be modified to support colections of objects).

NOTE if all objects that could be in that System.Object have some base class/interface- use it instead of object and enable this option: https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm

Here is for System.Object case:

public class SerializeMe
{
    [JsonConverter(typeof(ObjectJsonConverter))]
    public JsoSerializableObjectContainer VisualData { get; set; } = new JsoSerializableObjectContainer();
}

public class JsoSerializableObjectContainer
{
    public string ObjectTypeAssemblyName { get; set; }
    public string ObjectTypeName { get; set; }
    public string ObjectTypeData { get; set; }
    [JsonIgnore]
    public object Data { get; set; }
}

public class ObjectJsonConverter : JsonConverter
{
    private static Assembly[] _assemblies;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dataObj = (JsoSerializableObjectContainer) value;

        if (dataObj?.Data == null)
            return;

        var objType = dataObj.Data.GetType();
        dataObj.ObjectTypeName = objType.FullName;
        dataObj.ObjectTypeAssemblyName = objType.Assembly.FullName;
        dataObj.ObjectTypeData = JsonConvert.SerializeObject(dataObj.Data);

        var ser = JsonSerializer.Create();
        ser.Serialize(writer, dataObj);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jsonSerializer = JsonSerializer.Create();
        var container = jsonSerializer.Deserialize<JsoSerializableObjectContainer>(reader);

        if (container != null)
        {
            if (_assemblies == null)
            {
                _assemblies = AppDomain.CurrentDomain.GetAssemblies();
            }

            var assembly = _assemblies.Single(t => t.FullName == container.ObjectTypeAssemblyName);
            var deserializationType = assembly.GetType(container.ObjectTypeName);

            if (deserializationType == null)
            {
                throw new JsonException(
                    $"Can't find type for object deserialization {container.ObjectTypeName}"); //Probably type was deleted from code
            }

            var myObject = JsonConvert.DeserializeObject(container.ObjectTypeData, deserializationType);
            container.Data = myObject;
            return container;
        }

        return null;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(JsoSerializableObjectContainer);
    }
}

Test code:

        var ser = new SerializeMe();
        ser.VisualData.Data = (int)1;
        var serializationResult = JsonConvert.SerializeObject(ser);
        var deserializedObject = JsonConvert.DeserializeObject<SerializeMe>(serializationResult);


        ser.VisualData.Data = new System.Drawing.Rectangle(0, 1, 2, 3);
        serializationResult = JsonConvert.SerializeObject(ser);
        deserializedObject = JsonConvert.DeserializeObject<SerializeMe>(serializationResult);

Note: Instead of ObjectTypeAssemblyName and ObjectTypeName you can use only second one from objType.AssemblyQualifiedName, but it will probably fail because it will also check the assembly version while searching a type. And after updating packages or dll version changes it will not able to find the type.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.