Como você JSON.stringifica um mapa ES6?

103

Gostaria de começar a usar o mapa ES6 em vez de objetos JS, mas estou sendo impedido porque não consigo descobrir como JSON.stringify () um mapa. Minhas chaves são strings e meus valores sempre serão listados. Eu realmente preciso escrever um método wrapper para serializar?

rynop
fonte
1
artigo interessante sobre o tópico 2ality.com/2015/08/es6-map-json.html
David Chase
Consegui fazer isso funcionar. Os resultados estão no Plunkr em embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP . A solução usa um JSON.stringify (obj, replaceerFunction) que verifica se um objeto Map está sendo passado e converte o objeto Map em um objeto Javascript (que JSON.stringify) irá então converter em uma string.
PatS de
1
Se suas chaves são strings (ou números) e seus arrays de valores , você pode fazer algo como [...someMap.entries()].join(';'); para algo mais complexo, você pode tentar algo semelhante usando algo como[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8
@Oriol E se for possível que o nome da chave seja igual às propriedades padrão? obj[key]pode lhe trazer algo inesperado. Considere o caso if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Respostas:

65

Você não pode.

As chaves de um mapa podem ser qualquer coisa, incluindo objetos. Mas a sintaxe JSON permite apenas strings como chaves. Portanto, é impossível em um caso geral.

Minhas chaves são garantidamente strings e meus valores sempre serão listas

Nesse caso, você pode usar um objeto simples. Terá estas vantagens:

  • Poderá ser transformado em JSON.
  • Funcionará em navegadores mais antigos.
  • Pode ser mais rápido.
Oriol
fonte
31
para os curiosos - no cromo mais recente, qualquer mapa se serializa em '{}'
Capaj
Expliquei aqui o que exatamente quis dizer quando disse "você não pode".
Oriol
7
"Pode ser mais rápido" - Você tem alguma fonte sobre isso? Estou imaginando que um mapa hash simples deve ser mais rápido do que um objeto totalmente desenvolvido, mas não tenho provas. :)
Lilleman
1
@Xplouder Esse teste usa caro hasOwnProperty. Sem isso, o Firefox itera objetos muito mais rápido do que mapas. Os mapas são ainda mais rápidos no Chrome, no entanto. jsperf.com/es6-map-vs-object-properties/95
Oriol
1
Verdade, parece que o Firefox 45v repassa objetos mais rápido do que o Chrome + 49v. No entanto, o Maps ainda vence os objetos no Chrome.
Xplouder de
69

Você não pode stringificar diretamente a Mapinstância, pois ela não tem nenhuma propriedade, mas pode convertê-la em uma matriz de tuplas:

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

Para o reverso, use

map = new Map(JSON.parse(jsonText));
Bergi
fonte
6
Isso não se converte em um objeto JSON, mas em uma matriz de matrizes. Não é a mesma coisa. Veja a resposta de Evan Carroll abaixo para uma resposta mais completa.
Sábado, quinta-
3
@SatThiru Um array de tuplas é a representação usual de Maps, e combina com o construtor e o iterador. Além disso, é a única representação sensata de mapas que possuem chaves não-string e o objeto não funcionaria lá.
Bergi
Bergi, observe que OP disse "Minhas chaves são garantidamente strings".
Sábado, quinta-
@Bergi Stringify não funciona se o keyfor um objeto, por exemplo "{"[object Object]":{"b":2}}"- chaves de objetos sendo uma das principais características do Maps
Drenai
58

Ambos JSON.stringifye JSON.parseapóiam um segundo argumento. replacere reviverrespectivamente. Com substituto e reviver abaixo, é possível adicionar suporte para objeto de mapa nativo, incluindo valores profundamente aninhados

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

Uso :

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

Aninhamento profundo com combinação de Arrays, Objetos e Mapas

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
fonte
8
Esta é a melhor resposta até agora
Manuel Vera Silvestre
1
Definitivamente, a melhor resposta aqui.
JavaRunner
15

Embora não haja nenhum método fornecido pelo ecmascript ainda, isso ainda pode ser feito usando JSON.stingifyse você mapear o Mappara um primitivo JavaScript. Aqui está o exemplo Mapque usaremos.

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

Indo para um objeto JavaScript

Você pode converter para o literal de objeto JavaScript com a seguinte função auxiliar.

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

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

Indo para um array de objetos JavaScript

A função auxiliar para este seria ainda mais compacta

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

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

Indo para Array of Arrays

Isso é ainda mais fácil, você pode apenas usar

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

Usando o spread sytax, o Map pode ser serializado em uma linha:

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

e desserialize-o com:

let map = new Map(JSON.parse(map));
metodribico
fonte
Isso funcionará para um mapa unidimensional, mas não para um mapa n-dimensional.
mattsven
6

Stringify uma Mapinstância (objetos como chaves estão OK) :

JSON.stringify([...map])

ou

JSON.stringify(Array.from(map))

ou

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

Formato de saída:

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

Uma Solução Melhor

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

Resultado

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

Espero que seja menos irritante do que as respostas acima.

Cody
fonte
Não tenho certeza se muitos ficarão satisfeitos em estender a classe de mapa principal apenas para serializá-la em um json ...
vasia
1
Eles não precisam ser, mas é uma maneira mais SÓLIDA de fazer isso. Especificamente, isso se alinha com os princípios LSP e OCP de SOLID. Ou seja, o mapa nativo está sendo estendido, não modificado, e ainda é possível usar a substituição de Liskov (LSP) com um mapa nativo. Concedido, é mais OOP do que muitos novatos ou pessoas ferrenhas em Programação Funcional prefeririam, mas pelo menos é cercado por uma linha de base testada e comprovada de princípios fundamentais de design de software. Se você quiser implementar o Princípio de Segregação de Interface (ISP) do SOLID, pode ter uma IJSONAbleinterface pequena (usando TypeScript, é claro).
Cody
3

A solução abaixo funciona mesmo se você tiver mapas aninhados

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
fonte
Sem testar sua resposta, já apreciei como isso chama a atenção para o problema de mapas aninhados. Mesmo se você converter isso para JSON com êxito, qualquer análise feita no futuro deve ter consciência explícita de que o JSON era originalmente um Mape (pior ainda) que cada submapa (que contém) também era originalmente um mapa. Caso contrário, não há como ter certeza de que um array of pairsnão se destina a ser exatamente isso, em vez de um mapa. Hierarquias de objetos e arrays não carregam esse fardo quando analisadas. Qualquer serialização adequada de Mapindicaria explicitamente que é um Map.
Lonnie Best
Mais sobre isso aqui .
Lonnie Best