Làm cách nào để bạn JSON.stringify một Bản đồ ES6?


102

Tôi muốn bắt đầu sử dụng Bản đồ ES6 thay vì các đối tượng JS nhưng tôi đang bị cản trở vì tôi không thể tìm ra cách JSON.stringify () một Bản đồ. Khóa của tôi được đảm bảo là chuỗi và giá trị của tôi sẽ luôn được liệt kê. Tôi có thực sự phải viết một phương thức wrapper để tuần tự hóa không?


1
bài viết thú vị về chủ đề 2ality.com/2015/08/es6-map-json.html
David Chase

Tôi đã có thể làm cho nó hoạt động. Kết quả có trên Plunkr tại nhúng.plnkr.co/oNlQQBDyJUiIQlgWUPVP . Giải pháp sử dụng JSON.stringify (obj, ReplaceerFunction) để kiểm tra xem liệu một đối tượng Bản đồ có đang được truyền hay không và chuyển đổi đối tượng Bản đồ thành đối tượng Javascript (JSON.stringify đó) sau đó sẽ chuyển đổi thành một chuỗi.
PatS

1
Nếu các khóa của bạn được đảm bảo là các chuỗi (hoặc số) và các mảng giá trị của bạn , bạn có thể làm điều gì đó như [...someMap.entries()].join(';'); cho một cái gì đó phức tạp hơn bạn có thể thử một cái gì đó tương tự như sử dụng giống như[...someMap.entries()].reduce((acc, cur) => acc + `${cur[0]}:${/* do something to stringify cur[1] */ }`, '')
user56reinstatemonica8

@Oriol Điều gì xảy ra nếu có thể tên khóa giống với thuộc tính mặc định? obj[key]có thể mang lại cho bạn điều gì đó bất ngờ. Hãy xem xét trường hợp if (!obj[key]) obj[key] = newList; else obj[key].mergeWith(newList);.
Franklin Yu

Câu trả lời:


65

Bạn không thể.

Các khóa của bản đồ có thể là bất kỳ thứ gì, kể cả các đối tượng. Nhưng cú pháp JSON chỉ cho phép các chuỗi làm khóa. Vì vậy, nó không thể trong một trường hợp chung.

Các khóa của tôi được đảm bảo là chuỗi và giá trị của tôi sẽ luôn là danh sách

Trong trường hợp này, bạn có thể sử dụng một đối tượng đơn giản. Nó sẽ có những ưu điểm sau:

  • Nó sẽ có thể được chuỗi thành JSON.
  • Nó sẽ hoạt động trên các trình duyệt cũ hơn.
  • Nó có thể nhanh hơn.

30
đối với những người tò mò trong bản chrome mới nhất, bất kỳ bản đồ nào cũng được sắp xếp thành '{}'
Capaj

Tôi đã giải thích ở đây chính xác ý tôi là gì khi tôi nói "bạn không thể".
Oriol

7
"Nó có thể nhanh hơn" - Bạn có nguồn nào về điều đó không? Tôi đang tưởng tượng một bản đồ băm đơn giản phải nhanh hơn một vật thể được thổi đầy đủ, nhưng tôi không có bằng chứng. :)
Lilleman

1
@Xplouder Thử nghiệm đó sử dụng đắt tiền hasOwnProperty. Nếu không có điều đó, Firefox lặp lại các đối tượng nhanh hơn nhiều so với bản đồ. Tuy nhiên, Maps vẫn nhanh hơn trên Chrome. jsperf.com/es6-map-vs-object-properties/95
Oriol

1
Đúng, có vẻ như Firefox 45v lặp lại các đối tượng nhanh hơn so với Chrome + 49v. Tuy nhiên, Maps vẫn thắng đối tượng trong Chrome.
Xplouder

68

Bạn không thể trực tiếp xâu chuỗi Mapđối tượng vì nó không có bất kỳ thuộc tính nào, nhưng bạn có thể chuyển đổi nó thành một mảng các bộ giá trị:

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

Ngược lại, sử dụng

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

6
Điều này không chuyển đổi thành một đối tượng JSON, mà thay vào đó là một Mảng các mảng. Không giống nhau. Xem câu trả lời của Evan Carroll dưới đây để có câu trả lời đầy đủ hơn.
Thứ bảy,

3
@SatThiru Một mảng các bộ giá trị là đại diện thông thường của Maps, nó hoạt động tốt với hàm tạo và trình lặp. Ngoài ra, đây là cách thể hiện hợp lý duy nhất của bản đồ có các khóa không phải chuỗi và đối tượng sẽ không hoạt động ở đó.
Bergi

Bergi, xin lưu ý rằng OP đã nói "Các khóa của tôi được đảm bảo là dây".
Thứ bảy,


@Bergi Stringify không hoạt động nếu đó keylà một đối tượng, ví dụ "{"[object Object]":{"b":2}}"- các khóa đối tượng là một trong những tính năng chính của Maps
Drenai

