web-dev-qa-db-fra.com

Sérialiser le dictionnaire en tant que tableau (des paires clé-valeur)

Json.Net sérialise généralement un Dictionary<k,v> dans une collection;

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

Qui est genial. Et en regardant autour de SO cela semble être ce que la plupart des gens veulent. Cependant, dans ce cas particulier, je souhaite plutôt sérialiser entre mon Dictionary<k,v> et le format Array;

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

Y at-il un moyen facile de faire cela avec mon type de champ existant? Y a-t-il un attribut que je peux annoter par exemple?

29
cirrus

Ah, il s'avère que c'est aussi simple que je l'espérais. Mon Dictionary<k,v> est déjà sous-classé et j'ai découvert que je pouvais l'annoter avec [JsonArrayAttribute]. Cela me donne exactement le format dont j'ai besoin;

"MyDict": [
  {
    "Key": "Apples",
    "Value": {
        "Taste": 1341181398,
        "Title": "Granny Smith",
    }
  },
  {
    "Key:": "Oranges",
    "Value:": {
        "Taste": 9999999999,
        "Title": "Coxes Pippin",
    }
  },
]
23
cirrus

Pour ce faire, vous pouvez également utiliser une variable ContractResolver personnalisée. De cette façon, vous ne devez pas sous-classer Dictionary<K,V> ni appliquer une transformation à chaque fois que vous sérialisez, comme suggéré dans d'autres réponses.

Le résolveur suivant entraînera la sérialisation de TOUS les dictionnaires en tant que tableau d'objets avec les propriétés "Key" et "Value":

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);
    }
}

Pour utiliser le résolveur, ajoutez-le à votre JsonSerializerSettings, puis transmettez les paramètres à JsonConvert.SerializeObject() comme ceci:

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

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

Voici une démo de travail .

33
Brian Rogers

Pour cet exemple, je vais utiliser le dictonary:

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

qui sérialise (avec Newtonsoft.Json.JsonConvert.SerializeObject(myDict)) pour: 

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

Vous pouvez faire une transformation en utilisant LINQ pour créer un objet anonyme et sérialiser cela:

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

Ou vous pouvez utiliser un objet réel

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

et la syntaxe LINQ ci-dessus ou alternative:

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

De toute façon, cela sérial à:

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

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

12
gregmac

La solution la plus simple que j'ai trouvée consiste à convertir votre Dictionary<string, string> en List<KeyValuePair<string, string>>. JSON.NET convertit ensuite votre List en un tableau d'objets de la forme { Key: 'keyname', Value: 'value' }. Cela fonctionne bien si vous acceptez le changement de modèle requis et ne souhaitez pas sous-classer votre Dictionary.

10
Swoogan

la réponse de gregmac a été utile, mais n'a pas fonctionné. Ce qui suit est la même idée ... sans les calembours.

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

ou bien sur

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

Puis à json

var json = (new JavaScriptSerializer()).Serialize( 
                        new { Container = dictionaryTransformed.ToArray() } )
1
ccook

Je ne sais pas trop pourquoi, mais le ContractResolver personnalisé de Brian Rogers ci-dessus répertorié ne fonctionnait pas pour moi. Il semblait entrer dans une boucle sans fin quelque part en interne. Peut-être en raison d'autres parties de ma configuration json.net. 

Quoi qu'il en soit - cette solution de contournement a fait l'affaire pour moi. 

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);
        }
    }
0
Damien Sawyer