Как вы JSON.stringify карту ES6?

112

Я хотел бы начать использовать карту ES6 вместо объектов JS, но меня сдерживают, потому что я не могу понять, как использовать JSON.stringify () для карты. Мои ключи гарантированно будут строками, и мои значения всегда будут указаны. Мне действительно нужно писать метод-оболочку для сериализации?

Рыноп
источник
1
интересная статья на тему 2ality.com/2015/08/es6-map-json.html
Дэвид Чейз
Я смог заставить это работать. Результаты находятся на Plunkr по адресу embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP . В решении используется JSON.stringify (obj, replacerFunction), который проверяет, передается ли объект Map, и преобразует объект Map в объект Javascript (который JSON.stringify) затем преобразует в строку.
PatS
1
Если ваши ключи гарантированно являются строками (или числами) и массивами значений , вы можете сделать что-то вроде [...someMap.entries()].join(';'): для чего-то более сложного вы можете попробовать что-то подобное, используя что-то вроде[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8
@Oriol Что, если имя ключа может совпадать со свойствами по умолчанию? obj[key]может доставить вам что-то неожиданное. Рассмотрим случай if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Франклин Ю

Ответы:

71

Оба JSON.stringifyи JSON.parseподдерживают второй аргумент. replacerи reviverсоответственно. С помощью replacer и reviver ниже можно добавить поддержку собственного объекта Map, включая глубоко вложенные значения.

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

Использование :

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

Глубокая вложение с комбинацией массивов, объектов и карт

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
Это лучший ответ на данный момент
Мануэль Вера Сильвестре
1
Определенно лучший ответ здесь.
JavaRunner
Просто отметил это как правильное. Хотя мне не нравится тот факт, что вам нужно «испачкать» данные по сети нестандартными dataTypeспособами, я не могу придумать более чистого способа. Спасибо.
Рыноп
@rynop, да, это больше похоже на небольшую программу с собственным форматом хранения данных, чем на использование чистой нативной функциональности
Павел
77

Вы не можете напрямую Mapпреобразовать экземпляр, поскольку у него нет никаких свойств, но вы можете преобразовать его в массив кортежей:

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

Для обратного используйте

map = new Map(JSON.parse(jsonText));
Берги
источник
6
Это не преобразуется в объект JSON, а вместо этого преобразуется в массив массивов. Не то же самое. См. Ответ Эвана Кэрролла ниже для более полного ответа.
Сб Тиру
3
@SatThiru Массив кортежей - это обычное представление Maps, он хорошо сочетается с конструктором и итератором. Кроме того, это единственное разумное представление карт, которые имеют нестроковые ключи, и объект не будет работать там.
Берги
Берги, обратите внимание, что OP сказал: «Мои ключи гарантированно будут строками».
Сб Тиру
4
@SatThiru В таком случае используйте JSON.stringify(Object.fromEntries(map.entries()))иnew Map(Object.entries(JSON.parse(jsonText)))
Берги
@Bergi Stringify не работает, если keyэто объект, например "{"[object Object]":{"b":2}}"- ключи объекта являются одной из основных функций Карт
Дренай,
66

Вы не можете.

Ключи карты могут быть любыми, включая объекты. Но синтаксис JSON позволяет использовать только строки в качестве ключей. Так что в общем случае это невозможно.

Мои ключи гарантированно будут строками, а мои значения всегда будут списками

В этом случае вы можете использовать простой объект. У него будут следующие преимущества:

  • Его можно будет преобразовать в JSON.
  • Он будет работать в старых браузерах.
  • Это могло быть быстрее.
Ориол
источник
33
для любопытных - в последней версии Chrome любая карта сериализуется в '{}'
Capaj
Я объяснил здесь, что именно я имел в виду, когда сказал «вы не можете».
Oriol
8
«Это могло бы быть быстрее» - У вас есть какой-нибудь источник на этот счет? Я воображаю, что простая хеш-карта должна быть быстрее, чем полноценный объект, но у меня нет доказательств. :)
Лиллеман
1
@Xplouder Этот тест использует дорого hasOwnProperty. Без этого Firefox выполняет итерацию объектов намного быстрее, чем карты. Однако карты в Chrome по-прежнему работают быстрее. jsperf.com/es6-map-vs-object-properties/95
Oriol,
1
Правда, похоже, что Firefox 45v перебирает объекты быстрее, чем Chrome + 49v. Однако Карты по-прежнему выигрывают у объектов в Chrome.
Xplouder
16

