Wie können Sie eine ES6-Karte mit JSON.stringify erstellen?

102

Ich möchte ES6 Map anstelle von JS-Objekten verwenden, werde jedoch zurückgehalten, da ich nicht herausfinden kann, wie eine Map mit JSON.stringify () erstellt wird. Meine Schlüssel sind garantiert Zeichenfolgen und meine Werte werden immer aufgelistet. Muss ich wirklich eine Wrapper-Methode schreiben, um zu serialisieren?

rynop
quelle
1
interessanter Artikel zum Thema 2ality.com/2015/08/es6-map-json.html
David Chase
Ich konnte das zum Laufen bringen. Die Ergebnisse finden Sie auf Plunkr unter embedded.plnkr.co/oNlQQBDyJUiIQlgWUPVP . Die Lösung verwendet eine JSON.stringify (obj, replaceerFunction), die prüft, ob ein Map-Objekt übergeben wird, und das Map-Objekt in ein Javascript-Objekt konvertiert (das JSON.stringify) dann in eine Zeichenfolge konvertiert.
PatS
1
Wenn Sie Ihre Schlüssel garantiert Strings (oder Zahlen) und Ihre Werte - Arrays , können Sie so etwas wie zu tun [...someMap.entries()].join(';'); für etwas komplexeres könnten Sie etwas Ähnliches versuchen, indem Sie etwas wie[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8
@Oriol Was ist, wenn der Schlüsselname mit den Standardeigenschaften identisch sein kann? obj[key]kann Ihnen etwas Unerwartetes bringen. Betrachten Sie den Fall if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Antworten:

65

Das kannst du nicht.

Die Schlüssel einer Karte können alles sein, einschließlich Objekte. Die JSON-Syntax erlaubt jedoch nur Zeichenfolgen als Schlüssel. Im Allgemeinen ist das also unmöglich.

Meine Schlüssel sind garantiert Zeichenfolgen und meine Werte sind immer Listen

In diesem Fall können Sie ein einfaches Objekt verwenden. Es wird diese Vorteile haben:

  • Es kann mit JSON verknüpft werden.
  • Es funktioniert in älteren Browsern.
  • Es könnte schneller sein.
Oriol
quelle
30
Für die Neugierigen im neuesten Chrom wird jede Karte in '{}'
Capaj
Ich habe hier erklärt , was genau ich meinte, als ich sagte "du kannst nicht".
Oriol
7
"Es könnte schneller sein" - Haben Sie eine Quelle dafür? Ich stelle mir vor, eine einfache Hash-Map muss schneller sein als ein ausgewachsenes Objekt, aber ich habe keinen Beweis. :)
Lilleman
1
@Xplouder Dieser Test verwendet teuer hasOwnProperty. Ohne dies iteriert Firefox Objekte viel schneller als Karten. Karten sind in Chrome jedoch immer noch schneller. jsperf.com/es6-map-vs-object-properties/95
Oriol
1
Es stimmt, dass Firefox 45v Objekte schneller als Chrome + 49v entfernt. Maps gewinnt jedoch immer noch gegen Objekte in Chrome.
Xplouder
68

Sie können die MapInstanz nicht direkt stringifizieren, da sie keine Eigenschaften hat, aber Sie können sie in ein Array von Tupeln konvertieren:

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

Verwenden Sie für die Umkehrung

map = new Map(JSON.parse(jsonText));
Bergi
quelle
6
Dies wird nicht in ein JSON-Objekt konvertiert, sondern in ein Array von Arrays. Nicht dasselbe. Eine vollständigere Antwort finden Sie in der Antwort von Evan Carroll unten.
Sa Thiru
3
@SatThiru Ein Array von Tupeln ist die übliche Darstellung von Maps und passt gut zum Konstruktor und Iterator. Es ist auch die einzige sinnvolle Darstellung von Karten, die keine Zeichenfolgenschlüssel haben und deren Objekt dort nicht funktionieren würde.
Bergi
Bergi, bitte beachte, dass OP sagte "Meine Schlüssel sind garantiert Strings".
Sa Thiru
@Bergi Stringify funktioniert nicht, wenn keyes sich um ein Objekt handelt, z. B. "{"[object Object]":{"b":2}}"- Objektschlüssel sind eines der Hauptmerkmale von Maps
Drenai
57

