diff --git a/README.md b/README.md
index dd49b35..7260bd5 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,15 @@ uncompressed.
In deno:
```typescript
-import { Serializer } from "https://code.thunderk.net/typescript/serializer/raw/branch/master/mod.ts";
+import { Serializer } from "https://js.thunderk.net/serializer/mod.ts";
+```
+
+In browser:
+
+```html
+
```
## Use
diff --git a/TODO.md b/TODO.md
index f321421..49a13f2 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1 +1,3 @@
+# TODO
+
- Add protocol version, to handle breaking changes
diff --git a/doc/usage.md b/doc/usage.md
index 1c265ca..3a9e9e4 100644
--- a/doc/usage.md
+++ b/doc/usage.md
@@ -3,7 +3,15 @@
In deno:
```typescript
-import { Serializer } from "https://code.thunderk.net/typescript/serializer/raw/branch/master/mod.ts";
+import { Serializer } from "https://js.thunderk.net/serializer/mod.ts";
+```
+
+In browser:
+
+```html
+
```
## Use
diff --git a/serializer.test.ts b/serializer.test.ts
index b3ed531..c49efbe 100644
--- a/serializer.test.ts
+++ b/serializer.test.ts
@@ -1,8 +1,9 @@
import {
+ describe,
expect,
it,
mock,
-} from "https://code.thunderk.net/typescript/devtools/raw/1.2.2/testing.ts";
+} from "https://code.thunderk.net/typescript/devtools/raw/1.3.0/testing.ts";
import { Serializer } from "./serializer.ts";
class TestSerializerObj1 {
@@ -62,145 +63,152 @@ function checkReversability(
return loaded;
}
-it("serializes simple objects", () => {
- var obj = {
- "a": 5,
- "b": null,
- "c": [{ "a": 2 }, "test"],
- "d": new Set([1, 4]),
- "e": new Map([["z", 8], ["t", 2]]),
- };
- const result = checkReversability(obj);
- expect(result["a"]).toBe(5);
- expect(result["b"]).toBe(null);
- expect(result["c"][0]["a"]).toBe(2);
- expect(result["d"].has(1)).toBe(true);
- expect(result["d"].has(2)).toBe(false);
- expect(result["e"].get("z")).toBe(8);
- expect(result["e"].get("k")).toBeUndefined();
+describe(Serializer, () => {
+ it("serializes simple objects", () => {
+ var obj = {
+ "a": 5,
+ "b": null,
+ "c": [{ "a": 2 }, "test"],
+ "d": new Set([1, 4]),
+ "e": new Map([["z", 8], ["t", 2]]),
+ };
+ const result = checkReversability(obj);
+ expect(result["a"]).toBe(5);
+ expect(result["b"]).toBe(null);
+ expect(result["c"][0]["a"]).toBe(2);
+ expect(result["d"].has(1)).toBe(true);
+ expect(result["d"].has(2)).toBe(false);
+ expect(result["e"].get("z")).toBe(8);
+ expect(result["e"].get("k")).toBeUndefined();
- checkReversability(new Set(["a", new Set([1, "b"])]));
- checkReversability(new Map([["a", new Map([[1, "test"]])]]));
-});
-
-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);
+ checkReversability(new Set(["a", new Set([1, "b"])]));
+ checkReversability(new Map([["a", new Map([[1, "test"]])]]));
});
-});
-it("ignores some classes", () => {
- let serializer = new Serializer(TEST_NS);
- serializer.addIgnoredClass("TestSerializerObj1");
+ it("restores objects constructed from class", () => {
+ let loaded = checkReversability(new TestSerializerObj1(5));
+ expect(loaded.a).toBe(5);
+ expect(loaded).toBeInstanceOf(TestSerializerObj1);
- let data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
- let loaded = serializer.unserialize(data);
+ loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_FLAT);
+ expect(loaded.a).toBe(5);
+ expect(loaded).toBeInstanceOf(TestSerializerObj1);
- expect(loaded).toEqual({ a: 5, b: undefined });
+ loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_RENAME);
+ expect(loaded.a).toBe(5);
+ expect(loaded).toBeInstanceOf(TestSerializerObj1);
+ });
- serializer = new Serializer(TEST_NS);
- serializer.addIgnoredClass(TestSerializerObj1);
+ it("serializes reference to class type", () => {
+ let loaded = checkReversability(TestSerializerObj1);
+ expect(loaded).toBe(TestSerializerObj1);
+ });
- data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
- loaded = serializer.unserialize(data);
+ 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);
+ });
- expect(loaded).toEqual({ a: 5, b: undefined });
+ it("handles circular references", () => {
+ var a: any = { b: {} };
+ a.b.c = a;
- serializer = new Serializer(TEST_NS_RENAME);
- serializer.addIgnoredClass(TestSerializerObj1);
+ 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);
+ });
+ });
- 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 = 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", () => {
- mock(console, "error", undefined, (mock_error) => {
+ it("ignores some classes", () => {
let serializer = new Serializer(TEST_NS);
- let data = serializer.serialize({ obj: new TestSerializerObj4() });
+ serializer.addIgnoredClass("TestSerializerObj1");
+
+ let data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
let loaded = serializer.unserialize(data);
- expect(loaded).toEqual({ obj: { a: 0 } });
- expect(mock_error).toHaveBeenCalledWith(
- "Can't find class",
- "TestSerializerObj4",
- );
+
+ 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", () => {
+ mock(console, "error", undefined, (mock_error) => {
+ 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(mock_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);
});
});
-
-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/serializer.ts b/serializer.ts
index d529e77..fda2cc0 100644
--- a/serializer.ts
+++ b/serializer.ts
@@ -41,18 +41,32 @@ export class Serializer {
// Collect objects
var objects: Object[] = [];
var stats: any = {};
+
+ function add(value: any): void {
+ if (objects.indexOf(value) < 0) {
+ objects.push(value);
+ }
+ }
+
crawl(obj, (value) => {
if (isObject(value)) {
var vtype = classname(value);
if (vtype != "" && this.ignored.indexOf(vtype) < 0) {
stats[vtype] = (stats[vtype] || 0) + 1;
- if (objects.indexOf(value) < 0) {
- objects.push(value);
- }
+ add(value);
return value;
} else {
return STOP_CRAWLING;
}
+ } else if (typeof value == "function") {
+ const found = Object.entries(this.namespace).find(([_, cons]) =>
+ cons === value
+ );
+ if (found && this.ignored.indexOf(found[0]) < 0) {
+ // Keep references to constructors in the namespace
+ add(value);
+ }
+ return STOP_CRAWLING;
} else {
return value;
}
@@ -63,7 +77,8 @@ export class Serializer {
var fobjects = objects.map((value) => this.encodeObject(value));
return JSON.stringify(fobjects, (key, value) => {
if (
- key != "$f" && isObject(value) && !value.hasOwnProperty("$c") &&
+ key != "$f" && (isObject(value) || typeof value == "function") &&
+ !value.hasOwnProperty("$c") &&
!value.hasOwnProperty("$i")
) {
return { $i: objects.indexOf(value) };
@@ -132,6 +147,10 @@ export class Serializer {
$c: ctype,
$f: Array.from(obj),
};
+ } else if (typeof obj == "function") {
+ return {
+ $c: obj.name,
+ };
} else {
return {
$c: this.namespace_rev[ctype] || ctype,
@@ -149,8 +168,10 @@ export class Serializer {
return new Set(objdata.$f);
} else if (ctype == "Map") {
return new Map(objdata.$f);
- } else {
+ } else if (objdata.$f) {
return Object.assign(this.constructObject(ctype), objdata.$f);
+ } else {
+ return this.namespace[ctype];
}
}
}
@@ -159,7 +180,8 @@ export class Serializer {
* Check if the argument is an instance of a class
*/
function isObject(value: any): boolean {
- return value instanceof Object && !Array.isArray(value);
+ return typeof value == "object" && value instanceof Object &&
+ !Array.isArray(value);
}
/**
@@ -174,7 +196,7 @@ function crawl(
callback: (item: any) => any,
replace = false,
memo: any[] = [],
-) {
+): any {
if (isObject(obj)) {
if (memo.indexOf(obj) >= 0) {
return obj;
@@ -183,11 +205,11 @@ function crawl(
}
}
- if (obj !== undefined && obj !== null && typeof obj != "function") {
+ if (obj !== undefined && obj !== null) {
const result = callback(obj);
if (result === STOP_CRAWLING) {
- return;
+ return undefined;
}
if (Array.isArray(obj)) {
@@ -244,5 +266,5 @@ function crawl(
* Get the class name of an object.
*/
function classname(obj: Object): string {
- return ( obj.constructor).name;
+ return obj.constructor.name;
}