Add remotedev-serialize package (#562)

* Add remotedev-serialize package

* Update
This commit is contained in:
Nathan Bierema 2020-08-06 16:39:05 -04:00 committed by GitHub
parent ebdf32661e
commit ad281ba07a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 541 additions and 19 deletions

View File

@ -0,0 +1,115 @@
# Serialize ImmutableJS data
### Installation
```
yarn add remotedev-serialize
```
### Usage with ImmutableJS data structures
Just pass the Immutable library to our class:
```js
import Immutable from 'immutable';
import Serialize from 'remotedev-serialize';
const { stringify, parse } = Serialize.immutable(Immutable);
const data = Immutable.fromJS({ foo: 'bar', baz: { qux: 42 } });
const serialized = stringify(data);
console.log(serialized);
// {"data":{"foo":"bar","baz":{"data":{"qux":42},"__serializedType__":"ImmutableMap"}},"__serializedType__":"ImmutableMap"}
const parsed = parse(serialized);
console.log(Immutable.is(parsed, data));
// true
```
See [the tests](https://github.com/reduxjs/remote-devtools/blob/master/packages/remotedev-serialize/test/immutable.spec.js) for more examples of usage.
### Usage with ImmutableJS Record classes
To parse a Record class back, you need to specify a reference to it:
```js
import Immutable from 'immutable';
import Serialize from 'remotedev-serialize';
const ABRecord = Immutable.Record({ a: 1, b: 2 });
const { stringify, parse } = Serialize.immutable(Immutable, [ABRecord]);
const myRecord = new ABRecord({ b: 3 });
const serialized = stringify(myRecord);
console.log(serialized);
// {"data":{"a":1,"b":3},"__serializedType__":"ImmutableRecord","__serializedRef__":0}
const parsed = parse(serialized);
console.log(Immutable.is(parsed, myRecord));
// true
```
### Passing custom serialization functions
You can pass custom replacer and reviver functions to Serialize:
```js
import Immutable from 'immutable';
import Serialize from 'remotedev-serialize';
function customReplacer(key, value, defaultReplacer) {
if (value === 1) {
return { data: 'one', __serializedType__: 'number' };
}
return defaultReplacer(key, value);
}
function customReviver(key, value, defaultReviver) {
if (
typeof value === 'object' &&
value.__serializedType__ === 'number' &&
value.data === 'one'
) {
return 1;
}
return defaultReviver(key, value);
}
const { stringify, parse } = Serialize.immutable(
Immutable,
null,
customReplacer,
customReviver
);
const map = Immutable.Map({ a: 1, b: 2 });
const serialized = stringify(map);
console.log(serialized);
// {"data":{"a":{"data":"one","__serializedType__":"number"},"b":2},"__serializedType__":"ImmutableMap"}
const parsed = parse(serialized);
console.log(Immutable.is(parsed, map));
// true
```
### Supported
#### ImutableJS
- [x] Record
- [x] Range
- [x] Repeat
- [x] Map
- [x] OrderedMap
- [x] List
- [x] Set
- [x] OrderedSet
- [x] Seq
- [x] Stack
#### ES6
- [x] Symbol
- [x] Map
- [x] Set
- [ ] Typed Array
### License
MIT

View File

@ -0,0 +1,15 @@
// jsan stringify options
module.exports = {
refs: false, // references can't be resolved on the original Immutable structure
date: true,
function: true,
regex: true,
undefined: true,
error: true,
symbol: true,
map: true,
set: true,
nan: true,
infinity: true
};

View File

@ -0,0 +1,32 @@
function mark(data, type, transformMethod) {
return {
data: transformMethod ? data[transformMethod]() : data,
__serializedType__: type
};
}
function extract(data, type) {
return {
data: Object.assign({}, data),
__serializedType__: type
};
}
function refer(data, type, isArray, refs) {
var r = mark(data, type, isArray);
if (!refs) return r;
for (var i = 0; i < refs.length; i++) {
var ref = refs[i];
if (typeof ref === 'function' && data instanceof ref) {
r.__serializedRef__ = i;
return r;
}
}
return r;
}
module.exports = {
mark: mark,
extract: extract,
refer: refer
};

View File

@ -0,0 +1,23 @@
var jsan = require('jsan');
var serialize = require('./serialize');
var options = require('../constants/options');
module.exports = function(Immutable, refs, customReplacer, customReviver) {
return {
stringify: function(data) {
return jsan.stringify(
data,
serialize(Immutable, refs, customReplacer, customReviver).replacer,
null,
options
);
},
parse: function(data) {
return jsan.parse(
data,
serialize(Immutable, refs, customReplacer, customReviver).reviver
);
},
serialize: serialize
};
};

View File

@ -0,0 +1,87 @@
var helpers = require('../helpers');
var mark = helpers.mark;
var extract = helpers.extract;
var refer = helpers.refer;
var options = require('../constants/options');
module.exports = function serialize(
Immutable,
refs,
customReplacer,
customReviver
) {
function replacer(key, value) {
if (value instanceof Immutable.Record)
return refer(value, 'ImmutableRecord', 'toObject', refs);
if (value instanceof Immutable.Range)
return extract(value, 'ImmutableRange');
if (value instanceof Immutable.Repeat)
return extract(value, 'ImmutableRepeat');
if (Immutable.OrderedMap.isOrderedMap(value))
return mark(value, 'ImmutableOrderedMap', 'toObject');
if (Immutable.Map.isMap(value))
return mark(value, 'ImmutableMap', 'toObject');
if (Immutable.List.isList(value))
return mark(value, 'ImmutableList', 'toArray');
if (Immutable.OrderedSet.isOrderedSet(value))
return mark(value, 'ImmutableOrderedSet', 'toArray');
if (Immutable.Set.isSet(value))
return mark(value, 'ImmutableSet', 'toArray');
if (Immutable.Seq.isSeq(value))
return mark(value, 'ImmutableSeq', 'toArray');
if (Immutable.Stack.isStack(value))
return mark(value, 'ImmutableStack', 'toArray');
return value;
}
function reviver(key, value) {
if (
typeof value === 'object' &&
value !== null &&
'__serializedType__' in value
) {
var data = value.data;
switch (value.__serializedType__) {
case 'ImmutableMap':
return Immutable.Map(data);
case 'ImmutableOrderedMap':
return Immutable.OrderedMap(data);
case 'ImmutableList':
return Immutable.List(data);
case 'ImmutableRange':
return Immutable.Range(data._start, data._end, data._step);
case 'ImmutableRepeat':
return Immutable.Repeat(data._value, data.size);
case 'ImmutableSet':
return Immutable.Set(data);
case 'ImmutableOrderedSet':
return Immutable.OrderedSet(data);
case 'ImmutableSeq':
return Immutable.Seq(data);
case 'ImmutableStack':
return Immutable.Stack(data);
case 'ImmutableRecord':
return refs && refs[value.__serializedRef__]
? new refs[value.__serializedRef__](data)
: Immutable.Map(data);
default:
return data;
}
}
return value;
}
return {
replacer: customReplacer
? function(key, value) {
return customReplacer(key, value, replacer);
}
: replacer,
reviver: customReviver
? function(key, value) {
return customReviver(key, value, reviver);
}
: reviver,
options: options
};
};

View File

@ -0,0 +1,5 @@
var immutable = require('./immutable');
module.exports = {
immutable: immutable
};

View File

@ -0,0 +1,30 @@
{
"name": "remotedev-serialize",
"version": "0.1.8",
"description": "Serialize unserializable data and parse it back.",
"main": "index.js",
"scripts": {
"test": "jest --no-cache",
"prepublish": "npm run test"
},
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"keywords": [
"redux",
"devtools"
],
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
"license": "MIT",
"bugs": {
"url": "https://github.com/reduxjs/redux-devtools/issues"
},
"homepage": "https://github.com/reduxjs/redux-devtools",
"devDependencies": {
"immutable": "^3.8.2"
},
"dependencies": {
"jsan": "^3.1.13"
}
}

View File

@ -0,0 +1,35 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Helpers extract 1`] = `
Object {
"__serializedType__": "testType",
"data": Object {
"testData": "test",
},
}
`;
exports[`Helpers mark 1`] = `
Object {
"__serializedType__": "testType",
"data": Object {
"testData": "test",
},
}
`;
exports[`Helpers mark 2`] = `
Object {
"__serializedType__": "testType",
"data": "[object Object]",
}
`;
exports[`Helpers refer 1`] = `
Object {
"__serializedType__": "testType",
"data": Object {
"testData": "test",
},
}
`;

View File

@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Immutable Nested stringify 1`] = `"{\\"data\\":[[\\"map\\",{\\"data\\":{\\"seq\\":{\\"data\\":[1,2,3,4,5,6,7,8],\\"__serializedType__\\":\\"ImmutableSeq\\"},\\"stack\\":{\\"data\\":[\\"a\\",\\"b\\",\\"c\\"],\\"__serializedType__\\":\\"ImmutableStack\\"}},\\"__serializedType__\\":\\"ImmutableOrderedMap\\"}],[\\"repeat\\",{\\"data\\":{\\"_value\\":\\"hi\\",\\"size\\":100},\\"__serializedType__\\":\\"ImmutableRepeat\\"}]],\\"__serializedType__\\":\\"ImmutableSet\\"}"`;
exports[`Immutable Record stringify 1`] = `"{\\"data\\":{\\"a\\":1,\\"b\\":3},\\"__serializedType__\\":\\"ImmutableRecord\\",\\"__serializedRef__\\":0}"`;
exports[`Immutable Stringify list 1`] = `"{\\"data\\":[1,2,3,4,5,6,7,8,9,10],\\"__serializedType__\\":\\"ImmutableList\\"}"`;
exports[`Immutable Stringify map 1`] = `"{\\"data\\":{\\"a\\":1,\\"b\\":2,\\"c\\":3,\\"d\\":4},\\"__serializedType__\\":\\"ImmutableMap\\"}"`;
exports[`Immutable Stringify orderedMap 1`] = `"{\\"data\\":{\\"b\\":2,\\"a\\":1,\\"c\\":3,\\"d\\":4},\\"__serializedType__\\":\\"ImmutableOrderedMap\\"}"`;
exports[`Immutable Stringify orderedSet 1`] = `"{\\"data\\":[10,9,8,7,6,5,4,3,2,1],\\"__serializedType__\\":\\"ImmutableOrderedSet\\"}"`;
exports[`Immutable Stringify range 1`] = `"{\\"data\\":{\\"_start\\":0,\\"_end\\":7,\\"_step\\":1,\\"size\\":7},\\"__serializedType__\\":\\"ImmutableRange\\"}"`;
exports[`Immutable Stringify repeat 1`] = `"{\\"data\\":{\\"_value\\":\\"hi\\",\\"size\\":100},\\"__serializedType__\\":\\"ImmutableRepeat\\"}"`;
exports[`Immutable Stringify seq 1`] = `"{\\"data\\":[1,2,3,4,5,6,7,8],\\"__serializedType__\\":\\"ImmutableSeq\\"}"`;
exports[`Immutable Stringify set 1`] = `"{\\"data\\":[1,2,3,4,5,6,7,8,9,10],\\"__serializedType__\\":\\"ImmutableSet\\"}"`;
exports[`Immutable Stringify stack 1`] = `"{\\"data\\":[\\"a\\",\\"b\\",\\"c\\"],\\"__serializedType__\\":\\"ImmutableStack\\"}"`;

View File

@ -0,0 +1,27 @@
var helpers = require('../helpers');
var mark = helpers.mark;
var extract = helpers.extract;
var refer = helpers.refer;
describe('Helpers', function() {
it('mark', function() {
expect(mark({ testData: 'test' }, 'testType')).toMatchSnapshot();
expect(
mark({ testData: 'test' }, 'testType', 'toString')
).toMatchSnapshot();
});
it('extract', function() {
expect(extract({ testData: 'test' }, 'testType')).toMatchSnapshot();
});
it('refer', function() {
var TestClass = function(data) {
return data;
};
var testInstance = new TestClass({ testData: 'test' });
expect(
refer(testInstance, 'testType', false, [TestClass])
).toMatchSnapshot();
});
});

View File

@ -0,0 +1,148 @@
var Immutable = require('immutable');
var Serialize = require('../immutable');
var serialize = Serialize(Immutable);
var stringify = serialize.stringify;
var parse = serialize.parse;
var data = {
map: Immutable.Map({ a: 1, b: 2, c: 3, d: 4 }),
orderedMap: Immutable.OrderedMap({ b: 2, a: 1, c: 3, d: 4 }),
list: Immutable.List([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
range: Immutable.Range(0, 7),
repeat: Immutable.Repeat('hi', 100),
set: Immutable.Set([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]),
orderedSet: Immutable.OrderedSet([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]),
seq: Immutable.Seq.of(1, 2, 3, 4, 5, 6, 7, 8),
stack: Immutable.Stack.of('a', 'b', 'c')
};
describe('Immutable', function() {
var stringified = {};
describe('Stringify', function() {
Object.keys(data).forEach(function(key) {
it(key, function() {
stringified[key] = stringify(data[key]);
expect(stringified[key]).toMatchSnapshot();
});
});
});
describe('Parse', function() {
Object.keys(data).forEach(function(key) {
it(key, function() {
expect(parse(stringified[key])).toEqual(data[key]);
});
});
});
describe('Record', function() {
var ABRecord = Immutable.Record({ a: 1, b: 2 });
var myRecord = new ABRecord({ b: 3 });
var serialize = Serialize(Immutable, [ABRecord]);
var stringify = serialize.stringify;
var parse = serialize.parse;
var stringifiedRecord;
it('stringify', function() {
stringifiedRecord = stringify(myRecord);
expect(stringifiedRecord).toMatchSnapshot();
});
it('parse', function() {
expect(parse(stringifiedRecord)).toEqual(myRecord);
});
});
describe('Nested', function() {
var ABRecord = Immutable.Record({
map: Immutable.OrderedMap({ seq: data.seq, stack: data.stack }),
repeat: data.repeat
});
var nestedData = Immutable.Set(ABRecord(), data.orderedSet, data.range);
var serialize = Serialize(Immutable, [ABRecord]);
var stringify = serialize.stringify;
var parse = serialize.parse;
var stringifiedNested;
it('stringify', function() {
stringifiedNested = stringify(nestedData);
expect(stringifiedNested).toMatchSnapshot();
});
it('parse', function() {
expect(parse(stringifiedNested)).toEqual(nestedData);
});
});
describe('With references', function() {
it('serializes and deserializes', function() {
var sharedValue = [];
var record = Immutable.Record({
prop: sharedValue
});
var refs = [record];
var obj = Immutable.Map({
fst: new record(),
scnd: new record()
});
var serialized = stringify(
obj,
Serialize(Immutable, refs).replacer,
null,
true
);
var parsed = JSON.parse(serialized);
var fstProp = parsed.data.fst.data.prop;
var scndProp = parsed.data.scnd.data.prop;
expect(fstProp).toEqual(scndProp);
expect(Array.isArray(obj.get('fst').get('prop')));
});
});
describe('Custom replacer and reviver functions', function() {
var customOneRepresentation = 'one';
function customReplacer(key, value, defaultReplacer) {
if (value === 1) {
return { data: customOneRepresentation, __serializedType__: 'number' };
}
return defaultReplacer(key, value);
}
function customReviver(key, value, defaultReviver) {
if (
typeof value === 'object' &&
value.__serializedType__ === 'number' &&
value.data === customOneRepresentation
) {
return 1;
}
return defaultReviver(key, value);
}
var serializeCustom = Serialize(
Immutable,
null,
customReplacer,
customReviver
);
Object.keys(data).forEach(function(key) {
var stringified = serializeCustom.stringify(data[key]);
it(key, function() {
var deserialized = serializeCustom.parse(stringified);
expect(deserialized).toEqual(data[key]);
if (key === 'map' || key === 'orderedMap') {
var deserializedDefault = parse(stringified);
expect(deserializedDefault.get('a')).toEqual(customOneRepresentation);
}
});
});
});
});

View File

@ -4193,7 +4193,7 @@ babel-preset-react-app@^6.1.0:
babel-plugin-macros "2.4.2"
babel-plugin-transform-react-remove-prop-types "0.4.18"
babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.2.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.5.0:
babel-runtime@6.x.x, babel-runtime@^6.11.6, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0, babel-runtime@^6.5.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@ -13783,17 +13783,6 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
redux-devtools-dock-monitor@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/redux-devtools-dock-monitor/-/redux-devtools-dock-monitor-1.1.3.tgz#1205e823c82536570aac8551a1c4b70972cba6aa"
integrity sha512-yAXzoI0lpjv19CxVuw8RECeFWUVdyzayqnkX8ePZyeXV2ZgIk4T+rKx82Wk+REP1y3rl8o1/oFDq4B7EobOqMg==
dependencies:
babel-runtime "^6.2.0"
parse-key "^0.2.1"
prop-types "^15.5.8"
react-dock "^0.2.4"
react-pure-render "^1.0.2"
redux-devtools-themes@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redux-devtools-themes/-/redux-devtools-themes-1.0.0.tgz#c482dce3c5373976045f40134907d9dcb3ae3d5d"
@ -13926,13 +13915,6 @@ relateurl@0.2.x, relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
remotedev-serialize@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/remotedev-serialize/-/remotedev-serialize-0.1.8.tgz#c99cb184e7f71a906162abc404be8ce33810205f"
integrity sha512-3YG/FDcOmiK22bl5oMRM8RRnbGrFEuPGjbcDG+z2xi5aQaNQNZ8lqoRnZTwXVfaZtutXuiAQOgPRrogzQk8edg==
dependencies:
jsan "^3.1.13"
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"