Beides JSON.stringifyund JSON.parseunterstützen ein zweites Argument. replacerund reviverjeweils. Mit Ersetzer und Reviver unten ist es möglich, Unterstützung für native Map-Objekte hinzuzufügen, einschließlich tief verschachtelter Werte

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

Verwendung :

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

Tiefes Verschachteln mit einer Kombination aus Arrays, Objekten und Karten

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
quelle
8
Dies ist die bisher beste Antwort
Manuel Vera Silvestre
1
Auf jeden Fall die beste Antwort hier.
JavaRunner
15

Obwohl Ecmascript noch keine Methode bereitstellt, kann dies dennoch verwendet werden, JSON.stingifywenn Sie das Mapeinem JavaScript- Grundelement zuordnen . Hier ist das Beispiel, das Mapwir verwenden werden.

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

Wechseln zu einem JavaScript-Objekt

Sie können mit der folgenden Hilfsfunktion in ein JavaScript-Objektliteral konvertieren.

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

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

Wechseln zu einem JavaScript-Array von Objekten

Die Hilfsfunktion für diese wäre noch kompakter

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

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

Zum Array von Arrays gehen

Dies ist noch einfacher, Sie können nur verwenden

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

Mit Spread Sytax Map kann in einer Zeile serialisiert werden:

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

und deserialisieren Sie es mit:

let map = new Map(JSON.parse(map));
metodribic
quelle
Dies funktioniert für eine eindimensionale Karte, jedoch nicht für eine n-dimensionale Karte.
Mattsven
6

Stringifizieren Sie eine MapInstanz (Objekte als Schlüssel sind in Ordnung) :

JSON.stringify([...map])

oder

JSON.stringify(Array.from(map))

oder

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

Ausgabeformat:

// [["key1","value1"],["key2","value2"]]
am0wa
quelle
4

Eine bessere Lösung

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

Ausgabe

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

Hoffe, das ist weniger nervös als die obigen Antworten.

Cody
quelle
Ich bin mir nicht sicher, ob viele mit der Erweiterung der
Kernkartenklasse
1
Sie müssen es nicht sein, aber es ist eine FESTERE Art, es zu tun. Dies entspricht insbesondere den LSP- und OCP-Prinzipien von SOLID. Das heißt, die native Karte wird erweitert, nicht geändert, und man kann weiterhin Liskov Substitution (LSP) mit einer nativen Karte verwenden. Zugegeben, es ist mehr OOP, als viele Anfänger oder überzeugte Leute der funktionalen Programmierung bevorzugen würden, aber zumindest basiert es auf einer bewährten Grundlage grundlegender Prinzipien des Software-Designs. Wenn Sie das Interface Segregation Principle (ISP) von SOLID implementieren möchten, können Sie eine kleine IJSONAbleSchnittstelle verwenden (natürlich mit TypeScript).
Cody
3

Die folgende Lösung funktioniert auch, wenn Sie verschachtelte Karten haben

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
quelle
Ohne Ihre Antwort zu testen, weiß ich bereits zu schätzen, wie sie die Aufmerksamkeit auf das Problem verschachtelter Karten lenkt. Selbst wenn Sie dies erfolgreich in JSON konvertieren, muss bei jeder zukünftigen Analyse explizit berücksichtigt werden, dass es sich bei JSON ursprünglich um eine MapKarte handelte und (noch schlimmer), dass jede Unterkarte (die sie enthält) ursprünglich auch eine Karte war. Ansonsten gibt es keine Möglichkeit, sicher zu sein, dass ein array of pairsnicht nur genau das sein soll, sondern eine Karte. Hierarchien von Objekten und Arrays tragen diese Last nicht, wenn sie analysiert werden. Jede ordnungsgemäße Serialisierung von Mapwürde ausdrücklich darauf hinweisen, dass es sich um eine handelt Map.
Lonnie Best
Mehr dazu hier .
Lonnie Best