1
votes

https://dotnetfiddle.net/ka6XVw - Fiddle with example type structure

Suppose I have a class that implements IDictionary<string, T>. Json.Net can deserialize such types out of the box, creating an instance of the type and using its indexer to populate the dictionary. The issue is that this class also inherits a string Error property marked with JsonProperty attribute from its base class, and I'd like this property to be populated whenever the input json contains an error field. However, when deserializing an IDictionary Json.Net considers all fields to be dictionary entries and tries to add the value with the error key to the dictionary.

What is the simplest and cleanest way to deserialize the json into a dictionary and the error field into the Error property? Please note that the class is generic, so JsonExtensionData is not an option (without casting its values to the provided type).

Sample valid dictionary json: { 'foo': '1', 'bar': '2' }

Sample error json { 'error': 'blah' }

1
Maybe this may help Similar question handling a weird nested type w/o a custom converterJlalonde

1 Answers

1
votes

I've derived a converter solution from this question. Basically, you attach a converter to your DictionaryResponse class, and interpret the incoming JSON yourself. I was lazy enough to use a JObject for parsing:

class DictionaryResponseConverter : JsonConverter<ResponseBase>
{
    public override ResponseBase ReadJson(
        JsonReader reader, Type objectType,
        ResponseBase existingValue, bool hasExistingValue,
        JsonSerializer serializer)
    {
        // find the correct T and call the internal function through reflection
        // as DictionaryResponse<T> is sealed, we don't care about inheritance
        return (ResponseBase)GetType()
            .GetMethod(nameof(InternalReadJson),
                       BindingFlags.Instance | BindingFlags.NonPublic)
            .MakeGenericMethod(objectType.GetGenericArguments()[0])
            .Invoke(this, new object[]
            {
                reader,
                existingValue,
                hasExistingValue,
                serializer
            });
    }

    DictionaryResponse<T> InternalReadJson<T>(
        JsonReader reader,
        DictionaryResponse<T> existingValue, bool hasExistingValue,
        JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var obj = JObject.Load(reader);
        var error = (string)obj["error"];
        bool hadError = obj.Remove("error");
        //var result = new DictionaryResponse<T>();
        var result = hasExistingValue ? existingValue : new DictionaryResponse<T>();
        foreach (var kvp in obj)
            result[kvp.Key] = kvp.Value.ToObject<T>();
        if (hadError)
            result.Error = error;
        return result;
    }

    public override void WriteJson(
        JsonWriter writer, ResponseBase value, JsonSerializer serializer)
    {
        // don't care about serialization
        throw new NotImplementedException();
    }
}
[JsonConverter(typeof(DictionaryResponseConverter))]
internal sealed class DictionaryResponse<T> : ResponseBase, IDictionary<string, T>
{
    ...