Switched to deno
This commit is contained in:
parent
b9388a8f11
commit
f915a4b889
|
@ -1,10 +1,9 @@
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
[*]
|
[*.{ts,json}]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
|
||||||
end_of_line = lf
|
|
||||||
max_line_length = off
|
|
||||||
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,5 +1,3 @@
|
||||||
.venv
|
deno.d.ts
|
||||||
node_modules
|
.vscode
|
||||||
.rts2_cache_*
|
.local
|
||||||
.coverage
|
|
||||||
/dist/
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
image: node:latest
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- node_modules/
|
|
||||||
|
|
||||||
test:
|
|
||||||
before_script:
|
|
||||||
- npm install
|
|
||||||
script:
|
|
||||||
- npm test
|
|
73
README.md
73
README.md
|
@ -1,73 +0,0 @@
|
||||||
tk-serializer
|
|
||||||
=============
|
|
||||||
|
|
||||||
[![pipeline status](https://gitlab.com/thunderk/tk-serializer/badges/master/pipeline.svg)](https://gitlab.com/thunderk/tk-serializer/commits/master)
|
|
||||||
[![coverage report](https://gitlab.com/thunderk/tk-serializer/badges/master/coverage.svg)](https://gitlab.com/thunderk/tk-serializer/commits/master)
|
|
||||||
[![npm version](https://img.shields.io/npm/v/tk-serializer.svg)](https://npmjs.com/tk-serializer)
|
|
||||||
[![npm size](https://img.shields.io/bundlephobia/min/tk-serializer.svg)](https://bundlephobia.com/result?p=tk-serializer)
|
|
||||||
|
|
||||||
About
|
|
||||||
-----
|
|
||||||
|
|
||||||
This library offers a generic serialization system for Javascript.
|
|
||||||
|
|
||||||
Deep objects state may be serialized to a string, and reconstructed back.
|
|
||||||
|
|
||||||
Class instances are reconstructed properly, as long as they are in the provided namespace. Circular references are handled.
|
|
||||||
|
|
||||||
Be warned that resulting serialized value may be quite large.
|
|
||||||
|
|
||||||
Typescript definitions are included.
|
|
||||||
|
|
||||||
Issues can be reported on [GitLab](https://gitlab.com/thunderk/tk-serializer/issues).
|
|
||||||
|
|
||||||
Install
|
|
||||||
-------
|
|
||||||
|
|
||||||
Import in node:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
npm install tk-serializer
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import { Serializer } from "tk-serializer";
|
|
||||||
```
|
|
||||||
|
|
||||||
Import in browser:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="https://unpkg.com/tk-serializer"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const Serializer = tkSerializer.Serializer;
|
|
||||||
```
|
|
||||||
|
|
||||||
Use
|
|
||||||
---
|
|
||||||
|
|
||||||
Suppose you have 2 classes Class1 and Class2, whose instances you want to serialize:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const namespace = {
|
|
||||||
Class1,
|
|
||||||
Class2
|
|
||||||
};
|
|
||||||
const obj = {
|
|
||||||
a: [1, "a", new Class1()],
|
|
||||||
b: new Class2("x"),
|
|
||||||
c: new Class3()
|
|
||||||
};
|
|
||||||
|
|
||||||
let serializer = new Serializer(namespace);
|
|
||||||
|
|
||||||
// Optionally, some class instances may be ignored (they will be replaced by *undefined*)
|
|
||||||
serializer.addIgnoredClass(Class3);
|
|
||||||
|
|
||||||
// Serialize the object to a string
|
|
||||||
let state = serializer.serialize(obj);
|
|
||||||
|
|
||||||
// Reconstruct the object back (*c* will be undefined)
|
|
||||||
let nobj = serializer.unserialize(state);
|
|
||||||
```
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Activation script for virtual nodejs environment
|
|
||||||
# Usage:
|
|
||||||
# source activate_node
|
|
||||||
|
|
||||||
vdir="./.venv"
|
|
||||||
expected="12.13.0"
|
|
||||||
|
|
||||||
if [ \! -f "./activate_node" ]
|
|
||||||
then
|
|
||||||
echo "Not in project directory"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
test -x "${vdir}/bin/nodeenv" || ( python3 -m venv "${vdir}" && "${vdir}/bin/pip" install --upgrade nodeenv )
|
|
||||||
test "$(${vdir}/node/bin/nodejs --version)" = "v${expected}" || "${vdir}/bin/nodeenv" --node=${expected} --force "${vdir}/node"
|
|
||||||
source "${vdir}/node/bin/activate"
|
|
|
@ -1,27 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
transform: {
|
|
||||||
"^.+\\.ts$": "ts-jest"
|
|
||||||
},
|
|
||||||
moduleFileExtensions: [
|
|
||||||
"ts",
|
|
||||||
"js",
|
|
||||||
"json",
|
|
||||||
"node"
|
|
||||||
],
|
|
||||||
watchPathIgnorePatterns: [
|
|
||||||
"<rootDir>/dist/",
|
|
||||||
"<rootDir>/node_modules/",
|
|
||||||
],
|
|
||||||
restoreMocks: true,
|
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: [
|
|
||||||
"src/**/*.ts",
|
|
||||||
"!src/**/*.test.ts",
|
|
||||||
],
|
|
||||||
coverageDirectory: ".coverage",
|
|
||||||
coverageReporters: [
|
|
||||||
"lcovonly",
|
|
||||||
"html",
|
|
||||||
"text-summary"
|
|
||||||
]
|
|
||||||
}
|
|
8604
package-lock.json
generated
8604
package-lock.json
generated
File diff suppressed because it is too large
Load diff
43
package.json
43
package.json
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"name": "tk-serializer",
|
|
||||||
"version": "1.2.0",
|
|
||||||
"description": "Typescript/Javascript serializer, with full object reconstruction",
|
|
||||||
"author": {
|
|
||||||
"name": "Michaël Lemaire",
|
|
||||||
"url": "https://thunderk.net"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://code.thunderk.net/tslib/tk-serializer.git"
|
|
||||||
},
|
|
||||||
"license": "ISC",
|
|
||||||
"source": "src/index.ts",
|
|
||||||
"main": "dist/tk-serializer.umd.js",
|
|
||||||
"module": "dist/tk-serializer.modern.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"scripts": {
|
|
||||||
"build": "microbundle build -f modern,umd",
|
|
||||||
"test": "jest",
|
|
||||||
"prepare": "npm run build",
|
|
||||||
"prepublishOnly": "npm test",
|
|
||||||
"normalize": "tk-base",
|
|
||||||
"dev": "run-p dev:*",
|
|
||||||
"dev:test": "jest --watchAll",
|
|
||||||
"dev:build": "microbundle watch -f modern,umd",
|
|
||||||
"dev:serve": "live-server --host=localhost --port=5000 --no-browser --ignorePattern='.*\\.d\\.ts' dist"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"/src",
|
|
||||||
"/dist"
|
|
||||||
],
|
|
||||||
"devDependencies": {
|
|
||||||
"tk-base": "^0.2.5"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://gitlab.com/thunderk/tk-serializer/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://code.thunderk.net/tslib/tk-serializer",
|
|
||||||
"keywords": [
|
|
||||||
"typescript"
|
|
||||||
]
|
|
||||||
}
|
|
195
serializer.test.ts
Normal file
195
serializer.test.ts
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import {
|
||||||
|
it,
|
||||||
|
expect,
|
||||||
|
patch,
|
||||||
|
} from "https://code.thunderk.net/typescript/deno_tools/raw/1.0.0/testing.ts";
|
||||||
|
import { Serializer } from "./serializer.ts";
|
||||||
|
|
||||||
|
class TestSerializerObj1 {
|
||||||
|
a: number;
|
||||||
|
constructor(a = 0) {
|
||||||
|
this.a = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestSerializerObj2 {
|
||||||
|
a = () => 1;
|
||||||
|
b = [(obj: any) => 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestSerializerObj3 {
|
||||||
|
a = [1, 2];
|
||||||
|
postUnserialize() {
|
||||||
|
this.a.push(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestSerializerObj4 extends TestSerializerObj1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_NS = {
|
||||||
|
TestSerializerObj1,
|
||||||
|
TestSerializerObj2,
|
||||||
|
TestSerializerObj3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const TEST_NS_FLAT = [
|
||||||
|
TestSerializerObj1,
|
||||||
|
TestSerializerObj2,
|
||||||
|
TestSerializerObj3,
|
||||||
|
];
|
||||||
|
|
||||||
|
const TEST_NS_RENAME = {
|
||||||
|
ob1: TestSerializerObj1,
|
||||||
|
ob2: TestSerializerObj2,
|
||||||
|
ob3: TestSerializerObj3,
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkReversability(
|
||||||
|
obj: any,
|
||||||
|
namespace: any = TEST_NS,
|
||||||
|
equality?: (loaded: any, orig: any) => void,
|
||||||
|
): any {
|
||||||
|
var serializer = new Serializer(namespace);
|
||||||
|
var data = serializer.serialize(obj);
|
||||||
|
serializer = new Serializer(namespace);
|
||||||
|
var loaded = serializer.unserialize(data);
|
||||||
|
if (equality) {
|
||||||
|
equality(loaded, obj);
|
||||||
|
} else {
|
||||||
|
expect(loaded).toEqual(obj);
|
||||||
|
}
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("serializes simple objects", () => {
|
||||||
|
var obj = {
|
||||||
|
"a": 5,
|
||||||
|
"b": null,
|
||||||
|
"c": [{ "a": 2 }, "test"],
|
||||||
|
};
|
||||||
|
checkReversability(obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restores objects constructed from class", () => {
|
||||||
|
let loaded = checkReversability(new TestSerializerObj1(5));
|
||||||
|
expect(loaded.a).toBe(5);
|
||||||
|
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
||||||
|
|
||||||
|
loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_FLAT);
|
||||||
|
expect(loaded.a).toBe(5);
|
||||||
|
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
||||||
|
|
||||||
|
loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_RENAME);
|
||||||
|
expect(loaded.a).toBe(5);
|
||||||
|
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores one version of the same object", () => {
|
||||||
|
var a = new TestSerializerObj1(8);
|
||||||
|
var b = new TestSerializerObj1(8);
|
||||||
|
var c = {
|
||||||
|
"r": a,
|
||||||
|
"s": ["test", a],
|
||||||
|
"t": a,
|
||||||
|
"u": b,
|
||||||
|
};
|
||||||
|
var loaded = checkReversability(c);
|
||||||
|
expect(loaded.t).toBe(loaded.r);
|
||||||
|
expect(loaded.s[1]).toBe(loaded.r);
|
||||||
|
expect(loaded.u).not.toBe(loaded.r);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles circular references", () => {
|
||||||
|
var a: any = { b: {} };
|
||||||
|
a.b.c = a;
|
||||||
|
|
||||||
|
checkReversability(a, undefined, (loaded) => {
|
||||||
|
expect(Object.keys(loaded)).toEqual(["b"]);
|
||||||
|
expect(Object.keys(loaded.b)).toEqual(["c"]);
|
||||||
|
expect(Object.keys(loaded.b.c)).toEqual(["b"]);
|
||||||
|
expect(Object.keys(loaded.b.c.b)).toEqual(["c"]);
|
||||||
|
expect(loaded.b.c).toBe(loaded);
|
||||||
|
expect(loaded.b.c.b).toBe(loaded.b);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores some classes", () => {
|
||||||
|
let serializer = new Serializer(TEST_NS);
|
||||||
|
serializer.addIgnoredClass("TestSerializerObj1");
|
||||||
|
|
||||||
|
let data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
||||||
|
let loaded = serializer.unserialize(data);
|
||||||
|
|
||||||
|
expect(loaded).toEqual({ a: 5, b: undefined });
|
||||||
|
|
||||||
|
serializer = new Serializer(TEST_NS);
|
||||||
|
serializer.addIgnoredClass(TestSerializerObj1);
|
||||||
|
|
||||||
|
data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
||||||
|
loaded = serializer.unserialize(data);
|
||||||
|
|
||||||
|
expect(loaded).toEqual({ a: 5, b: undefined });
|
||||||
|
|
||||||
|
serializer = new Serializer(TEST_NS_RENAME);
|
||||||
|
serializer.addIgnoredClass(TestSerializerObj1);
|
||||||
|
|
||||||
|
data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
||||||
|
loaded = serializer.unserialize(data);
|
||||||
|
|
||||||
|
expect(loaded).toEqual({ a: 5, b: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores functions", () => {
|
||||||
|
let serializer = new Serializer(TEST_NS);
|
||||||
|
let data = serializer.serialize({ obj: new TestSerializerObj2() });
|
||||||
|
let loaded = serializer.unserialize(data);
|
||||||
|
|
||||||
|
let expected = <any> new TestSerializerObj2();
|
||||||
|
expected.a = undefined;
|
||||||
|
expected.b[0] = undefined;
|
||||||
|
expect(loaded).toEqual({ obj: expected });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls specific postUnserialize", () => {
|
||||||
|
let serializer = new Serializer(TEST_NS);
|
||||||
|
let data = serializer.serialize({ obj: new TestSerializerObj3() });
|
||||||
|
let loaded = serializer.unserialize(data);
|
||||||
|
|
||||||
|
let expected = new TestSerializerObj3();
|
||||||
|
expected.a = [1, 2, 3];
|
||||||
|
expect(loaded).toEqual({ obj: expected });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles missing classes", () => {
|
||||||
|
const mock_error = patch(console, "error", () => {});
|
||||||
|
let serializer = new Serializer(TEST_NS);
|
||||||
|
mock_error.exec(() => {
|
||||||
|
let data = serializer.serialize({ obj: new TestSerializerObj4() });
|
||||||
|
let loaded = serializer.unserialize(data);
|
||||||
|
expect(loaded).toEqual({ obj: { a: 0 } });
|
||||||
|
expect(mock_error.fn).toHaveBeenCalledWith(
|
||||||
|
"Can't find class",
|
||||||
|
"TestSerializerObj4",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses namespace alias to protect from property mangling", () => {
|
||||||
|
const data = {
|
||||||
|
a: new TestSerializerObj1(1),
|
||||||
|
b: new TestSerializerObj1(2),
|
||||||
|
c: [new TestSerializerObj1(3)],
|
||||||
|
};
|
||||||
|
|
||||||
|
const serializer1 = new Serializer(TEST_NS);
|
||||||
|
const serializer2 = new Serializer(TEST_NS_RENAME);
|
||||||
|
|
||||||
|
const dumped1 = serializer1.serialize(data);
|
||||||
|
const dumped2 = serializer2.serialize(data);
|
||||||
|
|
||||||
|
expect(dumped1.length).toBeGreaterThan(dumped2.length);
|
||||||
|
|
||||||
|
expect(serializer1.unserialize(dumped1)).toEqual(data);
|
||||||
|
expect(serializer2.unserialize(dumped2)).toEqual(data);
|
||||||
|
});
|
|
@ -16,7 +16,12 @@ function isObject(value: any): boolean {
|
||||||
*
|
*
|
||||||
* *memo* is used to prevent circular references to be traversed
|
* *memo* is used to prevent circular references to be traversed
|
||||||
*/
|
*/
|
||||||
function crawl(obj: any, callback: (item: any) => any, replace = false, memo: any[] = []) {
|
function crawl(
|
||||||
|
obj: any,
|
||||||
|
callback: (item: any) => any,
|
||||||
|
replace = false,
|
||||||
|
memo: any[] = [],
|
||||||
|
) {
|
||||||
if (isObject(obj)) {
|
if (isObject(obj)) {
|
||||||
if (memo.indexOf(obj) >= 0) {
|
if (memo.indexOf(obj) >= 0) {
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -33,7 +38,7 @@ function crawl(obj: any, callback: (item: any) => any, replace = false, memo: an
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
let subresult = obj.map(value => crawl(value, callback, replace, memo));
|
let subresult = obj.map((value) => crawl(value, callback, replace, memo));
|
||||||
if (replace) {
|
if (replace) {
|
||||||
subresult.forEach((value, index) => {
|
subresult.forEach((value, index) => {
|
||||||
obj[index] = value;
|
obj[index] = value;
|
||||||
|
@ -59,10 +64,10 @@ function crawl(obj: any, callback: (item: any) => any, replace = false, memo: an
|
||||||
* Get the class name of an object.
|
* Get the class name of an object.
|
||||||
*/
|
*/
|
||||||
function classname(obj: Object): string {
|
function classname(obj: Object): string {
|
||||||
return (<any>obj.constructor).name;
|
return (<any> obj.constructor).name;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SerializerNamespace = { [name: string]: { new(): any } };
|
type SerializerNamespace = { [name: string]: { new (): any } };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A deep serializer of javascript objects.
|
* A deep serializer of javascript objects.
|
||||||
|
@ -91,7 +96,7 @@ export class Serializer {
|
||||||
/**
|
/**
|
||||||
* Add a class to the ignore list
|
* Add a class to the ignore list
|
||||||
*/
|
*/
|
||||||
addIgnoredClass(cl: string | { new(): any }) {
|
addIgnoredClass(cl: string | { new (): any }) {
|
||||||
this.ignored.push(typeof cl === "string" ? cl : cl.name);
|
this.ignored.push(typeof cl === "string" ? cl : cl.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +124,7 @@ export class Serializer {
|
||||||
// Collect objects
|
// Collect objects
|
||||||
var objects: Object[] = [];
|
var objects: Object[] = [];
|
||||||
var stats: any = {};
|
var stats: any = {};
|
||||||
crawl(obj, value => {
|
crawl(obj, (value) => {
|
||||||
if (isObject(value)) {
|
if (isObject(value)) {
|
||||||
var vtype = classname(value);
|
var vtype = classname(value);
|
||||||
if (vtype != "" && this.ignored.indexOf(vtype) < 0) {
|
if (vtype != "" && this.ignored.indexOf(vtype) < 0) {
|
||||||
|
@ -138,9 +143,17 @@ export class Serializer {
|
||||||
//console.log("Serialize stats", stats);
|
//console.log("Serialize stats", stats);
|
||||||
|
|
||||||
// Serialize objects list, transforming deeper objects to links
|
// Serialize objects list, transforming deeper objects to links
|
||||||
var fobjects = objects.map(value => <Object>{ $c: this.namespace_rev[classname(value)] || classname(value), $f: merge({}, value) });
|
var fobjects = objects.map((value) =>
|
||||||
|
<Object> {
|
||||||
|
$c: this.namespace_rev[classname(value)] || classname(value),
|
||||||
|
$f: merge({}, value),
|
||||||
|
}
|
||||||
|
);
|
||||||
return JSON.stringify(fobjects, (key, value) => {
|
return JSON.stringify(fobjects, (key, value) => {
|
||||||
if (key != "$f" && isObject(value) && !value.hasOwnProperty("$c") && !value.hasOwnProperty("$i")) {
|
if (
|
||||||
|
key != "$f" && isObject(value) && !value.hasOwnProperty("$c") &&
|
||||||
|
!value.hasOwnProperty("$i")
|
||||||
|
) {
|
||||||
return { $i: objects.indexOf(value) };
|
return { $i: objects.indexOf(value) };
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
|
@ -156,20 +169,24 @@ export class Serializer {
|
||||||
var fobjects = JSON.parse(data);
|
var fobjects = JSON.parse(data);
|
||||||
|
|
||||||
// Reconstruct objects
|
// Reconstruct objects
|
||||||
var objects = fobjects.map((objdata: any) => merge(this.constructObject(objdata['$c']), objdata['$f']));
|
var objects = fobjects.map((objdata: any) =>
|
||||||
|
merge(this.constructObject(objdata["$c"]), objdata["$f"])
|
||||||
|
);
|
||||||
|
|
||||||
// Reconnect links
|
// Reconnect links
|
||||||
crawl(objects, value => {
|
crawl(objects, (value) => {
|
||||||
if (value instanceof Object && value.hasOwnProperty('$i')) {
|
if (value instanceof Object && value.hasOwnProperty("$i")) {
|
||||||
return objects[value['$i']];
|
return objects[value["$i"]];
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
// Post unserialize hooks
|
// Post unserialize hooks
|
||||||
crawl(objects[0], value => {
|
crawl(objects[0], (value) => {
|
||||||
if (value instanceof Object && typeof value.postUnserialize === "function") {
|
if (
|
||||||
|
value instanceof Object && typeof value.postUnserialize === "function"
|
||||||
|
) {
|
||||||
value.postUnserialize();
|
value.postUnserialize();
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,7 +0,0 @@
|
||||||
import * as index from "./index";
|
|
||||||
|
|
||||||
describe("index", () => {
|
|
||||||
it("exposes Serializer", () => {
|
|
||||||
expect(index.Serializer).toBeTruthy();
|
|
||||||
});
|
|
||||||
})
|
|
|
@ -1 +0,0 @@
|
||||||
export { Serializer } from "./serializer";
|
|
|
@ -1,172 +0,0 @@
|
||||||
import { Serializer } from "./serializer";
|
|
||||||
|
|
||||||
class TestSerializerObj1 {
|
|
||||||
a: number;
|
|
||||||
constructor(a = 0) {
|
|
||||||
this.a = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestSerializerObj2 {
|
|
||||||
a = () => 1
|
|
||||||
b = [(obj: any) => 2]
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestSerializerObj3 {
|
|
||||||
a = [1, 2];
|
|
||||||
postUnserialize() {
|
|
||||||
this.a.push(3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestSerializerObj4 extends TestSerializerObj1 {
|
|
||||||
}
|
|
||||||
|
|
||||||
const TEST_NS = {
|
|
||||||
TestSerializerObj1,
|
|
||||||
TestSerializerObj2,
|
|
||||||
TestSerializerObj3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const TEST_NS_FLAT = [
|
|
||||||
TestSerializerObj1,
|
|
||||||
TestSerializerObj2,
|
|
||||||
TestSerializerObj3,
|
|
||||||
];
|
|
||||||
|
|
||||||
const TEST_NS_RENAME = {
|
|
||||||
ob1: TestSerializerObj1,
|
|
||||||
ob2: TestSerializerObj2,
|
|
||||||
ob3: TestSerializerObj3,
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Serializer", () => {
|
|
||||||
function checkReversability(obj: any, namespace: any = TEST_NS): any {
|
|
||||||
var serializer = new Serializer(namespace);
|
|
||||||
var data = serializer.serialize(obj);
|
|
||||||
serializer = new Serializer(namespace);
|
|
||||||
var loaded = serializer.unserialize(data);
|
|
||||||
expect(loaded).toEqual(obj);
|
|
||||||
return loaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
it("serializes simple objects", () => {
|
|
||||||
var obj = {
|
|
||||||
"a": 5,
|
|
||||||
"b": null,
|
|
||||||
"c": [{ "a": 2 }, "test"]
|
|
||||||
};
|
|
||||||
checkReversability(obj);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("restores objects constructed from class", () => {
|
|
||||||
let loaded = checkReversability(new TestSerializerObj1(5));
|
|
||||||
expect(loaded.a).toBe(5);
|
|
||||||
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
|
||||||
|
|
||||||
loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_FLAT);
|
|
||||||
expect(loaded.a).toBe(5);
|
|
||||||
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
|
||||||
|
|
||||||
loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_RENAME);
|
|
||||||
expect(loaded.a).toBe(5);
|
|
||||||
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("stores one version of the same object", () => {
|
|
||||||
var a = new TestSerializerObj1(8);
|
|
||||||
var b = new TestSerializerObj1(8);
|
|
||||||
var c = {
|
|
||||||
'r': a,
|
|
||||||
's': ["test", a],
|
|
||||||
't': a,
|
|
||||||
'u': b
|
|
||||||
};
|
|
||||||
var loaded = checkReversability(c);
|
|
||||||
expect(loaded.t).toBe(loaded.r);
|
|
||||||
expect(loaded.s[1]).toBe(loaded.r);
|
|
||||||
expect(loaded.u).not.toBe(loaded.r);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles circular references", () => {
|
|
||||||
var a: any = { b: {} };
|
|
||||||
a.b.c = a;
|
|
||||||
|
|
||||||
checkReversability(a);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ignores some classes", () => {
|
|
||||||
let serializer = new Serializer(TEST_NS);
|
|
||||||
serializer.addIgnoredClass("TestSerializerObj1");
|
|
||||||
|
|
||||||
let data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
|
||||||
let loaded = serializer.unserialize(data);
|
|
||||||
|
|
||||||
expect(loaded).toEqual({ a: 5, b: undefined });
|
|
||||||
|
|
||||||
serializer = new Serializer(TEST_NS);
|
|
||||||
serializer.addIgnoredClass(TestSerializerObj1);
|
|
||||||
|
|
||||||
data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
|
||||||
loaded = serializer.unserialize(data);
|
|
||||||
|
|
||||||
expect(loaded).toEqual({ a: 5, b: undefined });
|
|
||||||
|
|
||||||
serializer = new Serializer(TEST_NS_RENAME);
|
|
||||||
serializer.addIgnoredClass(TestSerializerObj1);
|
|
||||||
|
|
||||||
data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
|
||||||
loaded = serializer.unserialize(data);
|
|
||||||
|
|
||||||
expect(loaded).toEqual({ a: 5, b: undefined });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("ignores functions", () => {
|
|
||||||
let serializer = new Serializer(TEST_NS);
|
|
||||||
let data = serializer.serialize({ obj: new TestSerializerObj2() });
|
|
||||||
let loaded = serializer.unserialize(data);
|
|
||||||
|
|
||||||
let expected = <any>new TestSerializerObj2();
|
|
||||||
expected.a = undefined;
|
|
||||||
expected.b[0] = undefined;
|
|
||||||
expect(loaded).toEqual({ obj: expected });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls specific postUnserialize", () => {
|
|
||||||
let serializer = new Serializer(TEST_NS);
|
|
||||||
let data = serializer.serialize({ obj: new TestSerializerObj3() });
|
|
||||||
let loaded = serializer.unserialize(data);
|
|
||||||
|
|
||||||
let expected = new TestSerializerObj3();
|
|
||||||
expected.a = [1, 2, 3];
|
|
||||||
expect(loaded).toEqual({ obj: expected });
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles missing classes", () => {
|
|
||||||
jest.spyOn(console, "error").mockImplementation();
|
|
||||||
let serializer = new Serializer(TEST_NS);
|
|
||||||
let data = serializer.serialize({ obj: new TestSerializerObj4() });
|
|
||||||
let loaded = serializer.unserialize(data);
|
|
||||||
expect(loaded).toEqual({ obj: { a: 0 } });
|
|
||||||
expect(console.error).toHaveBeenCalledWith("Can't find class", "TestSerializerObj4");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses namespace alias to protect from property mangling", () => {
|
|
||||||
const data = {
|
|
||||||
a: new TestSerializerObj1(1),
|
|
||||||
b: new TestSerializerObj1(2),
|
|
||||||
c: [new TestSerializerObj1(3)],
|
|
||||||
};
|
|
||||||
|
|
||||||
const serializer1 = new Serializer(TEST_NS);
|
|
||||||
const serializer2 = new Serializer(TEST_NS_RENAME);
|
|
||||||
|
|
||||||
const dumped1 = serializer1.serialize(data);
|
|
||||||
const dumped2 = serializer2.serialize(data);
|
|
||||||
|
|
||||||
expect(dumped1.length).toBeGreaterThan(dumped2.length);
|
|
||||||
|
|
||||||
expect(serializer1.unserialize(dumped1)).toEqual(data);
|
|
||||||
expect(serializer2.unserialize(dumped2)).toEqual(data);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,21 +1,10 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"moduleResolution": "node",
|
"module": "esnext",
|
||||||
"esModuleInterop": true,
|
"target": "ESNext",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true
|
||||||
"declaration": true,
|
}
|
||||||
"target": "es6",
|
|
||||||
"lib": [
|
|
||||||
"dom",
|
|
||||||
"es6"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"dist",
|
|
||||||
"src/**/*.test.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue