Jak JSON.stringify mapę ES6?

110

Chciałbym zacząć używać ES6 Map zamiast obiektów JS, ale jestem powstrzymywany, ponieważ nie mogę dowiedzieć się, jak JSON.stringify () a Map. Moje klucze są łańcuchami, a moje wartości zawsze będą wymienione. Czy naprawdę muszę napisać metodę opakowania, aby serializować?

rynop
źródło
1
ciekawy artykuł na ten temat 2ality.com/2015/08/es6-map-json.html
David Chase
Udało mi się to uruchomić. Wyniki są dostępne w Plunkr pod adresem embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP . Rozwiązanie wykorzystuje JSON.stringify (obj, replaceerFunction), który sprawdza, czy obiekt Map jest przekazywany i konwertuje obiekt Map na obiekt JavaScript (ten JSON.stringify) zostanie następnie przekonwertowany na ciąg.
PatS
1
Jeśli twoje klucze są łańcuchami (lub liczbami), a tablice wartości , możesz zrobić coś takiego [...someMap.entries()].join(';'); w przypadku czegoś bardziej złożonego możesz spróbować czegoś podobnego, używając czegoś takiego jak[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8
@Oriol A jeśli nazwa klucza może być taka sama, jak właściwości domyślne? obj[key]może przynieść coś nieoczekiwanego. Rozważ sprawę if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Odpowiedzi:

70

Oba JSON.stringifyi JSON.parsepopierają drugi argument. replaceri reviverodpowiednio. Z zamiennikiem i odświeżaczem poniżej możliwe jest dodanie obsługi natywnego obiektu Map, w tym głęboko zagnieżdżonych wartości

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

Zastosowanie :

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

Głębokie zagnieżdżanie dzięki połączeniu tablic, obiektów i map

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);
Paweł
źródło
8
To jest jak dotąd najlepsza odpowiedź
Manuel Vera Silvestre
1
Zdecydowanie najlepsza odpowiedź tutaj.
JavaRunner
Właśnie oznaczyłem to jako poprawne. Chociaż nie podoba mi się fakt, że trzeba „brudzić” dane w sieci w sposób niestandardowy dataType, nie mogę wymyślić prostszego sposobu. Dzięki.
rynop
@rynop yeah to bardziej mały program z własnym formatem przechowywania danych niż zwykła natywna funkcjonalność
Paweł
76

Nie możesz bezpośrednio zdefiniować Mapinstancji, ponieważ nie ma ona żadnych właściwości, ale możesz przekonwertować ją na tablicę krotek:

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

Na odwrót użyj

map = new Map(JSON.parse(jsonText));
Bergi
źródło
6
Nie konwertuje to na obiekt JSON, ale zamiast tego na tablicę tablic. To nie to samo. Zobacz odpowiedź Evana Carrolla poniżej, aby uzyskać pełniejszą odpowiedź.
Sob Thiru
3
@SatThiru Tablica krotek jest zwyczajową reprezentacją Maps, dobrze współpracuje z konstruktorem i iteratorem. Jest to również jedyna rozsądna reprezentacja map, które mają klucze niebędące łańcuchami, a obiekt nie będzie tam działał.
Bergi
Bergi, proszę zauważyć, że OP powiedział: „Moje klucze są gwarantowane jako struny”.
Sob Thiru,
@Bergi Stringify nie działa, jeśli keyjest obiektem, np. "{"[object Object]":{"b":2}}"- klucze obiektów są jedną z głównych funkcji Map
Drenai
66

Nie możesz.

Kluczami mapy mogą być cokolwiek, w tym obiekty. Ale składnia JSON zezwala tylko na ciągi jako klucze. Więc w ogólnym przypadku jest to niemożliwe.

Moje klucze są łańcuchami, a moje wartości zawsze będą listami

W takim przypadku możesz użyć zwykłego obiektu. Będzie miał następujące zalety:

  • Będzie można go zdefiniować w formacie JSON.
  • Będzie działać na starszych przeglądarkach.
  • To może być szybsze.
Oriol
źródło
33
dla ciekawskich najnowszego Chrome, każda mapa jest serializowana jako „{}”
Capaj,
Wyjaśniłem tutaj, co dokładnie miałem na myśli, mówiąc „nie możesz”.
Oriol
8
„Może być szybciej” - czy masz jakieś źródło na ten temat? Wyobrażam sobie, że prosta mapa skrótów musi być szybsza niż pełny obiekt, ale nie mam na to dowodów. :)
Lilleman
1
@Xplouder Ten test wykorzystuje drogie hasOwnProperty. Bez tego Firefox iteruje obiekty znacznie szybciej niż mapy. Mapy są jednak nadal szybsze w Chrome. jsperf.com/es6-map-vs-object-properties/95
Oriol
1
To prawda, wydaje się, że Firefox 45v iteruje obiekty szybciej niż Chrome + 49v. Jednak Mapy nadal wygrywają z obiektami w Chrome.
Xplouder
15

