1

I have a class with a property

public Dictionary<string, object> Attributes { get; set; }

The attribute values can only be strings, numbers, string arrays and number arrays.

I now have a JSON string (without type information) and would like to deserialize it into an object of that class. The problem is that attribute values that are arrays are deserialized into JArray objects rather than string[] or long[].

How can I perform the deserialization such that the resulting dictionary values of the property "Attribute" are all of type string, long, string[] or long[] (or alternatively object[] containing strings and longs)?

dbc
  • 104,963
  • 20
  • 228
  • 340
Mo B.
  • 5,307
  • 3
  • 25
  • 42
  • Related: [How do I use JSON.NET to deserialize into nested/recursive Dictionary and List?](https://stackoverflow.com/q/5546142/3744182). – dbc Oct 17 '18 at 16:11
  • Also related: [Deserialize JSON recursively to IDictionary (duplicate)](https://stackoverflow.com/q/11561597/3744182). – dbc Oct 17 '18 at 17:15

1 Answers1

1

One solution would be to create a custom JsonConverter for Dictionary<string, object> that adapts the logic from this answer to How do I use JSON.NET to deserialize into nested/recursive Dictionary and List? by Brian Rogers to return the specific types of arrays required, rather than just List<object> collections.

First, define the following converter and extension methods:

public class ObjectDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Dictionary<string, object>);
    }

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

        var tempDictionary = new Dictionary<string, object>();
        var old = reader.DateParseHandling;
        try
        {
            // Disable recognition of date strings
            reader.DateParseHandling = DateParseHandling.None;
            serializer.Populate(reader, tempDictionary);
        }
        finally
        {
            reader.DateParseHandling = old;
        }

        var dictionary = existingValue as IDictionary<string, object> ?? (IDictionary<string, object>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        foreach (var pair in tempDictionary)
            dictionary.Add(pair.Key, pair.Value.ToObject());

        return dictionary;
    }

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

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

public static partial class JsonExtensions
{
    public static JsonReader SkipComments(this JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            ;
        return reader;
    }

    public static object ToObject(this object obj)
    {
        return ToObject(obj as JToken) ?? obj;
    }

    public static object ToObject(this JToken token)
    {
        // Adapts the logic from https://stackoverflow.com/a/19140420/3744182) 
        // to https://stackoverflow.com/q/5546142/3744182
        // By [Brian Rogers](https://stackoverflow.com/users/10263/brian-rogers)

        if (token == null)
            return null;

        switch (token.Type)
        {
            case JTokenType.Null:
                return null;

            case JTokenType.Object:
                return token.Children<JProperty>()
                            .ToDictionary(prop => prop.Name,
                                          prop => ToObject(prop.Value));

            case JTokenType.Array:
                {
                    var list = token.Select(t => ToObject(t)).ToList();
                    if (list.All(i => i is long))
                        return list.Cast<long>().ToArray();
                    else if (list.All(i => i is string))
                        return list.Cast<string>().ToArray();
                    else return list.ToArray();
                }

            default:
                return ((JValue)token).Value;
        }
    }
}

Then add the converter to JsonSerializerSettings.Converters and deserialize as follows:

var settings = new JsonSerializerSettings
{
    Converters = { new ObjectDictionaryConverter() },
};
var deserializedObj = JsonConvert.DeserializeObject<AttributeObject>(jsonString, settings);

Notes:

  • Since you know that your dictionary values should be strings rather than DateTime values, I disabled Json.NET's automatic date recognition. For details about date recognition see Serializing Dates in JSON.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340