你如何处理JSON。stringify一个ES6地图?

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

140452 次浏览

你不能。

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

我的键肯定是字符串,我的值总是列表

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

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

你不能直接字符串化Map实例,因为它没有任何属性,但你可以将它转换为一个元组数组:

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

反之,使用

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

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

JSON.stringifyJSON.parse都支持第二个参数。分别是replacerreviver。使用下面的replacer和reviver,可以添加对原生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);

下面的解决方案工作,即使你有嵌套的地图

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

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

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

并反序列化它:

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

更好的解决方案

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

希望这比上面的答案不那么尴尬。

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

JSON.stringify([...map])

JSON.stringify(Array.from(map))

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

输出格式:

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

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

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

我认为Map的底层数据结构是键值对数组(数组本身)。就像这样:

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

因为底层的数据结构就是我们在Object中找到的。条目时,我们可以像在数组上一样在Map上使用本机JavaScript方法Object.fromEntries():

Object.fromEntries(myMap);


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

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

只是想分享我的版本的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
]
}

尽管在某些情况下,如果你是地图的创建者,你会在一个单独的“src”文件中编写代码,并将副本保存为.txt文件,如果写得足够简洁,可以很容易地读取、破译并添加到服务器端。

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

非常简单的方法。

  const map = new Map();
map.set('Key1', "Value1");
map.set('Key2', "Value2");
console.log(Object.fromEntries(map));
< p > ' 输出:- < / p >

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

公认的答案真的很可爱。然而,它不能处理具有dataType属性的对象被正确传递给函数的边缘情况。

const originalValue = { dataType: "Map" }; // Edge case input
const str = JSON.stringify(originalValue, replacer);
const newValue = JSON.parse(str, reviver);
console.log(originalValue, str, newValue);
// bug: newValue ends up being a Map

改进的变异

因此,我为自己的目的编写了这个改进版本。它使用_meta而不是dataType来减少冲突,如果冲突确实发生,它实际上会处理它。

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

最后,一个小的测试用例,试图尽可能多地打破。

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

我这样做

public static map: Map<string, boolean> = new Map<string, boolean>();

//然后创建方法

public static getJSONObj():any{
const json = JSON.stringify(Object.fromEntries(map));
return json;
}

//并使用此方法

你不能在MapSet上调用JSON.stringify

您将需要转换:

  • Map转换为原语Object,使用Object.fromEntries,或者
  • Set转换为原语Array,使用展开运算符[...]

在调用JSON.stringify之前

地图

const
obj = { 'Key1': 'Value1', 'Key2': 'Value2' },
map = new Map(Object.entries(obj));


map.set('Key3', 'Value3'); // Add a new entry


// Does NOT show the key-value pairs
console.log('Map:', JSON.stringify(map));


// Shows the key-value pairs
console.log(JSON.stringify(Object.fromEntries(map), null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }

Set

const
arr = ['Value1', 'Value2'],
set = new Set(arr);


set.add('Value3'); // Add a new item


// Does NOT show the values
console.log('Set:', JSON.stringify(set));


// Show the values
console.log(JSON.stringify([...set], null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }

toJSON method

If you want to call JSON.stringify on a class object, you will need to override the toJSON method to return your instance data.

class Cat {
constructor(options = {}) {
this.name = options.name ?? '';
this.age = options.age ?? 0;
}
toString() {
return `[Cat name="${this.name}", age="${this.age}"]`
}
toJSON() {
return { name: this.name, age: this.age };
}
static fromObject(obj) {
const { name, age } = obj ?? {};
return new Cat({ name, age });
}
}


/*
* JSON Set adds the missing methods:
* - toJSON
* - toString
*/
class JSONSet extends Set {
constructor(values) {
super(values)
}
toString() {
return super
.toString()
.replace(']', ` ${[...this].map(v => v.toString())
.join(', ')}]`);
}
toJSON() {
return [...this];
}
}


const cats = new JSONSet([
Cat.fromObject({ name: 'Furball', age: 2 }),
Cat.fromObject({ name: 'Artemis', age: 5 })
]);


console.log(cats.toString());
console.log(JSON.stringify(cats, null, 2));
.as-console-wrapper { top: 0; max-height: 100% !important; }