58

Cả hai JSON.stringifyJSON.parsehỗ trợ một lập luận thứ hai. replacerrevivertương ứng. Với trình thay thế và trình phục hồi bên dưới, bạn có thể thêm hỗ trợ cho đối tượng Bản đồ gốc, bao gồm các giá trị lồng nhau sâu sắc

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

Cách sử dụng :

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

Làm tổ sâu với sự kết hợp của Mảng, Đối tượng và Bản đồ

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
Đây là câu trả lời tốt nhất cho đến nay
Manuel Vera Silvestre

1
Chắc chắn câu trả lời tốt nhất ở đây.
JavaRunner

15

Mặc dù chưa có phương pháp nào được cung cấp bởi ecmascript, điều này vẫn có thể được thực hiện bằng cách sử dụng JSON.stingifynếu bạn ánh xạ Maptới một JavaScript nguyên thủy. Đây là mẫu Mapchúng tôi sẽ sử dụng.

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

Chuyển đến một đối tượng JavaScript

Bạn có thể chuyển đổi sang JavaScript Object theo nghĩa đen bằng hàm trợ giúp sau.

const mapToObj = m => {
  return Array.from(m).reduce((obj, [key, value]) => {
    obj[key] = value;
    return obj;
  }, {});
};

JSON.stringify(mapToObj(map)); // '{"foo":"bar","baz":"quz"}'

Chuyển đến một Mảng đối tượng JavaScript

Chức năng trợ giúp cho chức năng này sẽ còn nhỏ gọn hơn

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

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

Đi đến Mảng của Mảng

Điều này thậm chí còn dễ dàng hơn, bạn chỉ có thể sử dụng

JSON.stringify( Array.from(map) ); // '[["foo","bar"],["baz","quz"]]'

13

Sử dụng bản đồ sytax trải rộng có thể được tuần tự hóa trong một dòng:

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

và deserialize nó bằng:

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

Điều này sẽ hoạt động đối với Bản đồ một chiều, nhưng không hoạt động đối với bản đồ n chiều.
mattsven

6

Xâu chuỗi một Mapthể hiện (các đối tượng làm khóa là OK) :

JSON.stringify([...map])

hoặc là

JSON.stringify(Array.from(map))

hoặc là

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

định dạng đầu ra:

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

4

Một giải pháp tốt hơn

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

Đầu ra

    > {
        "a": 1,
        "b": {
            "datum": true
        },
        "c": [
            1,
            2,
            3
        ],
        "d": {
            "e": true
        }
    }

Hy vọng rằng ít rắc rối hơn các câu trả lời ở trên.


Tôi không chắc rằng nhiều người sẽ hài lòng với việc mở rộng lớp bản đồ lõi chỉ để tuần tự hóa nó thành một json ...
vasia

1
Họ không cần phải như vậy, nhưng đó là một cách làm RẮN hơn. Cụ thể, điều này phù hợp với các nguyên tắc LSP và OCP của SOLID. Tức là, Bản đồ gốc đang được mở rộng, không được sửa đổi và người ta vẫn có thể sử dụng Thay thế Liskov (LSP) với Bản đồ gốc. Đúng vậy, nó OOP hơn nhiều người mới làm quen hoặc những người trung thành trong Lập trình chức năng thích, nhưng ít nhất nó được thiết lập dựa trên một đường cơ sở đã thử và đúng về các nguyên tắc thiết kế phần mềm cơ bản. Nếu bạn muốn triển khai Nguyên tắc phân tách giao diện (ISP) của SOLID, bạn có thể có một IJSONAblegiao diện nhỏ (tất nhiên là sử dụng TypeScript).
Cody

3

Giải pháp bên dưới hoạt động ngay cả khi bạn có Bản đồ lồng nhau

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

Không cần kiểm tra câu trả lời của bạn, tôi đã đánh giá cao cách nó thu hút sự chú ý đến vấn đề của Bản đồ lồng nhau. Ngay cả khi bạn chuyển đổi thành công điều này thành JSON, bất kỳ phân tích cú pháp nào được thực hiện trong tương lai đều phải có nhận thức rõ ràng rằng JSON ban đầu là một Mapvà (thậm chí tệ hơn) rằng mỗi bản đồ con (nó chứa) cũng là một bản đồ ban đầu. Nếu không, không có cách nào để chắc chắn rằng một array of pairskhông chỉ nhằm mục đích chính xác mà thay vì một Bản đồ. Cấu trúc phân cấp của các đối tượng và mảng không mang gánh nặng này khi được phân tích cú pháp. Bất kỳ tuần tự hóa thích hợp nào của Mapsẽ chỉ ra rõ ràng rằng nó là a Map.
Lonnie Best

Thêm về điều đó ở đây .
Lonnie Best
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.