0

I am currently working on a Custom JSON converter to be used in a WebAPI project. The requirement is - I have a DTO object having some properties. The APIs can be consumed by multiple clients. Depending upon a client few of my DTO Entities might have some additional data apart from the properties already present in the DTO Model. I need to create a custom JSON converter to Serialize and Deserialize this data.

//DTO
class AbcDTO
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
    public List<AdditionalProperty> AdditionalData { get; set; }
}

//AdditionalProperty class
class AdditionalProperty
{
    public string Name { get; set; }
    public object Value { get; set; }
}

//Request JSON Body
{
    "Prop1": "Val1",
    "Prop2": "Val2",
    "AdditionalProp3": "Val3",
    "AdditionalProp4": "Val4"
}

//After Deserialization the object should be as below
AbcDTO dto = {
    Prop1 = "Val1",
    Prop2 = "Val2",
    AdditionalData = [
    { Name = "AdditionalProp3", Value = "Val3" },
    { Name = "AdditionalProp4", Value = "Val4" }]
}

//After Serialization of the above dto object the JSON should convert back to the Request JSON Body format

We don't want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we would need to keep the property as Dictionary<string, JToken> -- but we don't want to pass JToken to below layers.

Created a custom JSON converter -

class CustomJsonConverter : JsonConverter
{
    bool _canWrite = true;
    bool _canRead = true;

    public override bool CanConvert(Type objectType)
    {
        return typeof(IEntity).IsAssignableFrom(objectType);
    }

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

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);

        PropertyInfo[] availablePropertyNames = objectType.GetProperties();
        List<AdditionalProperties> additionalData = new List<AdditionalProperties>();
        IEntity obj;

            _canRead = false;
            obj = (IEntity)jObject.ToObject(objectType);
            _canRead = true;

        IEnumerable<JProperty> properties = jObject.Properties();
        foreach (JProperty prop in properties)
        {
            if (availablePropertyNames.Count(x => x.Name.Equals(prop.Name)) == 0)
            {
                AdditionalProperties addProp = new AdditionalProperties
                {
                    Name = prop.Name,
                    Value = prop.Value.ToObject<object>(),
                };
                additionalData.Add(addProp);
            }
        }

        obj.AdditionalData = additionalData;

        return obj;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        IEntity obj = (IEntity)value;
        List<AdditionalProperties> additionalData = obj.AdditionalData;
        JObject jObj;

        _canWrite = false;
        jObj = (JObject)JToken.FromObject(obj);
        _canWrite = true;

        jObj.Remove("AdditionalData");
        foreach (AdditionalProperties data in additionalData)
        {
            jObj.Add(data.Name, JToken.FromObject(data.Value));
        }
        jObj.WriteTo(writer);
    }
}

WebAPI ContractResolver creates 1 JSON converter per Entity. Now the issue is _canRead and _canWrite are not thread-safe. Need to use them to use the base implementation provided by Newtonsoft. If we don't use them, the ToObject and FromObject method again calls the custom converter methods internally resulting in infinite recursion. Using them with logs, reduces performance. Is there any way we can create a custom converter using the base implementation of Newtonsoft.JSON serialization/deserialization without using canRead and canWrite flags?

I can also have reference type child properties - say Person contains Address. I want to capture additional data for both Parent and Child entities. The additional data will not contain data of reference type.

dbc
  • 104,963
  • 20
  • 228
  • 340
Mayank
  • 1
  • 2
  • 1
    You could use a thread static to disable the converter, as shown in [JSON.Net throws StackOverflowException when using JsonConvert](https://stackoverflow.com/a/29720068/3744182) or [Generic method of modifying JSON before being returned to client](https://stackoverflow.com/a/35533682/3744182). – dbc Sep 19 '17 at 07:03
  • Or, you could take a look at the implementation of `TypedExtensionDataConverter` from [How to deserialize a child object with dynamic (numeric) key names?](https://stackoverflow.com/a/40094403). That one uses [`Populate`](https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonSerializer_Populate.htm) for reading, but still uses a thread static when writing. – dbc Sep 19 '17 at 07:10

1 Answers1

1

It's possible to disable the converter using a thread static variable or ThreadLocal<T> member, as shown in JSON.Net throws StackOverflowException when using JsonConvert or Generic method of modifying JSON before being returned to client. However, I'd like to suggest a simpler way of solving your problem.

You wrote, We dont want to use the JsonExtensionData attribute provided by Newtonsoft.JSON as we need to keep the property as Dictionary and we dont want to pass JToken to below layers. It is not necessary for the extension data dictionary to have values of type JToken. Values of type object are supported for extension data dictionaries, e.g.:

class AbcDTO
{
    public AbcDTO() { this.AdditionalData = new Dictionary<string, object>(); }

    public string Prop1 { get; set; }
    public string Prop2 { get; set; }

    [JsonExtensionData]
    public Dictionary<string, object> AdditionalData { get; private set; }
}

When the extension data dictionary is of type Dictionary<string, object>, Json.NET will deserialize JSON primitive values to their equivalent .Net primitives -- string, bool, long and so on -- rather than to JValue objects. Only when encountering an additional property whose value is a JSON object or array will a JToken be added to the dictionary, in which case you can use the answers from How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? to convert the JToken to a conventional .Net type. (However, your question states that The additional data will not contain data of reference type, so this should not be necessary.)

Using [JsonExtensionData] in this manner completely avoids the need for a converter while also deserializing primitives as per your requirements, and thus seems much simpler than the original design shown in the question.

Sample .Net fiddle demonstrating that extension properties can be deserialized into AbcDTO and asserting that none of them are of type JToken.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks @dbc. I was able to serialize/deserialize the object using reflection. The serializer already have my custom contract resolver. I used the same serializer to serialize or deserialize my object. – Mayank Oct 01 '17 at 10:46