Counteract namespace minification by terser

This breaks serialized data compatibility !
This commit is contained in:
Michaël Lemaire 2019-11-07 23:59:34 +01:00
parent 9710424caf
commit b9388a8f11
6 changed files with 103 additions and 14 deletions

View file

@ -63,7 +63,7 @@ const obj = {
let serializer = new Serializer(namespace);
// Optionally, some class instances may be ignored (they will be replaced by *undefined*)
serializer.addIgnoredClass("Class3");
serializer.addIgnoredClass(Class3);
// Serialize the object to a string
let state = serializer.serialize(obj);

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "tk-serializer",
"version": "1.1.2",
"version": "1.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -1,6 +1,6 @@
{
"name": "tk-serializer",
"version": "1.1.2",
"version": "1.2.0",
"description": "Typescript/Javascript serializer, with full object reconstruction",
"author": {
"name": "Michaël Lemaire",

7
src/index.test.ts Normal file
View file

@ -0,0 +1,7 @@
import * as index from "./index";
describe("index", () => {
it("exposes Serializer", () => {
expect(index.Serializer).toBeTruthy();
});
})

View file

@ -19,14 +19,29 @@ class TestSerializerObj3 {
}
}
class TestSerializerObj4 extends TestSerializerObj1 {
}
const TEST_NS = {
TestSerializerObj1,
TestSerializerObj2,
TestSerializerObj3
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 = TEST_NS): any {
function checkReversability(obj: any, namespace: any = TEST_NS): any {
var serializer = new Serializer(namespace);
var data = serializer.serialize(obj);
serializer = new Serializer(namespace);
@ -45,7 +60,15 @@ describe("Serializer", () => {
});
it("restores objects constructed from class", () => {
var loaded = checkReversability(new TestSerializerObj1(5));
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);
});
@ -73,11 +96,27 @@ describe("Serializer", () => {
});
it("ignores some classes", () => {
var serializer = new Serializer(TEST_NS);
let serializer = new Serializer(TEST_NS);
serializer.addIgnoredClass("TestSerializerObj1");
var data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
var loaded = serializer.unserialize(data);
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 });
});
@ -102,4 +141,32 @@ describe("Serializer", () => {
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);
});
});

View file

@ -62,22 +62,37 @@ function classname(obj: Object): string {
return (<any>obj.constructor).name;
}
type SerializerNamespace = { [name: string]: { new(): any } };
/**
* A deep serializer of javascript objects.
*/
export class Serializer {
private namespace: any;
private namespace: SerializerNamespace;
private namespace_rev: { [classname: string]: string };
private ignored: string[] = [];
constructor(namespace: any) {
this.namespace = namespace;
if (Array.isArray(namespace)) {
this.namespace = {};
for (let construct of namespace) {
this.namespace[construct.name] = construct;
}
} else {
this.namespace = namespace;
}
this.namespace_rev = {};
for (let [key, value] of Object.entries(this.namespace)) {
this.namespace_rev[value.name] = key;
}
}
/**
* Add a class to the ignore list
*/
addIgnoredClass(name: string) {
this.ignored.push(name);
addIgnoredClass(cl: string | { new(): any }) {
this.ignored.push(typeof cl === "string" ? cl : cl.name);
}
/**
@ -123,7 +138,7 @@ export class Serializer {
//console.log("Serialize stats", stats);
// Serialize objects list, transforming deeper objects to links
var fobjects = objects.map(value => <Object>{ $c: 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) => {
if (key != "$f" && isObject(value) && !value.hasOwnProperty("$c") && !value.hasOwnProperty("$i")) {
return { $i: objects.indexOf(value) };