你如何 JSON.stringify ES6 Map?

Posted

技术标签:

【中文标题】你如何 JSON.stringify ES6 Map?【英文标题】:How do you JSON.stringify an ES6 Map? 【发布时间】:2015-05-19 01:28:33 【问题描述】:

我想开始使用 ES6 Map 而不是 JS 对象,但我被阻止了,因为我不知道如何使用 JSON.stringify()Map。我的键保证是字符串,我的值将始终被列出。我真的要写一个包装方法来序列化吗?

【问题讨论】:

关于2ality.com/2015/08/es6-map-json.html主题的有趣文章 我能够让它工作。结果在 Plunkr 上embed.plnkr.co/oNlQQBDyJUiIQlgWUPVP。该解决方案使用 JSON.stringify(obj, replacerFunction) 检查是否正在传递 Map 对象并将 Map 对象转换为 javascript 对象(即 JSON.stringify)然后将转换为字符串。 如果你的键被保证是字符串(或数字)并且你的值是arrays,你可以做类似[...someMap.entries()].join(';');对于更复杂的事情,你可以尝试类似的事情,比如[...someMap.entries()].reduce((acc, cur) => acc + `$cur[0]:$/* do something to stringify cur[1] */ `, '') @Oriol 如果键名可能与默认属性相同怎么办? obj[key] 可能会给你带来意想不到的东西。考虑案例if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList); 【参考方案1】:

你不能。

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

我的键保证是字符串,我的值总是列表

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

它将能够被字符串化为 JSON。 它适用于旧版浏览器。 可能会更快。

【讨论】:

对于最新的 chrome 中的好奇者,任何地图都会序列化为 '' “可能更快” - 你有这方面的资料吗?我在想象一个简单的哈希映射必须比一个完整的对象更快,但我没有证据。 :) @Xplouder 该测试使用昂贵的hasOwnProperty。没有它,Firefox 迭代对象的速度比地图快得多。不过,Chrome 上的地图仍然更快。 jsperf.com/es6-map-vs-object-properties/95 没错,Firefox 45v 迭代对象的速度似乎比 Chrome +49v 快。然而,地图仍然胜过 Chrome 中的对象。 只是路过并找出我的问题多亏了这个。有时候,我真的很想搬到农场,把这一切抛在脑后。【参考方案2】:

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

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

反之,使用

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

【讨论】:

这不会转换为 JSON 对象,而是转换为数组的 Array。不是一回事。请参阅下面 Evan Carroll 的答案以获得更完整的答案。 @SatThiru 元组数组是Maps 的惯用表示,它与构造函数和迭代器配合得很好。它也是具有非字符串键的映射的唯一合理表示,并且对象不会在那里工作。 Bergi,请注意 OP 说“我的密钥保证是字符串”。 @SatThiru 在这种情况下,使用JSON.stringify(Object.fromEntries(map.entries()))new Map(Object.entries(JSON.parse(jsonText))) @Drenai 然后不要使用Obect.fromEntries,并使用我的主要答案中的代码而不是评论中的代码。构建对象文字的代码是对 Sat Thiru 的回应,他给出了键是字符串的情况。【参考方案3】:

虽然 ecmascript 还没有提供方法,但如果您将 Map 映射到 JavaScript 原语,这仍然可以使用 JSON.stingify 完成。这是我们将使用的示例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"]]'

【讨论】:

> 转到一个 JavaScript 对象 __proto__ 之类的键吗?或者您可以通过尝试序列化这样的地图来破坏整个环境。我相信,Alok 的反应不会因此受到影响。【参考方案4】:

JSON.stringifyJSON.parse 都支持第二个参数。 replacerreviver 分别。使用下面的替换器和恢复器,可以添加对原生 Map 对象的支持,包括深度嵌套的值

function replacer(key, value) 
  if(value instanceof Map) 
    return 
      dataType: 'Map',
      value: Array.from(value.entries()), // or with spread: value: [...value]
    ;
   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);

【讨论】:

刚刚将此标记为正确。虽然我不喜欢你必须用非标准化的dataType 来“弄脏”网络上的数据,但我想不出更干净的方法。谢谢。 @rynop 是的,它更像是一个有自己的数据存储格式的小程序,而不仅仅是使用纯原生功能 @Pawel 使用this[key] 而不是value 的原因是什么? @JimiDini 好点,已更新。现在,如果有人想将这些声明为箭头函数,它不会与范围混淆 对我来说似乎有一个小问题:任何偶然具有属性 o.dataType==='Map' 的普通对象 o 也将在您对其进行序列化-反序列化时转换为 Map .【参考方案5】:

即使您有嵌套地图,以下解决方案也可以使用

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 更多关于 here.【参考方案6】:

使用spread sytax Map 可以在一行内序列化:

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

并反序列化它:

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

【讨论】:

这适用于一维地图,但不适用于 n 维地图。【参考方案7】:

更好的解决方案

    // 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... 他们不必如此,但这是一种更可靠的方式。具体来说,这符合 SOLID 的 LSP 和 OCP 原则。也就是说,原生 Map 正在扩展,而不是修改,并且仍然可以将 Liskov Substitution (LSP) 与原生 Map 一起使用。诚然,它比许多新手或坚定的函数式编程人更喜欢的面向对象编程,但至少它受到基本软件设计原则的久经考验的真实基线的困扰。如果你想实现 SOLID 的接口隔离原则(ISP),你可以有一个小的 IJSONAble 接口(当然是使用 TypeScript)。【参考方案8】:

Stringify 一个Map 实例(对象作为键是可以的)

JSON.stringify([...map])

JSON.stringify(Array.from(map))

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

输出格式:

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

【讨论】:

【参考方案9】:

鉴于您的示例是一个简单的用例,其中键将是简单类型,我认为这是 JSON 字符串化 Map 的最简单方法。

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

我认为 Map 的底层数据结构的方式是作为键值对数组(作为数组本身)。所以,是这样的:

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

因为我们在 Object.entries 中找到了底层数据结构,所以我们可以像在数组上一样在 Map 上使用 Object.fromEntries() 的原生 JavaScript 方法:

Object.fromEntries(myMap);

/*

     key1: "value1",
     key2: "value2",
     key3: "value3"

*/

然后你剩下的就是在结果上使用 JSON.stringify()。

【讨论】:

这个不错,但需要你以 E​​S2019 为目标。【参考方案10】:

只想分享我的 Map 和 Set JSON.stringify 版本。 我正在整理它们,对调试很有用...

function replacer(key, value) 
    if (value instanceof Map) 
        const reducer = (obj, mapKey) => 
            obj[mapKey] = value.get(mapKey);
            return obj;
        ;
        return [...value.keys()].sort().reduce(reducer, );
     else if (value instanceof Set) 
        return [...value].sort();
    
    return value;

用法:

const map = new Map();
const numbers= new Set()
numbers.add(3);
numbers.add(2);
numbers.add(3);
numbers.add(1);
const chars= new Set()
chars.add('b')
chars.add('a')
chars.add('a')
map.set("numbers",numbers)
map.set("chars",chars)

console.log(JSON.stringify(map, replacer, 2));

结果:


  "chars": [
    "a",
    "b"
  ],
  "numbers": [
    1,
    2,
    3
  ]

【讨论】:

【参考方案11】:

虽然在某些情况下,如果您是地图的创建者,您会在单独的“src”文件中编写代码并将副本另存为 .txt 文件,如果编写得足够简洁,则可以轻松阅读输入、解密并添加到服务器端。

然后,新文件将保存为 .js 并从服务器发回对它的引用。一旦作为 JS 读回,该文件就会完美地重建自身。美妙之处在于重构不需要 hacky 迭代或解析。

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。 这听起来像是一个不错的 10,000 英尺流程概述,但实际实现会更有用。 嗯,这确实比一个完整的解决方案更值得深思。抱歉,我是新来的,还不知道如何将我的代码添加到 cmets。 我不明白,为什么这与问题有关?【参考方案12】:

非常简单的方法。

  const map = new Map();
  map.set('Key1', "Value1");
  map.set('Key2', "Value2");
  console.log(Object.fromEntries(map));

` 输出:-

"Key1": "Value1","Key2": "Value2"

【讨论】:

警告:地图可以有非字符串值作为键。如果您的 Map 键本身是不可字符串化的类型,这将不起作用:JSON.stringify(Object.fromEntries(new Map([['s', 'r'],[s:3,'g']]))) 变为 '"s":"r","[object Object]":"g"'

以上是关于你如何 JSON.stringify ES6 Map?的主要内容,如果未能解决你的问题,请参考以下文章

如何在本地存储(或其他地方)中保存 ES6 地图?

跪求json无法解析的原因 String resJSON=JSON.toJSONString(ma

深浅拷贝

如何 JSON.stringify 对象数组

浅谈JS中的JSON.stringify() 和 JSON.parse()

JSON.stringify()还可以这么用