Хотя ecmascript еще не предоставляет метода, это все еще можно сделать, JSON.stingifyесли вы сопоставите Mapего с примитивом JavaScript. Вот образец, который Mapмы будем использовать.

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

Переход к объекту JavaScript

Вы можете преобразовать в литерал объекта JavaScript с помощью следующей вспомогательной функции.

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

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

Переход к массиву объектов JavaScript

Вспомогательная функция для этого была бы еще более компактной

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

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

Переход к массиву массивов

Это еще проще, вы можете просто использовать

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'
Эван Кэрролл
источник
14

С помощью spread sytax Map можно сериализовать в одну строку:

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

и десериализуйте его с помощью:

let map = new Map(JSON.parse(map));
методика
источник
Это будет работать для одномерной карты, но не для n-мерной карты.
матцвен
7

Стрингируйте Mapэкземпляр (объекты как ключи в порядке) :

JSON.stringify([...map])

или

JSON.stringify(Array.from(map))

или

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

Выходной формат:

// [["key1","value1"],["key2","value2"]]
am0wa
источник
4

Лучшее решение

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

Выход

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

Надеюсь, это менее неприятно, чем ответы выше.

Коди
источник
Я не уверен, что многие будут удовлетворены расширением класса основной карты только для того, чтобы сериализовать его в json ...
Васия,
1
Они не должны быть такими, но это более НАДЕЖНЫЙ способ сделать это. В частности, это соответствует принципам LSP и OCP SOLID. То есть собственная карта расширяется, а не изменяется, и можно по-прежнему использовать замещение Лискова (LSP) с собственной картой. Конечно, это больше ООП, чем предпочли бы многие новички или стойкие люди, занимающиеся функциональным программированием, но, по крайней мере, он опирается на проверенные и надежные основы фундаментальных принципов проектирования программного обеспечения. Если вы хотите реализовать принцип разделения интерфейса (ISP) SOLID, у вас может быть небольшой IJSONAbleинтерфейс (конечно, с использованием TypeScript).
Коди,
3

Решение ниже работает, даже если у вас есть вложенные карты.

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);
}
Имамудин Насим
источник
Не проверяя ваш ответ, я уже понимаю, как он привлекает внимание к проблеме вложенных карт. Даже если вы успешно конвертируете его в JSON, любой анализ, выполняемый в будущем, должен иметь явное понимание того, что JSON изначально был Mapи (что еще хуже) что каждая вложенная карта (она содержит) также изначально была картой. В противном случае невозможно быть уверенным, что это array of pairsне просто объект, а не Map. Иерархии объектов и массивов не несут этой нагрузки при синтаксическом анализе. Любая правильная сериализация Mapявно указала бы, что это Map.
Lonnie Best
Подробнее об этом здесь .
Lonnie Best
3

Учитывая, что ваш пример представляет собой простой вариант использования, в котором ключи будут простыми типами, я думаю, что это самый простой способ JSON структурировать карту.

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

Я думаю о базовой структуре данных карты как о массиве пар ключ-значение (как самих массивах). Итак, примерно так:

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

Поскольку эта базовая структура данных - это то, что мы находим в Object.entries, мы можем использовать собственный метод JavaScript для Object.fromEntries()Map, как и для Array:

Object.fromEntries(myMap);

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

И тогда все, что вам осталось, это использовать JSON.stringify () в результате этого.

Алок Сомани
источник