Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
Michaël Lemaire | 95b3cf9fcc | ||
Michaël Lemaire | 9e0d1d623e |
10
README.md
10
README.md
|
@ -23,7 +23,15 @@ uncompressed.
|
||||||
In deno:
|
In deno:
|
||||||
|
|
||||||
```typescript
|
```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
|
||||||
|
<script type="module">
|
||||||
|
import { Serializer } from "https://js.thunderk.net/serializer/mod.js";
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
2
TODO.md
2
TODO.md
|
@ -1 +1,3 @@
|
||||||
|
# TODO
|
||||||
|
|
||||||
- Add protocol version, to handle breaking changes
|
- Add protocol version, to handle breaking changes
|
||||||
|
|
10
doc/usage.md
10
doc/usage.md
|
@ -3,7 +3,15 @@
|
||||||
In deno:
|
In deno:
|
||||||
|
|
||||||
```typescript
|
```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
|
||||||
|
<script type="module">
|
||||||
|
import { Serializer } from "https://js.thunderk.net/serializer/mod.js";
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import {
|
import {
|
||||||
|
describe,
|
||||||
expect,
|
expect,
|
||||||
it,
|
it,
|
||||||
mock,
|
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";
|
import { Serializer } from "./serializer.ts";
|
||||||
|
|
||||||
class TestSerializerObj1 {
|
class TestSerializerObj1 {
|
||||||
|
@ -62,142 +63,152 @@ function checkReversability(
|
||||||
return loaded;
|
return loaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
it("serializes simple objects", () => {
|
describe(Serializer, () => {
|
||||||
var obj = {
|
it("serializes simple objects", () => {
|
||||||
"a": 5,
|
var obj = {
|
||||||
"b": null,
|
"a": 5,
|
||||||
"c": [{ "a": 2 }, "test"],
|
"b": null,
|
||||||
"d": new Set([1, 4]),
|
"c": [{ "a": 2 }, "test"],
|
||||||
"e": new Map([["z", 8], ["t", 2]]),
|
"d": new Set([1, 4]),
|
||||||
};
|
"e": new Map([["z", 8], ["t", 2]]),
|
||||||
const result = checkReversability(obj);
|
};
|
||||||
expect(result["a"]).toBe(5);
|
const result = checkReversability(obj);
|
||||||
expect(result["b"]).toBe(null);
|
expect(result["a"]).toBe(5);
|
||||||
expect(result["c"][0]["a"]).toBe(2);
|
expect(result["b"]).toBe(null);
|
||||||
expect(result["d"].has(1)).toBe(true);
|
expect(result["c"][0]["a"]).toBe(2);
|
||||||
expect(result["d"].has(2)).toBe(false);
|
expect(result["d"].has(1)).toBe(true);
|
||||||
expect(result["e"].get("z")).toBe(8);
|
expect(result["d"].has(2)).toBe(false);
|
||||||
expect(result["e"].get("k")).toBeUndefined();
|
expect(result["e"].get("z")).toBe(8);
|
||||||
});
|
expect(result["e"].get("k")).toBeUndefined();
|
||||||
|
|
||||||
it("restores objects constructed from class", () => {
|
checkReversability(new Set(["a", new Set([1, "b"])]));
|
||||||
let loaded = checkReversability(new TestSerializerObj1(5));
|
checkReversability(new Map([["a", new Map([[1, "test"]])]]));
|
||||||
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", () => {
|
it("restores objects constructed from class", () => {
|
||||||
let serializer = new Serializer(TEST_NS);
|
let loaded = checkReversability(new TestSerializerObj1(5));
|
||||||
serializer.addIgnoredClass("TestSerializerObj1");
|
expect(loaded.a).toBe(5);
|
||||||
|
expect(loaded).toBeInstanceOf(TestSerializerObj1);
|
||||||
|
|
||||||
let data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
loaded = checkReversability(new TestSerializerObj1(5), TEST_NS_FLAT);
|
||||||
let loaded = serializer.unserialize(data);
|
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);
|
it("serializes reference to class type", () => {
|
||||||
serializer.addIgnoredClass(TestSerializerObj1);
|
let loaded = checkReversability(TestSerializerObj1);
|
||||||
|
expect(loaded).toBe(TestSerializerObj1);
|
||||||
|
});
|
||||||
|
|
||||||
data = serializer.serialize({ a: 5, b: new TestSerializerObj1() });
|
it("stores one version of the same object", () => {
|
||||||
loaded = serializer.unserialize(data);
|
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);
|
checkReversability(a, undefined, (loaded) => {
|
||||||
serializer.addIgnoredClass(TestSerializerObj1);
|
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() });
|
it("ignores some classes", () => {
|
||||||
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 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);
|
let loaded = serializer.unserialize(data);
|
||||||
expect(loaded).toEqual({ obj: { a: 0 } });
|
|
||||||
expect(mock_error).toHaveBeenCalledWith(
|
expect(loaded).toEqual({ a: 5, b: undefined });
|
||||||
"Can't find class",
|
|
||||||
"TestSerializerObj4",
|
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);
|
|
||||||
});
|
|
||||||
|
|
|
@ -41,18 +41,32 @@ export class Serializer {
|
||||||
// Collect objects
|
// Collect objects
|
||||||
var objects: Object[] = [];
|
var objects: Object[] = [];
|
||||||
var stats: any = {};
|
var stats: any = {};
|
||||||
|
|
||||||
|
function add(value: any): void {
|
||||||
|
if (objects.indexOf(value) < 0) {
|
||||||
|
objects.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
stats[vtype] = (stats[vtype] || 0) + 1;
|
stats[vtype] = (stats[vtype] || 0) + 1;
|
||||||
if (objects.indexOf(value) < 0) {
|
add(value);
|
||||||
objects.push(value);
|
|
||||||
}
|
|
||||||
return value;
|
return value;
|
||||||
} else {
|
} else {
|
||||||
return STOP_CRAWLING;
|
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 {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +77,8 @@ export class Serializer {
|
||||||
var fobjects = objects.map((value) => this.encodeObject(value));
|
var fobjects = objects.map((value) => this.encodeObject(value));
|
||||||
return JSON.stringify(fobjects, (key, value) => {
|
return JSON.stringify(fobjects, (key, value) => {
|
||||||
if (
|
if (
|
||||||
key != "$f" && isObject(value) && !value.hasOwnProperty("$c") &&
|
key != "$f" && (isObject(value) || typeof value == "function") &&
|
||||||
|
!value.hasOwnProperty("$c") &&
|
||||||
!value.hasOwnProperty("$i")
|
!value.hasOwnProperty("$i")
|
||||||
) {
|
) {
|
||||||
return { $i: objects.indexOf(value) };
|
return { $i: objects.indexOf(value) };
|
||||||
|
@ -132,6 +147,10 @@ export class Serializer {
|
||||||
$c: ctype,
|
$c: ctype,
|
||||||
$f: Array.from(obj),
|
$f: Array.from(obj),
|
||||||
};
|
};
|
||||||
|
} else if (typeof obj == "function") {
|
||||||
|
return {
|
||||||
|
$c: obj.name,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
$c: this.namespace_rev[ctype] || ctype,
|
$c: this.namespace_rev[ctype] || ctype,
|
||||||
|
@ -149,8 +168,10 @@ export class Serializer {
|
||||||
return new Set(objdata.$f);
|
return new Set(objdata.$f);
|
||||||
} else if (ctype == "Map") {
|
} else if (ctype == "Map") {
|
||||||
return new Map(objdata.$f);
|
return new Map(objdata.$f);
|
||||||
} else {
|
} else if (objdata.$f) {
|
||||||
return Object.assign(this.constructObject(ctype), 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
|
* Check if the argument is an instance of a class
|
||||||
*/
|
*/
|
||||||
function isObject(value: any): boolean {
|
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,
|
callback: (item: any) => any,
|
||||||
replace = false,
|
replace = false,
|
||||||
memo: any[] = [],
|
memo: any[] = [],
|
||||||
) {
|
): any {
|
||||||
if (isObject(obj)) {
|
if (isObject(obj)) {
|
||||||
if (memo.indexOf(obj) >= 0) {
|
if (memo.indexOf(obj) >= 0) {
|
||||||
return obj;
|
return obj;
|
||||||
|
@ -183,34 +205,50 @@ function crawl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obj !== undefined && obj !== null && typeof obj != "function") {
|
if (obj !== undefined && obj !== null) {
|
||||||
let result = callback(obj);
|
const result = callback(obj);
|
||||||
|
|
||||||
if (result === STOP_CRAWLING) {
|
if (result === STOP_CRAWLING) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
let subresult = obj.map((value) => crawl(value, callback, replace, memo));
|
const 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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (obj instanceof Set) {
|
} else if (obj instanceof Set) {
|
||||||
let subresult = new Set();
|
const subresult = new Set();
|
||||||
for (let item of obj) {
|
for (const item of obj) {
|
||||||
subresult.add(crawl(item, callback, replace, memo));
|
subresult.add(crawl(item, callback, replace, memo));
|
||||||
}
|
}
|
||||||
if (replace) {
|
if (replace) {
|
||||||
obj.clear();
|
obj.clear();
|
||||||
for (let item of subresult) {
|
for (const item of subresult) {
|
||||||
obj.add(item);
|
obj.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (obj instanceof Map) {
|
||||||
|
const subresult = new Map();
|
||||||
|
for (const [key, item] of obj.entries()) {
|
||||||
|
subresult.set(
|
||||||
|
crawl(key, callback, replace, memo),
|
||||||
|
crawl(item, callback, replace, memo),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (replace) {
|
||||||
|
obj.clear();
|
||||||
|
for (const [key, item] of subresult.entries()) {
|
||||||
|
obj.set(key, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (obj instanceof Object) {
|
} else if (obj instanceof Object) {
|
||||||
let subresult: any = {};
|
const subresult: any = {};
|
||||||
for (let key in obj) {
|
for (const key in obj) {
|
||||||
subresult[key] = crawl(obj[key], callback, replace, memo);
|
subresult[key] = crawl(obj[key], callback, replace, memo);
|
||||||
}
|
}
|
||||||
if (replace) {
|
if (replace) {
|
||||||
|
@ -228,5 +266,5 @@ function crawl(
|
||||||
* 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 obj.constructor.name;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue