Serialize dictionary as array (of key value pairs)

2020-01-24 02:41发布

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?

标签: c# json.net
6条回答
不美不萌又怎样
2楼-- · 2020-01-24 03:21

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",
    }
  },
]
查看更多
冷血范
3楼-- · 2020-01-24 03:27

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() } )
查看更多
Rolldiameter
4楼-- · 2020-01-24 03:29

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);
        }
    }
查看更多
甜甜的少女心
5楼-- · 2020-01-24 03:34

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

查看更多
▲ chillily
6楼-- · 2020-01-24 03:36

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.

查看更多
ら.Afraid
7楼-- · 2020-01-24 03:43

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.

查看更多
登录 后发表回答