diff --git a/README.md b/README.md index 26d0978..2cf58ec 100644 --- a/README.md +++ b/README.md @@ -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); diff --git a/package-lock.json b/package-lock.json index e7abc2c..973ecba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tk-serializer", - "version": "1.1.2", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 79c92d5..1b9234e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..4a5d231 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,7 @@ +import * as index from "./index"; + +describe("index", () => { + it("exposes Serializer", () => { + expect(index.Serializer).toBeTruthy(); + }); +}) diff --git a/src/serializer.test.ts b/src/serializer.test.ts index 6ab6152..3b54eab 100644 --- a/src/serializer.test.ts +++ b/src/serializer.test.ts @@ -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); + }); }); diff --git a/src/serializer.ts b/src/serializer.ts index 09c1d73..b8db9fd 100644 --- a/src/serializer.ts +++ b/src/serializer.ts @@ -62,22 +62,37 @@ function classname(obj: Object): string { return (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 => { $c: classname(value), $f: merge({}, value) }); + var fobjects = objects.map(value => { $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) };