2

EDIT: Clarify question:

I have overridden the JsonConverter for a base type (by applying [JsonConverter(typeof(TConverter))] to the superclass), but when deserializing the sub-type directly I want to use STANDARD serialization (i.e. no custom converter) for deserializing my derived object. How do I specify STANDARD serialization for use in the deserialize method, as if I had NOT overridden the JsonConverter?

I am using elastic search and can't call JsonConvert.DeserializeObject with my custom implementation of JsonConverter, and have to rely on the attribute for Elastic to use my converter.

However, using this converter as attribute seems to affect all sub classes as well, but I just want them to use the standard converter, so that I don't have to implement JsonConverter for each of many implementations.

This is my classes/logic as I would like it to look:

    [Route("test")]
    [HttpPost]
    public HttpResponseMessage Test([FromBody] JToken json)
    {
        var res = json.ToObject<Product>(); // I want an object of ProductImpl type here
        return Request.CreateResponse(res); 
    }

    [JsonConverter(typeof(JsonProductConverted))]
    public abstract class Product
    {
    }

    public class ProductImpl : Product
    {
    }

    public class JsonProductConverted : 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)
        {
            JObject json = JObject.Load(reader);
            //var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in 
            var type = typeof(ProductImpl);
            // var res = JsonConvert.DeserializeObject(json.ToString(), type, DEFAULT_JSONCONVERTER_HERE);
            var res = DeserializeToObjectWithStandardJsonConverter(json, type);
            return res;
        }

        public override bool CanConvert(Type objectType)
        {
            return false;
        }
    }

If I don't supply the default JsonConverter, or similar it will just use the JsonProductConverted converter, which creates an infinite loop.

dbc
  • 104,963
  • 20
  • 228
  • 340
Nixxon
  • 767
  • 1
  • 11
  • 24

1 Answers1

5

Since you have added [JsonConverter(typeof(JsonProductConverted))] directly to your Product type, you could add a dummy converter to ProductImpl that returns false from CanRead and CanWrite:

[JsonConverter(typeof(NoConverter))]
public class ProductImpl : Product
{
}

public class NoConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return false;
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

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

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

This overrides the base class's converter and then falls back on default serialization for both reading and writing

Sample .Net fiddle.

Another option would be to use serializer.Populate(). This avoids the call to the converter for the object itself:

public class JsonProductConverted : JsonTypeInferringConverterBase
{
    protected override Type InferType(Type objectType, JObject json)
    {
        //var type = GetTypeFromId((int) json["typeId"]); // Construct type from field in 
        return typeof(ProductImpl);
    }

    public override bool CanConvert(Type objectType)
    {
        return false;
    }
}

public abstract class JsonTypeInferringConverterBase : JsonConverter
{
    public override bool CanWrite { get { return false; } }

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

    protected abstract Type InferType(Type objectType, JObject json);

    protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json)
    {
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(actualType);
        return contract.DefaultCreator();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var json = JObject.Load(reader);

        var actualType = InferType(objectType, json);

        // Construct object (or reuse existingValue if compatible)
        if (existingValue == null || !actualType.IsAssignableFrom(existingValue.GetType()))
        {
            existingValue = CreateObject(actualType, serializer, json);
        }

        // Populate object.
        using (var subReader = json.CreateReader())
        {
            serializer.Populate(subReader, existingValue);
        }

        return existingValue;
    }
}

Note that the concrete objects must have parameterless constructors for this to work. If not, you can override protected virtual object CreateObject(Type actualType, JsonSerializer serializer, JObject json) and manually invoke a parameterized constructor by deserializing select properties inside the JObject json.

Sample fiddle #2.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This worked 100%. Allows for saving abstract classes in Elastic Search and then very easily deserializing them into the real objects, and makes this very simple even for abstract objects with LARGE amount of implementations. Thank you very much. – Nixxon Aug 08 '17 at 05:38
  • this is superb, but i needed t o add a private json constructor to my concrete classes to get them to deserialise correctly.. it would be nice to get a full explanation (or link) so what the contract aspect is about becuase my existing knowledge does not extend to that, and i would NEVER have worked this solution out – m1nkeh Feb 20 '18 at 13:55