web-dev-qa-db-fra.com

Impossible de sérialiser le dictionnaire avec une clé complexe à l'aide de Json.net

J'ai un dictionnaire avec un type .net personnalisé comme clé. J'essaie de sérialiser ce dictionnaire en JSON en utilisant JSON.net, mais il n'est pas en mesure de convertir les clés en valeur appropriée pendant la sérialisation.

class ListBaseClass
{
    public String testA;
    public String testB;
}
-----
var details = new Dictionary<ListBaseClass, string>();
details.Add(new ListBaseClass { testA = "Hello", testB = "World" }, "Normal");
var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<ListBaseClass, string>> results);

Cela me donne -> "{\" JSonSerialization.ListBaseClass\": \" Normal\"}"

Cependant, si j'ai mon type personnalisé comme valeur dans le dictionnaire, cela fonctionne bien

  var details = new Dictionary<string, ListBaseClass>();
  details.Add("Normal", new ListBaseClass { testA = "Hello", testB = "World" });
  var results = Newtonsoft.Json.JsonConvert.SerializeObject(details);
  var data = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, ListBaseClass>>(results);

Cela me donne -> "{\" Normal\": {\" testA\": \" Bonjour\", \" testB\": \" Monde\"}}"

Quelqu'un peut-il suggérer si je rencontre une limitation de Json.net ou si je fais quelque chose de mal?

34
Shashwat Gupta

Le Guide de sérialisation indique (voir la section: Dictionnaires et tables de hachage; merci @Shashwat pour le lien):

Lors de la sérialisation d'un dictionnaire, les clés du dictionnaire sont converties en chaînes et utilisées comme noms de propriété d'objet JSON. La chaîne écrite pour une clé peut être personnalisée en remplaçant ToString () pour le type de clé ou en implémentant un TypeConverter. Un TypeConverter prend également en charge la reconversion d'une chaîne personnalisée lors de la désérialisation d'un dictionnaire.

J'ai trouvé un exemple utile pour savoir comment implémenter un tel convertisseur de type sur la page "How-to" de Microsoft:

Essentiellement, j'avais besoin d'étendre System.ComponentModel.TypeConverter Et de remplacer:

bool CanConvertFrom(ITypeDescriptorContext context, Type source);

object ConvertFrom(ITypeDescriptorContext context,
                   System.Globalization.CultureInfo culture, object value);

object ConvertTo(ITypeDescriptorContext context, 
                 System.Globalization.CultureInfo culture, 
                 object value, Type destinationType);

Il était également nécessaire d'ajouter l'attribut [TypeConverter(typeof(MyClassConverter))] à la déclaration de classe MyClass.

Avec ceux-ci en place, j'ai pu sérialiser et désérialiser les dictionnaires automatiquement.

26
Gordon Bean

Vous ne voulez probablement pas utiliser la réponse que Gordon Bean a présentée. La solution fonctionne, mais elle fournit une chaîne sérialisée pour la sortie. Si vous utilisez JSON, cela vous donnera un résultat loin d'être idéal, car vous voulez vraiment une représentation JSON d'un objet et non une représentation de chaîne.

par exemple, supposons que vous ayez une structure de données qui associe des points de grille uniques à des chaînes:

class Point
{
    public int x { get; set; }
    public int y { get; set; }
}

public Dictionary<Point,string> Locations { get; set; };

En utilisant la substitution TypeConverter, vous obtiendrez une représentation sous forme de chaîne de cet objet lorsque vous le sérialisez.

"Locations": {
  "4,3": "foo",
  "3,4": "bar"
},

Mais ce que nous voulons vraiment, c'est:

"Locations": {
  { "x": 4, "y": 3 }: "foo",
  { "x": 3, "y": 4 }: "bar"
},

Il existe plusieurs problèmes avec la substitution du TypeConverter pour sérialiser/désérialiser la classe.

Tout d'abord, il ne s'agit pas de JSON, et vous devrez peut-être écrire une logique personnalisée supplémentaire pour gérer la sérialisation et la désérialisation ailleurs. (peut-être Javascript dans votre couche client, par exemple?)

Deuxièmement, n'importe où ailleurs qui utilise cet objet va maintenant cracher cette chaîne, où auparavant elle était sérialisée correctement en un objet:

"GridCenterPoint": { "x": 0, "y": 0 },

sérialise maintenant vers:

"GridCenterPoint": "0,0",

Vous pouvez contrôler un peu le formatage de TypeConverter, mais vous ne pouvez pas vous éloigner du fait qu'il est rendu sous forme de chaîne et non d'objet.

Ce problème n'est pas un problème avec le sérialiseur, car json.net parcourt des objets complexes sans manquer un battement, c'est un problème avec la façon dont les clés de dictionnaire sont traitées. Si vous essayez de prendre l'exemple d'objet et de sérialiser une liste ou même un Hashset, vous remarquez qu'il n'y a pas de problème pour produire le JSON approprié. Cela nous donne un moyen beaucoup plus simple de résoudre ce problème.

Idéalement, nous aimerions simplement dire à json.net de sérialiser la clé en tant que type d'objet, et de ne pas la forcer à être une chaîne. Puisque cela ne semble pas être une option, l'autre façon est de donner à json.net quelque chose avec lequel il peut fonctionner: a List<KeyValuePair<T,K>>.

Si vous introduisez une liste de KeyValuePairs dans le sérialiseur de json.net, vous obtenez exactement ce que vous attendez. Par exemple, voici un wrapper beaucoup plus simple que vous pourriez implémenter:

    private Dictionary<Point, string> _Locations;
    public List<KeyValuePair<Point, string>> SerializedLocations
    {
        get { return _Locations.ToList(); }
        set { _Locations= value.ToDictionary(x => x.Key, x => x.Value); }
    }

Cette astuce fonctionne, car les clés d'un kvp ne sont pas forcées au format chaîne. Pourquoi est-ce, demandez-vous? Ça bat l'enfer de moi. l'objet Dictionary implémente le IEnumerable<KeyValuePair<TKey, TValue>> interface, donc il ne devrait pas y avoir de problème à la sérialiser de la même manière que la liste des kvps, car c'est essentiellement ce qu'est une liste des kvps. Quelqu'un (James Newton?) A pris une décision lors de l'écriture du sérialiseur de dictionnaire Newtonsoft que les clés complexes étaient trop compliquées à gérer. Il y a probablement des cas d'angle que je n'ai pas considérés qui rendent ce problème beaucoup plus collant.

C'est une bien meilleure solution car elle produit des objets JSON réels, est techniquement plus simple et ne produit aucun effet secondaire résultant du remplacement du sérialiseur.

11
Roger Hill