你如何JSON.stringify ES6地图?

ryn*_*nop 71 javascript json dictionary ecmascript-6

我想开始使用ES6 Map而不是JS对象,但我被阻止了,因为我无法弄清楚如何使用JSON.stringify()一个Map.我的密钥保证是字符串,我的值将始终是列表.我是否真的必须编写一个包装器方法来序列化?

Ori*_*iol 46

你不能.

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

我的密钥保证是字符串,我的值将始终是列表

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

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

  • 对于好奇的 - 在最新的chrome中,任何地图都会序列化为"{}" (14认同)
  • "它可能会更快" - 你有任何消息来源吗?我想象一个简单的哈希映射必须比一个完整的对象快,但我没有证据.:) (7认同)
  • 只是路过并通过这个找出我的问题。有时我真的很想搬到农场,把这一切抛在脑后。 (5认同)
  • @Xplouder 该测试使用昂贵的“hasOwnProperty”。如果没有这个,Firefox 迭代对象的速度就会比地图快得多。不过,Chrome 上的地图速度仍然更快。http://jsperf.com/es6-map-vs-object-properties/95 (3认同)
  • 虽然这个答案明确指出了棘手的部分,但它肯定不是“不可能”,正如公认的答案所表明的那样。 (3认同)
  • 确实,Firefox 45v 迭代对象的速度似乎比 Chrome +49v 更快。然而,与 Chrome 中的对象相比,地图仍然胜出。 (2认同)

Ber*_*rgi 33

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

jsonText = JSON.stringify(Array.from(map.entries()));
Run Code Online (Sandbox Code Playgroud)

反之,请使用

map = new Map(JSON.parse(jsonText));
Run Code Online (Sandbox Code Playgroud)

  • @Drenai然后不要使用`Obect.fromEntries`,并使用我的主要答案中的代码而不是评论中的代码。构建对象文字的代码是对 Sat Thiru 的回应,他给出了键是字符串的情况。 (6认同)
  • @SatThiru在这种情况下,请使用[`JSON.stringify(Object.fromEntries(map.entries()))`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects / Object / fromEntries)和[`New Map(Object.entries(JSON.parse(jsonText)))`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/对象/条目) (4认同)
  • 这不会转换为JSON对象,而是转换为数组数组。不一样的东西。有关更完整的答案,请参见下面的Evan Carroll的答案。 (2认同)
  • @SatThiru元组数组是Map的惯用表示,它与构造函数和迭代器配合得很好。这也是具有非字符串键的唯一明智的地图表示形式,并且对象将无法在其中运行。 (2认同)

met*_*bic 25

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

JSON.stringify([...new Map()]);
Run Code Online (Sandbox Code Playgroud)

并将其反序列化:

let map = new Map(JSON.parse(map));
Run Code Online (Sandbox Code Playgroud)

  • 这适用于一维映射,但不适用于 n 维映射。 (3认同)

Paw*_*wel 18

双方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;
  }
}
Run Code Online (Sandbox Code Playgroud)
function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}
Run Code Online (Sandbox Code Playgroud)

用法

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

结合使用数组,对象和贴图的深度嵌套

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);
Run Code Online (Sandbox Code Playgroud)

  • 绝对是这里最好的答案。 (3认同)
  • 只是将其标记为正确。虽然我不喜欢这样的事实:您必须使用非标准化的“数据类型”来“弄脏”网络上的数据,但我想不出更干净的方法。谢谢。 (3认同)
  • @mkoe 当然,但是这种可能性介于被闪电击中和躲在地下室时被闪电击中之间 (3认同)
  • 这是迄今为止最好的回应 (2认同)
  • @Pawel 使用“this[key]”而不是“value”的原因是什么? (2认同)
  • 对我来说,似乎有一个小问题:任何普通对象 o 偶然具有属性 o.dataType==='Map' ,当您序列化-反序列化它时,它也会被转换为 Map 。 (2认同)
  • @Stefnotch我同意可能有一个地方可以让这个溜走。即一个内部公司 npm 包,它是 api 的包装器,用于传递 auth 标头的一些默认选项并自动解析 JSON 以方便使用,即 `const getUrl = (url) => fetch(url, apiOptions).then(res => res .json())` 其中 `apiOptions` 有一些全局设置。值得记住的是,这种情况可能会发生,并且必须正确适应这些情况 (2认同)

小智 12

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

JSON.stringify(Object.fromEntries(map));
Run Code Online (Sandbox Code Playgroud)

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

const myMap = new Map([
     ["key1", "value1"],
     ["key2", "value2"],
     ["key3", "value3"]
]);
Run Code Online (Sandbox Code Playgroud)

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

Object.fromEntries(myMap);

/*
{
     key1: "value1",
     key2: "value2",
     key3: "value3"
}
*/
Run Code Online (Sandbox Code Playgroud)

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


Ste*_*tch 12

正确的往返序列化

只需复制并使用它即可。或者使用npm 包

const serialize = (value) => JSON.stringify(value, stringifyReplacer);
const deserialize = (text) => JSON.parse(text, parseReviver);

