Program Tip

localstorage (또는 다른 곳)에서 ES6 맵을 유지하려면 어떻게해야합니까?

programtip 2020. 12. 1. 19:37
반응형

localstorage (또는 다른 곳)에서 ES6 맵을 유지하려면 어떻게해야합니까?


var a = new Map([[ 'a', 1 ]]);
a.get('a') // 1

var forStorageSomewhere = JSON.stringify(a);
// Store, in my case, in localStorage.

// Later:
var a = JSON.parse(forStorageSomewhere);
a.get('a') // TypeError: undefined is not a function

불행히도 JSON.stringify(a);단순히 '{}'를 반환합니다. 이는 a가 복원 될 때 빈 개체가됨을 의미합니다.

나는 지도와 일반 객체 사이의 업 / 다운 캐스팅을 허용하는 es6-mapify찾았 으므로 하나의 해결책이 될 수 있지만 단순히 내지도를 유지하기 위해 외부 종속성에 의존해야 할 필요가 있기를 바랐습니다.


키와 값이 모두 직렬화 가능하다고 가정하고,

localStorage.myMap = JSON.stringify(Array.from(map.entries()));

작동해야합니다. 반대의 경우

map = new Map(JSON.parse(localStorage.myMap));

일반적으로 직렬화는이 속성이

deserialize(serialize(data)).get(key) ≈ data.get(key)

a ≈ b로 정의 될 수있는 serialize(a) === serialize(b).

이는 객체를 JSON으로 직렬화 할 때 충족됩니다.

var obj1 = {foo: [1,2]},
    obj2 = JSON.parse(JSON.stringify(obj1));
obj1.foo; // [1,2]
obj2.foo; // [1,2] :)
JSON.stringify(obj1.foo) === JSON.stringify(obj2.foo); // true :)

속성은 문자열로만 무손실 직렬화 될 수 있기 때문에 작동합니다.

그러나 ES6 맵은 임의의 값을 키로 허용합니다. 객체는 데이터가 아니라 참조로 고유하게 식별되기 때문에 문제가됩니다. 그리고 객체를 직렬화 할 때 참조를 잃게됩니다.

var key = {},
    map1 = new Map([ [1,2], [key,3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()])));
map1.get(1); // 2
map2.get(1); // 2 :)
map1.get(key); // 3
map2.get(key); // undefined :(

그래서 나는 일반적으로 유용한 방법 으로 그것을 할 수 없다고 말하고 싶습니다 .

그리고 그것이 작동하는 경우에는 아마도 map 대신 일반 객체를 사용할있습니다 . 또한 다음과 같은 이점이 있습니다.

  • 주요 정보를 잃지 않고 JSON으로 문자열화할 수 있습니다.
  • 이전 브라우저에서 작동합니다.
  • 더 빠를 수 있습니다.

Oriol의 답변을 바탕 으로 조금 더 잘할 수 있습니다. 기본 루트 또는 맵 입구가 있고 각 오브젝트 키가 해당 루트 키에서 전 이적으로 발견 될 수있는 한 키에 대한 오브젝트 참조를 계속 사용할 수 있습니다.

Douglas Crockford의 JSON.decycle 및 JSON.retrocycle 을 사용하도록 Oriol의 예제를 수정 하면이 경우를 처리하는 맵을 만들 수 있습니다.

var key = {},
    map1 = new Map([ [1, key], [key, 3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()]))),
    map3 = new Map(JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle([...map1.entries()])))));
map1.get(1); // key
map2.get(1); // key
map3.get(1); // key
map1.get(map1.get(1)); // 3 :)
map2.get(map2.get(1)); // undefined :(
map3.get(map3.get(1)); // 3 :)

Decycle 및 Retrocycle을 사용하면 주기적 구조와 dag를 JSON으로 인코딩 할 수 있습니다. 이는 객체 자체에 추가 속성을 생성하지 않고 객체 간의 관계를 구축하거나 ES6 맵을 사용하여 기본 요소를 객체에 상호 교환 적으로 연관시키려는 경우 유용합니다.

한 가지 함정은 새 맵에 원래 키 객체를 사용할 수 없다는map3.get(key); 것입니다 ( undefined 반환). 그러나 원래 키 참조를 보유하고 있지만 새로 구문 분석 된 JSON 맵은 가능성이 거의없는 케이스처럼 보입니다.


호루라기로 청소 :

JSON.stringify([...myMap])

다차원지도가있는 경우 수락 된 답변이 실패합니다. Map 객체는 다른 Map 객체를 키 또는 값으로 사용할 수 있다는 점을 항상 염두에 두어야합니다.

따라서이 작업을 처리하는 더 좋고 안전한 방법은 다음과 같습니다.

function arrayifyMap(m){
  return m.constructor === Map ? [...m].map(([v,k]) => [arrayifyMap(v),arrayifyMap(k)])
                               : m;
}

이 도구가 있으면 언제든지 다음과 같이 할 수 있습니다.

localStorage.myMap = JSON.stringify(arrayifyMap(myMap))

당신이 가지고 toJSON()있는 어떤 class객체에 대해 당신 자신의 함수를 구현한다면, 보통의 오래된 JSON.stringify()것만이 작동 할 것입니다!

Maps with Arrays for keys? Map다른 Map값으로? Map정기적으로 내부 Object? 어쩌면 당신 만의 커스텀 클래스 일 수도 있습니다. 쉬운.

