Come si stringe in JSON una mappa ES6?


103

Vorrei iniziare a utilizzare ES6 Map invece degli oggetti JS, ma sono trattenuto perché non riesco a capire come JSON.stringify () una mappa. Le mie chiavi sono garantite come stringhe e i miei valori saranno sempre elencati. Devo davvero scrivere un metodo wrapper per serializzare?


1
articolo interessante sull'argomento 2ality.com/2015/08/es6-map-json.html
David Chase

Sono riuscito a farlo funzionare. I risultati sono su Plunkr all'indirizzo embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP . La soluzione utilizza un JSON.stringify (obj, replacerFunction) che controlla se viene passato un oggetto Map e converte l'oggetto Map in un oggetto Javascript (che JSON.stringify) verrà quindi convertito in una stringa.
PatS

1
Se è garantito che le tue chiavi siano stringhe (o numeri) e matrici di valori , puoi fare qualcosa di simile [...someMap.entries()].join(';'); per qualcosa di più complesso potresti provare qualcosa di simile usando qualcosa come[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8

@Oriol E se fosse possibile che il nome della chiave corrisponda alle proprietà predefinite? obj[key]potrebbe procurarti qualcosa di inaspettato. Considera il caso if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Risposte:


65

Non puoi.

Le chiavi di una mappa possono essere qualsiasi cosa, inclusi gli oggetti. Ma la sintassi JSON consente solo le stringhe come chiavi. Quindi è impossibile in un caso generale.

Le mie chiavi sono garantite come stringhe e i miei valori saranno sempre elenchi

In questo caso, puoi usare un oggetto semplice. Avrà questi vantaggi:

  • Potrà essere associato a JSON.
  • Funzionerà su browser meno recenti.
  • Potrebbe essere più veloce.

31
per i curiosi: nell'ultimo Chrome, qualsiasi mappa viene serializzata in "{}"
Capaj

Ho spiegato qui cosa intendevo esattamente quando ho detto "non puoi".
Oriol

7
"Potrebbe essere più veloce" - Hai qualche fonte su questo? Immagino che una semplice mappa hash debba essere più veloce di un oggetto in piena regola, ma non ho prove. :)
Lilleman

1
@Xplouder Quel test utilizza costosi hasOwnProperty. Senza questo, Firefox itera gli oggetti molto più velocemente delle mappe. Le mappe sono ancora più veloci su Chrome, però. jsperf.com/es6-map-vs-object-properties/95
Oriol

1
È vero, sembra che Firefox 45v iteri gli oggetti più velocemente di Chrome + 49v. Tuttavia Maps vince ancora contro gli oggetti in Chrome.
Xplouder

69

Non puoi stringere direttamente l' Mapistanza poiché non ha proprietà, ma puoi convertirla in un array di tuple:

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

Per il contrario, usa

map = new Map(JSON.parse(jsonText));

6
Non viene convertito in un oggetto JSON, ma in un array di array. Non è la stessa cosa. Vedi la risposta di Evan Carroll di seguito per una risposta più completa.
Sab Thiru

3
@SatThiru Un array di tuple è la rappresentazione abituale di Maps, si sposa bene con il costruttore e l'iteratore. Inoltre è l'unica rappresentazione sensata di mappe che hanno chiavi non stringa e l'oggetto non funzionerebbe lì.
Bergi

Bergi, tieni presente che OP ha detto "Le mie chiavi sono garantite come stringhe".
Sab Thiru


@Bergi Stringify non funziona se keyè un oggetto, ad esempio le "{"[object Object]":{"b":2}}"chiavi degli oggetti sono una delle caratteristiche principali di Maps
Drenai

58

Entrambi JSON.stringifye JSON.parsesupportano un secondo argomento. replacere reviverrispettivamente. Con replacer e reviver di seguito è possibile aggiungere il supporto per l'oggetto Map nativo, inclusi valori profondamente nidificati

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

Utilizzo :

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

Annidamento profondo con combinazione di array, oggetti e mappe

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

8
Questa è la migliore risposta finora
Manuel Vera Silvestre

1
Sicuramente la migliore risposta qui.
JavaRunner

15

Anche se non esiste ancora un metodo fornito da ecmascript, questo può ancora essere fatto utilizzando JSON.stingifyse si associa Mapa una primitiva JavaScript. Ecco il campione Mapche useremo.

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

Andare a un oggetto JavaScript

È possibile convertire in valore letterale oggetto JavaScript con la seguente funzione di supporto.

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

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

Andare a una matrice di oggetti JavaScript

La funzione di supporto per questo sarebbe ancora più compatta

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

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

Andando a Array of Arrays

Questo è ancora più semplice, puoi semplicemente usare

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'

13

Utilizzando spread sytax Map può essere serializzato in una riga:

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

e deserializzalo con:

let map = new Map(JSON.parse(map));

Funzionerà per una mappa unidimensionale, ma non per una mappa n-dimensionale.
mattsven

6

Stringa Mapun'istanza (gli oggetti come chiavi sono OK) :

JSON.stringify([...map])

o

JSON.stringify(Array.from(map))

o

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

formato di output:

// [["key1","value1"],["key2","value2"]]

4

Una soluzione migliore

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

Produzione

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

Spero che sia meno imbarazzante delle risposte sopra.


Non sono sicuro che molti saranno soddisfatti dell'estensione della classe della mappa principale solo per serializzarla su un json ...
vasia

1
Non devono esserlo, ma è un modo più SOLIDO per farlo. Nello specifico, questo è in linea con i principi LSP e OCP di SOLID. Cioè, la mappa nativa viene estesa, non modificata, e si può ancora usare Liskov Substitution (LSP) con una mappa nativa. Certo, è più OOP di quanto preferirebbero molti principianti o persone devote di Programmazione Funzionale, ma almeno è assediato su una base collaudata di principi fondamentali di progettazione del software. Se si desidera implementare il principio di segregazione dell'interfaccia (ISP) di SOLID, è possibile disporre di una piccola IJSONAbleinterfaccia (utilizzando TypeScript, ovviamente).
Cody

3

La soluzione di seguito funziona anche se hai Maps nidificato

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

Senza testare la tua risposta, apprezzo già come attiri l'attenzione sul problema delle mappe nidificate. Anche se converti con successo questo in JSON, qualsiasi analisi eseguita in futuro deve avere la consapevolezza esplicita che il JSON era originariamente una Mape (anche peggio) che ogni mappa secondaria (che contiene) era anche originariamente una mappa. Altrimenti, non c'è modo di essere sicuri che un array of pairsnon sia inteso solo per essere esattamente quello, invece di una mappa. Le gerarchie di oggetti e array non portano questo onere quando vengono analizzate. Qualsiasi serializzazione corretta di Mapindicherebbe esplicitamente che si tratta di un file Map.
Lonnie Best

Maggiori informazioni qui .
Lonnie Best
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.