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


你不能。

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

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

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

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

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

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

反之,使用

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

虽然ecmascript还没有提供方法,但这仍然可以使用JSON来完成。如果将map映射到JavaScript原语,则使用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。stringify和JSON。解析支持第二个参数。替换器和恢复器分别。使用下面的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);
}

使用spread 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])

or

JSON.stringify(Array.from(map))

or

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上使用Object.fromEntries()的原生JavaScript方法,就像在数组上一样:

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

` 输出:

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

正确的往返序列化

复制并使用它。或者使用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;
}

为什么这很难?

它应该能够输入任何类型的数据,获得有效的JSON,并从那里正确地重建输入。

这意味着要处理

以对象为键的映射new Map([[{cat:1}, "value"]])。这意味着任何使用Object.fromEntries的答案都可能是错误的。 有嵌套映射的映射new Map([["key", new Map([["嵌套键","嵌套值"]])]])很多答案都回避了这一点,只回答了问题,而没有处理其他任何问题。 混合对象和映射{"key": new Map([["嵌套键","嵌套值"]])}。

在这些困难之上,序列化格式必须是明确的。否则就不能总是重建输入。顶部的答案有一个失败的测试用例,见下面。

因此,我写了这个改进版。它使用_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));

已接受答案,不往返

公认的答案真的很可爱。但是,当一个带有dataType属性的对象被传递给它时,它不会进行往返。

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

下面的方法将Map转换为JSON字符串:

public static getJSONObj(): string {
    return JSON.stringify(Object.fromEntries(map));
}

例子:

const x = new Map();
x.set("SomeBool", true);
x.set("number1", 1);
x.set("anObj", { name: "joe", age: 22, isAlive: true });

const json = getJSONObj(x);

// Output:
// '{"SomeBool":true,"number1":1,"anObj":{"name":"joe","age":222,"isAlive":true}}'

不能调用JSON。在Map或Set上进行stringify。

您将需要转换:

使用Object. fromentries或 使用展开运算符[…]

在调用JSON.stringify之前

Map

常量 obj = {'Key1': 'Value1', 'Key2': 'Value2'}, map = new map (Object.entries(obj)); 地图。设置(“Key3”、“Value3”);//添加一个新条目 //不显示键值对 console.log(地图:,JSON.stringify(地图)); //显示键值对 console.log(JSON.stringify(Object.fromEntries(map), null, 2)); .as-console-wrapper {top: 0;Max-height: 100%重要;}

Set

常量 arr = ['Value1', 'Value2'], set = new set (arr); set.add(“Value3”);//添加一个新项目 //不显示值 console.log (': ', JSON.stringify(集)); //显示值 console.log (JSON.stringify([…Set], null, 2)); .as-console-wrapper {top: 0;Max-height: 100%重要;}

toJSON方法

如果你想调用JSON。如果在类对象上使用stringify,则需要重写toJSON方法以返回实例数据。

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

我真不知道为什么这里有这么多冗长的笑话。这个简短的版本解决了我的问题:

const data = new Map()
data.set('visible', true)
data.set('child', new Map())
data.get('child').set('visible', false)

const str = JSON.stringify(data, (_, v) => v instanceof Map ? Object.fromEntries(v) : v)
// '{"visible":true,"child":{"visible":false}}'

const recovered = JSON.parse(str, (_, v) => typeof v === 'object' ? new Map(Object.entries(v)) : v)
// Map(2) { 'visible' => true, 'child' => Map(1) { 'visible' => false } }