¿Cómo se puede crear JSON en un mapa ES6?

105

Me gustaría comenzar a usar ES6 Map en lugar de objetos JS, pero me están reteniendo porque no puedo averiguar cómo JSON.stringify () un mapa. Se garantiza que mis claves serán cadenas y mis valores siempre estarán listados. ¿Realmente tengo que escribir un método contenedor para serializar?

Rynop
fuente
1
artículo interesante sobre el tema 2ality.com/2015/08/es6-map-json.html
David Chase
Pude hacer que esto funcionara. Los resultados están en Plunkr en embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP . La solución utiliza un JSON.stringify (obj, replacerFunction) que verifica si se está pasando un objeto Map y convierte el objeto Map en un objeto Javascript (que JSON.stringify) luego se convertirá en una cadena.
PatS
1
Si se garantiza que sus claves son cadenas (o números) y sus matrices de valores , puede hacer algo como [...someMap.entries()].join(';'); para algo más complejo, puede probar algo similar usando algo como[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica 8
@Oriol ¿Qué pasa si es posible que el nombre de la clave sea el mismo que las propiedades predeterminadas? obj[key]puede traerle algo inesperado. Considere el caso if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Respuestas:

65

No puedes.

Las claves de un mapa pueden ser cualquier cosa, incluidos los objetos. Pero la sintaxis JSON solo permite cadenas como claves. Entonces es imposible en un caso general.

Se garantiza que mis claves serán cadenas y mis valores siempre serán listas

En este caso, puede utilizar un objeto simple. Tendrá estas ventajas:

  • Se podrá encadenar a JSON.
  • Funcionará en navegadores más antiguos.
  • Puede que sea más rápido.
Oriol
fuente
31
para los curiosos, en la última
versión de
He explicado aquí lo que quise decir exactamente cuando dije "no puedes".
Oriol
8
"Podría ser más rápido" - ¿Tiene alguna fuente sobre eso? Me imagino que un simple mapa hash debe ser más rápido que un objeto completo, pero no tengo pruebas. :)
Lilleman
1
@Xplouder Esa prueba utiliza caro hasOwnProperty. Sin eso, Firefox itera objetos mucho más rápido que los mapas. Sin embargo, los mapas son aún más rápidos en Chrome. jsperf.com/es6-map-vs-object-properties/95
Oriol
1
Es cierto, parece que Firefox 45v itera los objetos más rápido que Chrome + 49v. Sin embargo, Maps aún gana frente a los objetos en Chrome.
Xplouder
71

No puede secuenciar directamente la Mapinstancia ya que no tiene ninguna propiedad, pero puede convertirla en una matriz de tuplas:

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

Para el reverso, use

map = new Map(JSON.parse(jsonText));
Bergi
fuente
6
Esto no se convierte en un objeto JSON, sino en una matriz de matrices. No es lo mismo. Vea la respuesta de Evan Carroll a continuación para obtener una respuesta más completa.
Sábado Thiru
3
@SatThiru Una matriz de tuplas es la representación habitual de Maps, va bien con el constructor y el iterador. Además, es la única representación sensata de mapas que tienen claves que no son cadenas, y el objeto no funcionaría allí.
Bergi
Bergi, tenga en cuenta que OP dijo "Se garantiza que mis claves son cadenas".
Sábado Thiru
@Bergi Stringify no funciona si keyes un objeto, por ejemplo "{"[object Object]":{"b":2}}", las claves de objeto son una de las características principales de Maps
Drenai
60

Ambos JSON.stringifyy JSON.parseapoyan un segundo argumento. replacery reviverrespectivamente. Con el reemplazo y el reviver a continuación, es posible agregar soporte para el objeto Map nativo, incluidos los valores profundamente anidados

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

Anidación profunda con combinación de matrices, objetos y 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
fuente
8
Esta ES la mejor respuesta hasta ahora
Manuel Vera Silvestre
1
Definitivamente la mejor respuesta aquí.
JavaRunner
15

Si bien ecmascript aún no proporciona un método, esto aún se puede hacer usando JSON.stingifysi asigna el Mapa una primitiva de JavaScript. Aquí está la muestra Mapque usaremos.

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

Ir a un objeto JavaScript

Puede convertir a JavaScript Object literal con la siguiente función 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"}'

Ir a una matriz de objetos de JavaScript

La función de ayuda para este sería aún más compacta

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

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

Ir a Matriz de matrices

Esto es aún más fácil, solo puede usar

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

El uso de spread sytax Map se puede serializar en una línea:

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

y deserializarlo con:

let map = new Map(JSON.parse(map));
metodribico
fuente
Esto funcionará para un mapa unidimensional, pero no para un mapa n-dimensional.
mattsven
6

Cadena de una Mapinstancia (los objetos como claves están bien) :

JSON.stringify([...map])

o

JSON.stringify(Array.from(map))

o

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

formato de salida:

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

Una mejor solucion

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

Salida

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

Espero que sea menos vergonzoso que las respuestas anteriores.

Cody
fuente
No estoy seguro de que muchos estén satisfechos con extender la clase de mapa central solo para serializarla en un json ...
vasia
1
No tienen por qué serlo, pero es una forma más SÓLIDA de hacerlo. Específicamente, esto se alinea con los principios LSP y OCP de SOLID. Es decir, el mapa nativo se está ampliando, no modificando, y todavía se puede usar la sustitución de Liskov (LSP) con un mapa nativo. Por supuesto, es más OOP de lo que preferirían muchos novatos o gente acérrima de Programación Funcional, pero al menos se basa en una línea de base probada y verdadera de principios fundamentales de diseño de software. Si desea implementar el principio de segregación de interfaz (ISP) de SOLID, puede tener una IJSONAbleinterfaz pequeña (usando TypeScript, por supuesto).
Cody
3

La siguiente solución funciona incluso si tiene mapas anidados

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
fuente
Sin probar su respuesta, ya aprecio cómo llama la atención sobre el problema de los mapas anidados. Incluso si convierte esto a JSON con éxito, cualquier análisis realizado en el futuro debe tener conciencia explícita de que el JSON era originalmente un Mapy (peor aún) que cada submapa (que contiene) también era originalmente un mapa. De lo contrario, no hay forma de estar seguro de que un array of pairsno solo pretende ser exactamente eso, en lugar de un Mapa. Las jerarquías de objetos y matrices no llevan esta carga cuando se analizan. Cualquier serialización adecuada de Mapindicaría explícitamente que es un Map.
Lonnie Best
Más sobre eso aquí .
Lonnie Best