Comment JSON.stringify une carte ES6?

105

J'aimerais commencer à utiliser ES6 Map au lieu d'objets JS mais je suis retenu car je ne peux pas comprendre comment JSON.stringify () une Map. Il est garanti que mes clés sont des chaînes et mes valeurs seront toujours répertoriées. Dois-je vraiment écrire une méthode wrapper pour sérialiser?

rynop
la source
1
article intéressant sur le sujet 2ality.com/2015/08/es6-map-json.html
David Chase
J'ai pu faire fonctionner cela. Les résultats sont sur Plunkr à embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP . La solution utilise un JSON.stringify (obj, replacerFunction) qui vérifie si un objet Map est passé et convertit l'objet Map en objet Javascript (que JSON.stringify) sera ensuite converti en une chaîne.
PatS
1
S'il est garanti que vos clés sont des chaînes (ou des nombres) et que vos tableaux de valeurs , vous pouvez faire quelque chose comme [...someMap.entries()].join(';'); pour quelque chose de plus complexe, vous pouvez essayer quelque chose de similaire en utilisant quelque chose comme[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8
@Oriol Que faire s'il est possible que le nom de la clé soit identique aux propriétés par défaut? obj[key]peut vous apporter quelque chose d'inattendu. Considérez le cas if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Réponses:

65

Vous ne pouvez pas.

Les clés d'une carte peuvent être n'importe quoi, y compris des objets. Mais la syntaxe JSON n'autorise que les chaînes en tant que clés. C'est donc impossible dans un cas général.

Il est garanti que mes clés sont des chaînes et mes valeurs seront toujours des listes

Dans ce cas, vous pouvez utiliser un objet simple. Il aura ces avantages:

  • Il pourra être stringifié en JSON.
  • Cela fonctionnera sur les anciens navigateurs.
  • Cela pourrait être plus rapide.
Oriol
la source
31
pour les curieux-dans le dernier chrome, n'importe quelle carte sérialisée dans '{}'
Capaj
J'ai expliqué ici ce que je voulais dire exactement quand j'ai dit "vous ne pouvez pas".
Oriol
7
"Cela pourrait être plus rapide" - Avez-vous une source à ce sujet? J'imagine qu'une simple carte de hachage doit être plus rapide qu'un objet complet, mais je n'en ai aucune preuve. :)
Lilleman
1
@Xplouder Ce test utilise cher hasOwnProperty. Sans cela, Firefox itère les objets beaucoup plus rapidement que les cartes. Les cartes sont cependant encore plus rapides sur Chrome. jsperf.com/es6-map-vs-object-properties/95
Oriol
1
Certes, il semble que Firefox 45v répète les objets plus rapidement que Chrome + 49v. Cependant, Maps gagne toujours par rapport aux objets dans Chrome.
Xplouder
70

Vous ne pouvez pas directement stringifier l' Mapinstance car elle n'a aucune propriété, mais vous pouvez la convertir en un tableau de tuples:

jsonText = JSON.stringify(Array.from(map.entries()));

Pour l'inverse, utilisez

map = new Map(JSON.parse(jsonText));
Bergi
la source
6
Cela ne se convertit pas en un objet JSON, mais en un tableau de tableaux. Pas la même chose. Voir la réponse d'Evan Carroll ci-dessous pour une réponse plus complète.
Sam Thiru
3
@SatThiru Un tableau de tuples est la représentation habituelle de Maps, cela va bien avec le constructeur et l'itérateur. C'est aussi la seule représentation sensée des cartes qui ont des clés sans chaîne, et l'objet ne fonctionnerait pas là.
Bergi
Bergi, veuillez noter que OP a dit "Mes clés sont garanties comme des chaînes".
Sam Thiru
@Bergi Stringify ne fonctionne pas si le keyest un objet, par exemple "{"[object Object]":{"b":2}}"- les clés d'objet étant l'une des principales fonctionnalités de Maps
Drenai
59

Les deux JSON.stringifyet JSON.parsesoutiennent un deuxième argument. replaceret reviverrespectivement. Avec replacer et reviver ci-dessous, il est possible d'ajouter la prise en charge de l'objet Map natif, y compris des valeurs profondément imbriquées

function replacer(key, value) {
  const originalObject = this[key];
  if(originalObject instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(originalObject.entries()), // or with spread: value: [...originalObject]
    };
  } else {
    return value;
  }
}
function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}

Utilisation :

const originalValue = new Map([['a', 1]]);
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);

Imbrication profonde avec combinaison de tableaux, d'objets et de cartes

const originalValue = [
  new Map([['a', {
    b: {
      c: new Map([['d', 'text']])
    }
  }]])
];
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, newValue);
Pawel
la source
8
C'est la meilleure réponse à ce jour
Manuel Vera Silvestre
1
Certainement la meilleure réponse ici.
JavaRunner
15

Bien qu'il n'y ait pas encore de méthode fournie par ecmascript, cela peut toujours être fait en utilisant JSON.stingifysi vous mappez le Mapà une primitive JavaScript. Voici l'exemple que Mapnous utiliserons.

const map = new Map();
map.set('foo', 'bar');
map.set('baz', 'quz');

Accéder à un objet JavaScript

Vous pouvez convertir en littéral objet JavaScript avec la fonction d'assistance suivante.

const mapToObj = m => {
  return Array.from(m).reduce((obj, [key, value]) => {
    obj[key] = value;
    return obj;
  }, {});
};

JSON.stringify(mapToObj(map)); // '{"foo":"bar","baz":"quz"}'

Accéder à un tableau d'objets JavaScript

La fonction d'assistance pour celui-ci serait encore plus compacte

const mapToAoO = m => {
  return Array.from(m).map( ([k,v]) => {return {[k]:v}} );
};

JSON.stringify(mapToAoO(map)); // '[{"foo":"bar"},{"baz":"quz"}]'

Aller à Array of Arrays

C'est encore plus facile, vous pouvez simplement utiliser

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'
Evan Carroll
la source
13

Utilisation de la sytaxe de propagation La carte peut être sérialisée sur une seule ligne:

JSON.stringify([...new Map()]);

et désérialisez-le avec:

let map = new Map(JSON.parse(map));
métodribique
la source
Cela fonctionnera pour une carte à une dimension, mais pas pour une carte à n dimensions.
mattsven
6

Stringifier une Mapinstance (les objets comme clés sont OK) :

JSON.stringify([...map])

ou

JSON.stringify(Array.from(map))

ou

JSON.stringify(Array.from(map.entries()))

format de sortie:

// [["key1","value1"],["key2","value2"]]
am0wa
la source
4

Une meilleure solution

    // somewhere...
    class Klass extends Map {

        toJSON() {
            var object = { };
            for (let [key, value] of this) object[key] = value;
            return object;
        }

    }

    // somewhere else...
    import { Klass as Map } from '@core/utilities/ds/map';  // <--wherever "somewhere" is

    var map = new Map();
    map.set('a', 1);
    map.set('b', { datum: true });
    map.set('c', [ 1,2,3 ]);
    map.set( 'd', new Map([ ['e', true] ]) );

    var json = JSON.stringify(map, null, '\t');
    console.log('>', json);

Production

    > {
        "a": 1,
        "b": {
            "datum": true
        },
        "c": [
            1,
            2,
            3
        ],
        "d": {
            "e": true
        }
    }

J'espère que c'est moins grincheux que les réponses ci-dessus.

Cody
la source
Je ne suis pas sûr que beaucoup se contenteront d'étendre la classe de carte principale juste pour la sérialiser en json ...
vasia
1
Ils ne doivent pas l'être, mais c'est une façon plus SOLIDE de le faire. Plus précisément, cela correspond aux principes LSP et OCP de SOLID. Autrement dit, la carte native est étendue, non modifiée, et on peut toujours utiliser la substitution de Liskov (LSP) avec une carte native. Certes, c'est plus de la POO que beaucoup de novices ou de fervents programmeurs fonctionnels ne le préféreraient, mais au moins, il repose sur une base éprouvée de principes fondamentaux de conception de logiciels. Si vous souhaitez implémenter le principe de séparation d'interface (ISP) de SOLID, vous pouvez avoir une petite IJSONAbleinterface (en utilisant TypeScript, bien sûr).
Cody le
3

La solution ci-dessous fonctionne même si vous avez des cartes imbriquées

function stringifyMap(myMap) {
    function selfIterator(map) {
        return Array.from(map).reduce((acc, [key, value]) => {
            if (value instanceof Map) {
                acc[key] = selfIterator(value);
            } else {
                acc[key] = value;
            }

            return acc;
        }, {})
    }

    const res = selfIterator(myMap)
    return JSON.stringify(res);
}
Imamudin Naseem
la source
Sans tester votre réponse, j'apprécie déjà à quel point elle attire l'attention sur le problème des cartes imbriquées. Même si vous réussissez à le convertir en JSON, toute analyse effectuée à l'avenir doit avoir une conscience explicite du fait que le JSON était à l'origine un Mapet (pire encore) que chaque sous-carte (qu'elle contient) était également à l'origine une carte. Sinon, il n'y a aucun moyen d'être sûr qu'un array of pairsn'est pas simplement destiné à être exactement cela, au lieu d'une carte. Les hiérarchies d'objets et de tableaux ne supportent pas ce fardeau lorsqu'elles sont analysées. Toute sérialisation appropriée de Mapindiquerait explicitement qu'il s'agit d'un fichier Map.
Lonnie Best
Plus à ce sujet ici .
Lonnie Best