37

Json.Net typically serializes a Dictionary<k,v> into a collection;

"MyDict": {
  "Apples": {
    "Taste": 1341181398,
    "Title": "Granny Smith",
  },
  "Oranges": {
    "Taste": 9999999999,
    "Title": "Coxes Pippin",
  },
 }

Which is great. And from looking around on SO it seems to be what most people want. However, in this particular case, I want to serialize between my Dictionary<k,v> and the Array format instead;

"MyDict": [
    "k": "Apples",
    "v": {
        "Taste": 1341181398,
        "Title": "Granny Smith",
    }
  },
    "k:": "Oranges",
    "v:": {
        "Taste": 9999999999,
        "Title": "Coxes Pippin",
    }
  },
]

Is there an easy way to do this with my existing field type? Is there an attribute I can annotate for instance?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
cirrus
  • 5,624
  • 8
  • 44
  • 62

6 Answers6

46

Another way to accomplish this is to use a custom ContractResolver. That way you do not have to subclass Dictionary<K,V> nor apply a transform each time you serialize, as suggested in other answers.

The following resolver will cause ALL dictionaries to be serialized as an array of objects with "Key" and "Value" properties:

class DictionaryAsArrayResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (objectType.GetInterfaces().Any(i => i == typeof(IDictionary) || 
           (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))))
        {
            return base.CreateArrayContract(objectType);
        }

        return base.CreateContract(objectType);
    }
}

To use the resolver, add it to your JsonSerializerSettings, then pass the settings to JsonConvert.SerializeObject() like this:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new DictionaryAsArrayResolver();

string json = JsonConvert.SerializeObject(obj, settings);

Here is a working demo.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • Working for serialization, now How to deserialize same object – MSTdev Sep 07 '15 at 12:54
  • @MSTdev Same idea, use `JsonConvert.DeserializeObject` and be sure pass the `JsonSerializerSettings` with the resolver to that method. I updated the fiddle to demonstrate. – Brian Rogers Sep 07 '15 at 16:20
  • @BrianRogers please check my detailed issue http://stackoverflow.com/questions/32473308/serialize-deserialization-issue-on-newton-json-for-idictionarymycustomclass-li – MSTdev Sep 09 '15 at 07:24
  • What if I only want Dictionary to be converted? Change both IDictionary to IDictionary? – Seaky Lone May 27 '18 at 05:56
29

Ah, it turns out this is as straightforward as I'd hoped. My Dictionary<k,v> is subclassed already and I found that I can annotate it with [JsonArrayAttribute]. That gives me exactly the format I need;

"MyDict": [
  {
    "Key": "Apples",
    "Value": {
        "Taste": 1341181398,
        "Title": "Granny Smith",
    }
  },
  {
    "Key:": "Oranges",
    "Value:": {
        "Taste": 9999999999,
        "Title": "Coxes Pippin",
    }
  },
]
cirrus
  • 5,624
  • 8
  • 44
  • 62
  • 2
    Is there a way to flatten the Key and Value properties (to be one property on the object in the top-level array), with Json.NET configuration? – ryanwebjackson Aug 03 '20 at 16:54
14

For this example, I'll use the dictonary:

var myDict = new Dictionary<string,string>() { 
    {"a","123"}, 
    {"b","234"}, 
    {"c","345"} 
};

which serializes (with Newtonsoft.Json.JsonConvert.SerializeObject(myDict)) to:

{"a":"123","b":"234","c":"345"}

You could do a transform using LINQ to create an anonymous object, and serialize that:

 var myDictTransformed = from key in myDict.Keys
                         select new { k = key, v = myDict[key] };

Or you could use a real object

class MyDictEntry 
{
    public string k { get; set; }
    public string v { get; set; }
}

and either the above or the alternative LINQ syntax:

var myDictTransformed = myDict.Keys.AsEnumerable()
                        .Select(key => new MyDictEntry{ 
                            k = key, 
                            v = myDict[key] 
                        });

Either way, this serializes to:

[
  {"k":"a", "v":"123"},
  {"k":"b", "v":"234"},
  {"k":"c", "v":"345"}
]

.NET Fiddle link: https://dotnetfiddle.net/LhisVW

gregmac
  • 24,276
  • 10
  • 87
  • 118
12

The simplest solution I found is to convert your Dictionary<string, string> to a List<KeyValuePair<string, string>>. JSON.NET then converts your List into an array of objects with the form { Key: 'keyname', Value: 'value' }. This works well if you accept the required model change and don't want to subclass your Dictionary.

Swoogan
  • 5,298
  • 5
  • 35
  • 47
1

gregmac's answer was helpful, but didn't quite work. The following is the same idea... without the puns.

var dictionaryTransformed = dictionary.Select(item => item.Key).Select(i => 
                        new {Key = i, Value = dictionary[i] });

or of course

var dictionaryTransformed = dictionary.Select(item => 
                        new {item.Key, Value = dictionary[item.Key] });

Then to json

var json = (new JavaScriptSerializer()).Serialize( 
                        new { Container = dictionaryTransformed.ToArray() } )
ccook
  • 5,869
  • 6
  • 56
  • 81
0

I'm not exactly sure why, but the custom ContractResolver by Brian Rogers listed above didn't work for me. It seemed to get into an endless loop somewhere internally. Possibly due to other parts of my json.net setup.

Anyway - this workaround did the trick for me.

public interface IStrongIdentifier
    {
        string StringValue { get; set; }
    }

public class StrongIdentifierKeyedDictionaryWrapper<TKey, TValue> : Dictionary<string, TValue>
        where TKey : IStrongIdentifier
    {
        public void Add(TKey key, TValue value)
        {
            base.Add(key.StringValue, value);
        }

        public void Remove(TKey key)
        {
            base.Remove(key.StringValue);
        }

        public TValue this[TKey index]
        {
            get => base[index.StringValue];
            set => base[index.StringValue] = value;
        }

        public bool ContainsKey(TKey key)
        {
            return base.ContainsKey(key.StringValue);
        }
    }
Damien Sawyer
  • 5,323
  • 3
  • 44
  • 56