Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using default JsonSerializer when decorating class with JsonConverterAttribute

I have a class decorated with JsonConverter attribute to use my custom converter. The aim of the custom converter is to serialize CustomProperty using some custom logic. Instead of writing code to serialize all the primitive properties, I decided to use JObject.FromObject to automatically serialize the properties and would later do something like o.Remove("CustomProperty") and then add the custom serialized member to o.
But since the class is decorated with JsonConverter attribute, JObject.FromObject again calls my ClassAJsonConverter which leads to infinte recursive call. At the point of calling JObject.FromObject is it possible to specifically tell the json to use it's default converter instead of my custom one.

[Newtonsoft.Json.JsonConverter(typeof(ClassAJsonConverter))]
public class ClassA
{
   public string A {get; set;}
   public int B {get; set;}
    .
    //20 some properties
    .
   public CustomProp CustomProperty {get; set;}
}

public class ClassAJsonConverter : JsonConverter
{
   public override bool CanConvert(Type objectType)
   {
       return objectType == typeof(ClassA);
   }

   public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
   {
      .
      var o = JObject.FromObject(value);     //Here infinite recurrence occur
      .
   }
   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
   {
      .
      .
      .
   }
}

Note: I came across Recursively call JsonSerializer in a JsonConverter but was not able to implement it. Also I would not like to add dependency to AutoMapper just for this one use. Since the question was more than a year old, has anyone found a better way to do it?

like image 886
Ankit Sinha Avatar asked Jun 11 '14 17:06

Ankit Sinha


1 Answers

If you've decorated your class with a [JsonConverter] attribute, then all instances of the serializer will know about it. Therefore if you use JObject.FromObject within the converter you will get into an infinite recursive loop, even if you try to pass it a new serializer instance.

There are two ways around the problem:

  1. Manually handle the serialization of each field in your class instead of calling JObject.FromObject, or
  2. Remove the [JsonConverter] attribute from the class declaration, and instead pass an instance of the converter to the serializer. This approach relies on the correct implementation of CanConvert (as shown in your question) so that the converter is applied only to the intended class.

For example:

string json = JsonConvert.SerializeObject(classA, new ClassAJsonConverter());

If the serialization of your CustomProperty does not depend on the other members of ClassA, then another alternative is to create a custom converter specifically for the CustomProp class instead of ClassA. Then your converter doesn't have to do tricks to worry about other properties; it just has to worry about CustomProp itself.


Another possible solution

I found a solution that might work for you, but it feels a little hacky. The idea is to create a new JsonSerializer instance inside the JsonConverter, and then use a special ContractResolver on that serializer which disavows knowledge of the current converter when asked to resolve it. This will allow you to use JObject.FromObject inside the converter without getting into a recursive loop, even while you have the [JsonConverter] attribute applied to your class. The downside to this approach is that any other settings that you may have applied to the outer serializer will not be automatically carried to the inner serializer, so you will need to manually copy those settings if you need to keep them.

Here is the code for the resolver:

class JsonConverterExclusionResolver<T> : DefaultContractResolver
{
    protected override JsonConverter ResolveContractConverter(Type objectType)
    {
        JsonConverter conv = base.ResolveContractConverter(objectType);
        if (conv != null && conv.GetType() == typeof(T))
        {
            // if something asks for the converter we're excluding,
            // we never heard of it
            return null;
        }
        return conv;
    }
}

With this resolver in place, you would need to modify your ClassAJsonConverter to use it like this:

public class ClassAJsonConverter : JsonConverter
{
    private IContractResolver exclusionResolver = 
        new JsonConverterExclusionResolver<ClassAJsonConverter>();

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

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JsonSerializer innerSerializer = new JsonSerializer();
        innerSerializer.ContractResolver = exclusionResolver;
        // (copy other settings from the outer serializer if needed)

        var o = JObject.FromObject(value, innerSerializer);

        // ...do your custom stuff here...

        o.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
like image 85
Brian Rogers Avatar answered Nov 16 '22 00:11

Brian Rogers