-1

When serialise, I write ClassName to object into _CurrentClassName property. And when read json with JSON.Net library I need to change object to value from this property.

{
 "Field1": 0,
 "Field2": "34",
 "_CurrentClassName": "MyCustomClass"
}


class CustomJsonConverter : JsonConverter
{
...
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsClass;
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = existingValue;
        if (reader.TokenType == JsonToken.Null)
        {
            return null;
        }
        else if (reader.TokenType == JsonToken.StartObject)
        {
            JObject jObject = JObject.Load(reader);
            JToken jToken;
            if (jObject.TryGetValue("_CurrentClassName", out jToken))
            {
                var t = jToken.Value<string>();
                Type tt = Type.GetType(objectType.Namespace + "." + t);
                value = Activator.CreateInstance(tt);
                return value;
            }
        }
        return serializer.Deserialize(reader);            
    }

...
}
ndbnndbn
  • 3
  • 1
  • 4
  • You could do something similar to what is done in the answer to [Json.Net Serialization of Type with Polymorphic Child Object](https://stackoverflow.com/questions/29528648/json-net-serialization-of-type-with-polymorphic-child-object/29531372#29531372). – dbc Sep 01 '16 at 08:10
  • Thanks. Similar, but I can't use "hard" type, my CanConvert looks like: public override bool CanConvert(Type objectType) { return objectType.IsClass; } – ndbnndbn Sep 01 '16 at 09:03
  • Are you married to that property name and syntax or could you just use Json.Net built in handling of this? – Lasse V. Karlsen Sep 01 '16 at 09:06
  • If you pass in a `new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }` to the serialization or deserialization methods you will add a property like `$"type": "Namespace.Type, Assembly"`. – Lasse V. Karlsen Sep 01 '16 at 09:08

2 Answers2

1

Once the object type has been inferred and the object has been instantiated, you can use JsonSerializer.Populate(jObject.CreateReader()) to populate it.

For instance:

public abstract class CustomJsonConverterBase : JsonConverter
{
    protected abstract Type InferType(JToken token, Type objectType, object existingValue);

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var token = JToken.Load(reader);
        var actualType = InferType(token, objectType, existingValue);
        if (existingValue == null || existingValue.GetType() != actualType)
        {
            var contract = serializer.ContractResolver.ResolveContract(actualType);
            existingValue = contract.DefaultCreator();
        }
        using (var subReader = token.CreateReader())
        {
            // Using "populate" avoids infinite recursion.
            serializer.Populate(subReader, existingValue);
        }
        return existingValue;
    }

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

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

public class CustomJsonConverter : CustomJsonConverterBase
{
    public const string ClassPropertyName = @"_CurrentClassName";

    protected override Type InferType(JToken token, Type objectType, object existingValue)
    {
        if (token is JObject)
        {
            var typeName = (string)token[ClassPropertyName];
            if (typeName != null)
            {
                var actualType = Type.GetType(objectType.Namespace + "." + typeName);
                if (actualType != null)
                    return actualType;
            }
        }
        return objectType;
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}

CustomCreationConverter<T> also uses serializer.Populate() to fill in the just-allocated object so this is a standard way to solve this problem.

Note that you are partially replicating Json.NET's built-in TypeNameHandling functionality.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thank you, I will try understand and use this. – ndbnndbn Sep 01 '16 at 09:22
  • Working with filter system types. Thank you! public override bool CanConvert(Type objectType) { // throw new NotImplementedException(); return objectType.IsClass && objectType.Namespace != null && !objectType.Namespace.StartsWith("System") ; } – ndbnndbn Sep 01 '16 at 10:08
1

If you're not married to the _CurrentClassName property name or its value syntax you could use Json.Net's built in handling of types.

When serializing or deserializing you can pass in a JsonSerializerSettings object controlling the serialization or deserialization.

On this object you can set a TypeNameHandling property that controls how Json.Net serializes and deserializes the exact type being processed.

Here is a LINQPad example:

void Main()
{
    var t = new Test { Key = 42, Value = "Meaning of life" };

    var json = JsonConvert.SerializeObject(
        t, Newtonsoft.Json.Formatting.Indented,
        new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
    Console.WriteLine(json);

    var obj  =JsonConvert.DeserializeObject(json,
        new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All });
    Console.WriteLine(obj.GetType().FullName);
}

public class Test
{
    public int Key { get; set; }
    public string Value { get; set; }
}

Output:

{
  "$type": "UserQuery+Test, LINQPadQuery",
  "Key": 42,
  "Value": "Meaning of life"
}
UserQuery+Test

Here you can see that the type of the object being returned from deserialization is the Test class.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825