// License: CC0
function stringifyReplacer(key, value) {
  if (typeof value === "object" && value !== null) {
    if (value instanceof Map) {
      return {
        _meta: { type: "map" },
        value: Array.from(value.entries()),
      };
    } else if (value instanceof Set) { // bonus feature!
      return {
        _meta: { type: "set" },
        value: Array.from(value.values()),
      };
    } else if ("_meta" in value) {
      // Escape "_meta" properties
      return {
        ...value,
        _meta: {
          type: "escaped-meta",
          value: value["_meta"],
        },
      };
    }
  }
  return value;
}

function parseReviver(key, value) {
  if (typeof value === "object" && value !== null) {
    if ("_meta" in value) {
      if (value._meta.type === "map") {
        return new Map(value.value);
      } else if (value._meta.type === "set") {
        return new Set(value.value);
      } else if (value._meta.type === "escaped-meta") {
        // Un-escape the "_meta" property
        return {
          ...value,
          _meta: value._meta.value,
        };
      } else {
        console.warn("Unexpected meta", value._meta);
      }
    }
  }
  return value;
}
Run Code Online (Sandbox Code Playgroud)

为什么这很难?

应该可以输入任何类型的数据,获取有效的 JSON,并从那里正确地重建输入。

这意味着处理

  • 以对象作为键的映射new Map([ [{cat:1}, "value"] ])。这意味着任何使用的答案都Object.fromEntries可能是错误的。
  • 具有嵌套地图的地图new Map([ ["key", new Map([ ["nested key", "nested value"] ])] ])。很多答案只回答问题而不处理除此之外的任何事情,从而回避了这一点。
  • 混合对象和贴图{"key": new Map([ ["nested key", "nested value"] ]) }

除了这些困难之外,序列化格式必须明确。否则,人们无法总是重建输入。最上面的答案有一个失败的测试用例,请参见下文。

因此,我写了这个改进版本。它使用,_meta来代替dataType, 来减少冲突,并且如果确实发生冲突,它实际上会明确地处理它。希望代码也足够简单,可以轻松扩展以处理其他容器。

然而,我的回答并没有尝试处理极其受诅咒的情况,例如具有对象属性的地图

我的答案的测试用例,演示了一些边缘情况

const originalValue = [
  new Map([['a', {
    b: {
      _meta: { __meta: "cat" },
      c: new Map([['d', 'text']])
    }
  }]]),
 { _meta: { type: "map" }}
];

console.log(originalValue);
let text = JSON.stringify(originalValue, stringifyReplacer);
console.log(text);
console.log(JSON.parse(text, parseReviver));
Run Code Online (Sandbox Code Playgroud)

接受的答案不往返

接受的答案真的很可爱。dataType但是,当将具有属性的对象传递给它时,它不会往返。这可能会使其在某些情况下使用变得危险,例如

  1. JSON.stringify(data, acceptedAnswerReplacer)并通过网络发送。
  2. Naive 网络处理程序会自动对其进行 JSON 解码。从现在开始,您将无法安全地将接受的答案与解码的数据一起使用,因为这样做会导致许多偷偷摸摸的问题。

这个答案使用稍微复杂的方案来解决此类问题。

// Test case for the accepted answer
const originalValue = { dataType: "Map" };
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, str, newValue); 
// > Object { dataType: "Map" } , Map(0)
// Notice how the input was changed into something different
Run Code Online (Sandbox Code Playgroud)


Cod*_*ody 11

更好的解决方案

    // 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);
Run Code Online (Sandbox Code Playgroud)

输出

    > {
        "a": 1,
        "b": {
            "datum": true
        },
        "c": [
            1,
            2,
            3
        ],
        "d": {
            "e": true
        }
    }
Run Code Online (Sandbox Code Playgroud)

希望这比上面的答案更不令人讨厌。

  • 它们不一定是这样,但这是一种更可靠的方法。具体来说,这符合 SOLID 的 LSP 和 OCP 原则。也就是说,原生 Map 正在被扩展,而不是修改,并且仍然可以将里氏替换 (LSP) 与原生 Map 一起使用。诚然,它比许多新手或坚定的函数式编程人员更喜欢的 OOP,但至少它建立在基本软件设计原则的经过验证的真实基线之上。如果你想实现 SOLID 的接口隔离原则 (ISP),你可以有一个小的“IJSONAble”接口(当然使用 TypeScript)。 (2认同)

am0*_*0wa 10

字符串化一个Map实例(对象作为键是可以的)

JSON.stringify([...map])
Run Code Online (Sandbox Code Playgroud)

或者

JSON.stringify(Array.from(map))
Run Code Online (Sandbox Code Playgroud)

或者

JSON.stringify(Array.from(map.entries()))
Run Code Online (Sandbox Code Playgroud)

输出格式:

// [["key1","value1"],["key2","value2"]]
Run Code Online (Sandbox Code Playgroud)


Eva*_*oll 8

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

const map = new Map();
map.set('foo', 'bar');
map.set('baz', 'quz');
Run Code Online (Sandbox Code Playgroud)

转到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"}'
Run Code Online (Sandbox Code Playgroud)

转到对象的JavaScript数组

这个的辅助功能会更加紧凑

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

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

前往数组数组

这甚至更容易,您可以使用

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'
Run Code Online (Sandbox Code Playgroud)


Rak*_*ara 5

非常简单的方法。

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

` 输出:-

{"Key1": "Value1","Key2": "Value2"}

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