Chociaż nie ma jeszcze metody dostarczonej przez ecmascript, nadal można to zrobić za pomocą, JSON.stingifyjeśli mapujesz na Mapprymityw JavaScript. Oto próbka, Mapktórej użyjemy.

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

Przechodzenie do obiektu JavaScript

Możesz przekonwertować na literał obiektu JavaScript za pomocą następującej funkcji pomocniczej.

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

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

Przechodzenie do tablicy obiektów JavaScript

Funkcja pomocnicza dla tego byłaby jeszcze bardziej kompaktowa

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

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

Przechodzenie do Array of Arrays

Jest to jeszcze łatwiejsze, możesz po prostu użyć

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'
Evan Carroll
źródło
14

Korzystanie z rozszerzonej mapy sytax można serializować w jednej linii:

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

i zdeserializuj go za pomocą:

let map = new Map(JSON.parse(map));
metodribic
źródło
To zadziała dla mapy jednowymiarowej, ale nie dla mapy n-wymiarowej.
mattsven
7

Stringify do Mapinstancji (przedmioty jak klucze są OK) :

JSON.stringify([...map])

lub

JSON.stringify(Array.from(map))

lub

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

format wyjściowy:

// [["key1","value1"],["key2","value2"]]
am0wa
źródło
4

Lepsze rozwiązanie

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

Wynik

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

Mam nadzieję, że to mniej wstydliwe niż powyższe odpowiedzi.

Cody
źródło
Nie jestem pewien, czy wielu będzie zadowolonych z rozszerzenia klasy podstawowej mapy tylko po to, aby serializować ją do json ...
vasia
1
Nie muszą, ale jest to bardziej SOLIDNY ​​sposób na zrobienie tego. W szczególności jest to zgodne z zasadami LSP i OCP SOLID. Oznacza to, że natywna mapa jest rozszerzana, a nie modyfikowana, i nadal można używać podstawienia Liskova (LSP) z natywną mapą. To prawda, że ​​jest to bardziej OOP niż wielu nowicjuszy lub zagorzałych programistów funkcyjnych, ale przynajmniej jest on obciążony wypróbowaną i prawdziwą podstawą podstawowych zasad projektowania oprogramowania. Jeśli chcesz zaimplementować zasadę segregacji interfejsu (ISP) SOLID, możesz mieć mały IJSONAbleinterfejs (oczywiście używając TypeScript).
Cody
3

Poniższe rozwiązanie działa, nawet jeśli masz zagnieżdżone mapy

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
źródło
Nie testując Twojej odpowiedzi, doceniam już, jak zwraca ona uwagę na problem zagnieżdżonych Map. Nawet jeśli pomyślnie przekonwertujesz to na JSON, wszelkie analizy wykonane w przyszłości muszą mieć wyraźną świadomość, że JSON był pierwotnie Mapi (co gorsza), że każda mapa podrzędna (zawierająca) była również pierwotnie mapą. W przeciwnym razie nie ma sposobu, aby mieć pewność, że array of pairsnie ma być po prostu dokładnie tym, zamiast Mapą. Hierarchie obiektów i tablic nie są obciążone podczas analizowania. Każda prawidłowa serializacja Mapwyraźnie wskazywałaby, że jest to plik Map.
Lonnie Best
Więcej o tym tutaj .
Lonnie Best
3

Biorąc pod uwagę twój przykład jest prostym przypadkiem użycia, w którym klucze będą prostymi typami, myślę, że jest to najłatwiejszy sposób na stringifikację mapy przez JSON.

JSON.stringify(Object.fromEntries(map));

Sposób, w jaki myślę o podstawowej strukturze danych Map, to tablica par klucz-wartość (jak same tablice). Więc coś takiego:

const myMap = new Map([
     ["key1", "value1"],
     ["key2", "value2"],
     ["key3", "value3"]
]);

Ponieważ ta podstawowa struktura danych jest tym, co znajdujemy w Object.entries, możemy wykorzystać natywną metodę JavaScript Object.fromEntries()na Mapie, tak jak w Array:

Object.fromEntries(myMap);

/*
{
     key1: "value1",
     key2: "value2",
     key3: "value3"
}
*/

A potem wszystko, co pozostaje, to użycie JSON.stringify () w wyniku tego.

Alok Somani
źródło