您如何通过JSON.stringify ES6 Map?


103

我想开始使用ES6 Map而不是JS对象,但是由于无法弄清楚如何对地图进行JSON.stringify()而被推迟了。我的键保证是字符串,并且我的值将始终列出。我真的必须编写包装方法来序列化吗?



我能够使它工作。结果位于Plunkr上的embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP上。该解决方案使用JSON.stringify(obj,replacerFunction)来检查是否传递了Map对象,并将Map对象转换为Javascript对象(该JSON.stringify)随后将转换为字符串。
PatS '18

1
如果保证您的键是字符串(或数字)和值数组,则可以执行类似[...someMap.entries()].join(';');的操作。对于更复杂的东西,您可以尝试使用类似的东西[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8'5

@Oriol如果密钥名称可以与默认属性相同怎么办?obj[key]可能会给您带来意想不到的效果。考虑这种情况if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);
富兰克林·于

Answers:


65

你不能

地图的键可以是任何东西,包括对象。但是JSON语法仅允许将字符串作为键。因此,在一般情况下是不可能的。

我的键保证是字符串,而我的值将永远是列表

在这种情况下,可以使用普通对象。它具有以下优点:

  • 它将能够被字符串化为JSON。
  • 它将在较旧的浏览器上运行。
  • 可能会更快。

31
对于好奇的最新Chrome,任何地图都会序列化为“ {}”
Capaj

我在这里解释了我说“你不能”的确切含义。
Oriol

7
“可能会更快”-您有任何消息来源吗?我正在想象一个简单的哈希图必须比一个完整的对象快,但是我没有证据。:)
Lilleman

1
@Xplouder该测试使用昂贵的hasOwnProperty。否则,Firefox会比地图更快地迭代对象。不过,Chrome上的地图仍然更快。jsperf.com/es6-map-vs-object-properties/95
Oriol

1
没错,Firefox 45v似乎比Chrome + 49v更快地迭代对象。但是,地图仍然胜过Chrome中的对象。
Xplouder

69

您不能直接对Map实例进行字符串化,因为它没有任何属性,但是可以将其转换为元组数组:

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

反之,使用

map = new Map(JSON.parse(jsonText));

6
这不会转换为JSON对象,而是转换为数组数组。不一样的东西。有关更完整的答案,请参见下面的Evan Carroll的答案。
星期六星期六

3
@SatThiru元组数组是Maps 的惯用表示形式,它与构造函数和迭代器配合得很好。同样,它是具有非字符串键的唯一明智的地图表示形式,并且对象在那里不起作用。
Bergi

Bergi,请注意OP表示“我的键一定是字符串”。
星期六星期六


如果key对象是对象,则@Bergi Stringify不起作用,例如"{"[object Object]":{"b":2}}"-对象键是地图的主要功能之一
Drenai

58

双方JSON.stringifyJSON.parse支持第二个参数。replacerreviver分别。使用下面的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
这是迄今为止最好的回应
Manuel Vera Silvestre,

1
绝对是这里最好的答案。
JavaRunner

15

尽管ecmascript还没有提供任何方法,但是JSON.stingify如果将其映射Map到JavaScript原语,仍然可以使用此方法。这是Map我们将使用的示例。

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

转到JavaScript对象

您可以使用以下帮助函数将其转换为JavaScript Object文字。

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"]]'

13

使用扩展语法, Map可以在一行中序列化:

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

并反序列化:

let map = new Map(JSON.parse(map));

这将适用于一维地图,但不适用于n维地图。
mattsven

6

字符串化Map实例(对象可以通过键确定)

JSON.stringify([...map])

要么

JSON.stringify(Array.from(map))

要么

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

输出格式:

// [["key1","value1"],["key2","value2"]]

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)会感到满意...
vasia

1
他们不是必须的,但这是一种更可靠的方式。具体来说,这符合SOLID的LSP和OCP原则。也就是说,本机Map正在扩展而不是被修改,并且仍然可以对本机Map使用Liskov替代(LSP)。诚然,它比许多新手或坚定的函数式编程人员更喜欢OOP,但至少它是在基本软件设计原则的可靠基准上受困的。如果要实现SOLID的接口隔离原则(ISP),则可以有一个小的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最初是a,Map并且(更糟的是)每个子映射(其中包含)也是原始图。否则,就无法确定an array of pairs并非仅用于此目的,而不能确定是Map。对象和数组的层次结构在解析时不承担此负担。任何适当的序列化Map都会明确表明它是一个Map
Lonnie Best,

更多关于这里
Lonnie Best,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.