Map.prototype.toJSON = function() {
    return Array.from(this.entries());
};

그게 다야! 여기서 프로토 타입 조작이 필요합니다. toJSON()모든 비표준 항목에 수동으로 추가 할 수 있지만 실제로는 JS의 힘을 피하는 것입니다.

데모

test = {
    regular : 'object',
    map     : new Map([
        [['array', 'key'], 7],
        ['stringKey'     , new Map([
            ['innerMap'    , 'supported'],
            ['anotherValue', 8]
        ])]
    ])
};
console.log(JSON.stringify(test));

출력 :

{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}

Map하지만 실제로 돌아가는 역 직렬화 는 자동으로 수행되지 않습니다. 위의 결과 문자열을 사용하여 맵을 다시 만들어 값을 추출합니다.

test2 = JSON.parse(JSON.stringify(test));
console.log((new Map((new Map(test2.map)).get('stringKey'))).get('innerMap'));

출력

"supported"

그것은 약간 지저분하지만 약간의 마법 소스 를 사용하면 deserialization automagic도 만들 수 있습니다 .

Map.prototype.toJSON = function() {
    return ['window.Map', Array.from(this.entries())];
};
Map.fromJSON = function(key, value) {
    return (value instanceof Array && value[0] == 'window.Map') ?
        new Map(value[1]) :
        value
    ;
};

이제 JSON은

{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}

그리고 역 직렬화 및 사용은 Map.fromJSON

test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get('stringKey').get('innerMap'));

출력 ( new Map()사용 되지 않음 )

"supported"

데모


빠진 한 가지는 Map이 ORDERED 구조 라는 것입니다. 즉, 입력 된 첫 번째 항목이 반복 될 때 첫 번째 항목이 나열됩니다.

이다 NOT 자바 스크립트 객체처럼. 이 유형의 구조가 필요했기 때문에 (Map을 사용했습니다) JSON.stringify가 작동하지 않는다는 것을 알아 내려면 고통 스럽지만 이해할 수 있습니다.

결국 가장 기본적인 '유형'에 대해서만 JSON.stringify를 사용하여 모든 구문 분석을 의미하는 'value_to_json'함수를 만들었습니다.

불행히도 .toJSON ()으로 MAP를 서브 클래 싱하는 것은 JSON_string이 아닌 값을 제외하고는 작동하지 않습니다. 또한 유산으로 간주됩니다.

내 사용 사례는 예외적입니다.

관련 :

function value_to_json(value) {
  if (value === null) {
    return 'null';
  }
  if (value === undefined) {
    return 'null';
  }
  //DEAL WITH +/- INF at your leisure - null instead..

  const type = typeof value;
  //handle as much as possible taht have no side effects. function could
  //return some MAP / SET -> TODO, but not likely
  if (['string', 'boolean', 'number', 'function'].includes(type)) {
    return JSON.stringify(value)
  } else if (Object.prototype.toString.call(value) === '[object Object]') {
    let parts = [];
    for (let key in value) {
      if (Object.prototype.hasOwnProperty.call(value, key)) {
        parts.push(JSON.stringify(key) + ': ' + value_to_json(value[key]));
      }
    }
    return '{' + parts.join(',') + '}';
  }
  else if (value instanceof Map) {
    let parts_in_order = [];
    value.forEach((entry, key) => {
      if (typeof key === 'string') {
        parts_in_order.push(JSON.stringify(key) + ':' + value_to_json(entry));
      } else {
        console.log('Non String KEYS in MAP not directly supported');
      }
      //FOR OTHER KEY TYPES ADD CUSTOM... 'Key' encoding...
    });
    return '{' + parts_in_order.join(',') + '}';
  } else if (typeof value[Symbol.iterator] !== "undefined") {
    //Other iterables like SET (also in ORDER)
    let parts = [];
    for (let entry of value) {
      parts.push(value_to_json(entry))
    }
    return '[' + parts.join(',') + ']';
  } else {
    return JSON.stringify(value)
  }
}


let m = new Map();
m.set('first', 'first_value');
m.set('second', 'second_value');
let m2 = new Map();
m2.set('nested', 'nested_value');
m.set('sub_map', m2);
let map_in_array = new Map();
map_in_array.set('key', 'value');
let set1 = new Set(["1", 2, 3.0, 4]);

m2.set('array_here', [map_in_array, "Hello", true, 0.1, null, undefined, Number.POSITIVE_INFINITY, {
  "a": 4
}]);
m2.set('a set: ', set1);
const test = {
  "hello": "ok",
  "map": m
};

console.log(value_to_json(test));


// store
const mapObj = new Map([['a', 1]]);
localStorage.a = JSON.stringify(mapObj, replacer);

// retrieve
const newMapObj = JSON.parse(localStorage.a, reviver);

// required replacer and reviver functions
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;
}

여기에 대체자 및 리바이 버 기능에 대한 설명을 썼습니다 https://stackoverflow.com/a/56150320/696535

이 코드는 일반 JSON.stringify와 같은 다른 값에 대해 작동하므로 직렬화 된 객체가 맵이어야한다는 가정이 없습니다. 또한 배열이나 객체에 깊이 중첩 된 Map 일 수도 있습니다.

참고 URL : https://stackoverflow.com/questions/28918232/how-do-i-persist-a-es6-map-in-localstorage-or-elsewhere

반응형