Skip to content

Commit 94a75e2

Browse files
committed
add JavaScriptCodec to serialize JavaScript data structure
1 parent 6e0069d commit 94a75e2

File tree

3 files changed

+100
-5
lines changed

3 files changed

+100
-5
lines changed

src/Encoder.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,14 @@ export class Encoder {
2424
throw new Error(`Too deep objects in depth ${depth}`);
2525
}
2626

27-
if (object == null) {
28-
this.encodeNil();
29-
} else if (typeof object === "boolean") {
27+
if (typeof object === "boolean") {
3028
this.encodeBoolean(object);
3129
} else if (typeof object === "number") {
3230
this.encodeNumber(object);
3331
} else if (typeof object === "string") {
3432
this.encodeString(object);
3533
} else {
36-
this.encodeObject(object, depth);
34+
this.encodeObjectOrNil(object, depth);
3735
}
3836
}
3937

@@ -164,11 +162,14 @@ export class Encoder {
164162
}
165163
}
166164

167-
encodeObject(object: unknown, depth: number) {
165+
encodeObjectOrNil(object: unknown, depth: number) {
168166
// try to encode objects with custom codec first of non-primitives
169167
const ext = this.extensionCodec.tryToEncode(object);
170168
if (ext != null) {
171169
this.encodeExtension(ext);
170+
} else if (object == null) {
171+
// `encodeNil()` must be here for extentionCodec to handle null / undefined.
172+
this.encodeNil();
172173
} else if (Array.isArray(object)) {
173174
this.encodeArray(object, depth);
174175
} else if (ArrayBuffer.isView(object)) {

src/JavaScriptCodec.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { ExtensionCodec, ExtensionCodecType } from "./ExtensionCodec";
2+
import { encode } from "./encode";
3+
import { decode } from "./decode";
4+
5+
export const JavaScriptCodecType = 0;
6+
7+
export function encodeJavaScriptData(input: unknown): Uint8Array | null {
8+
if (input == null) {
9+
// to distinguish undefined from null
10+
if (input === undefined) {
11+
return encode(["undefined"]);
12+
} else {
13+
return null; // fallback to MessagePack nil
14+
}
15+
} else if (input instanceof Map) {
16+
return encode(["Map", [...input]]);
17+
} else if (input instanceof Set) {
18+
return encode(["Set", [...input]]);
19+
} else if (input instanceof Date) {
20+
// Not a MessagePack timestamp because
21+
// it may be overrided by users
22+
return encode(["Date", input.getTime()]);
23+
} else if (input instanceof RegExp) {
24+
return encode(["RegExp", [input.source, input.flags]]);
25+
} else {
26+
return null;
27+
}
28+
}
29+
30+
export function decodeJavaScriptData(data: Uint8Array) {
31+
const [constructor, source] = decode(data) as [string, any];
32+
33+
switch (constructor) {
34+
case "undefined": {
35+
return undefined;
36+
}
37+
case "Map": {
38+
return new Map<unknown, unknown>(source);
39+
}
40+
case "Set": {
41+
return new Set<unknown>(source);
42+
}
43+
case "Date": {
44+
return new Date(source);
45+
}
46+
case "RegExp": {
47+
const [pattern, flags] = source;
48+
return new RegExp(pattern, flags);
49+
}
50+
default: {
51+
throw new Error(`Unknown constructor: ${constructor}`);
52+
}
53+
}
54+
}
55+
56+
export const JavaScriptCodec: ExtensionCodecType = (() => {
57+
const ext = new ExtensionCodec();
58+
59+
ext.register({
60+
type: JavaScriptCodecType,
61+
encode: encodeJavaScriptData,
62+
decode: decodeJavaScriptData,
63+
});
64+
65+
return ext;
66+
})();

test/javascript-codec.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import assert from "assert";
2+
import { encode, decode } from "@msgpack/msgpack";
3+
import { JavaScriptCodec } from "src/JavaScriptCodec";
4+
5+
describe("JavaScriptCodec", () => {
6+
context("mixed", () => {
7+
// this data comes from https://github.com/yahoo/serialize-javascript
8+
9+
it("encodes and decodes the object", () => {
10+
const object = {
11+
str: "string",
12+
num: 0,
13+
obj: { foo: "foo", bar: "bar" },
14+
arr: [1, 2, 3],
15+
bool: true,
16+
nil: null,
17+
undef: undefined,
18+
date: new Date("Thu, 28 Apr 2016 22:02:17 GMT"),
19+
map: new Map([["foo", 10], ["bar", 20]]),
20+
set: new Set([123, 456]),
21+
regexp: /foo\n/i,
22+
};
23+
const encoded = encode(object, { extensionCodec: JavaScriptCodec });
24+
25+
assert.deepStrictEqual(decode(encoded, { extensionCodec: JavaScriptCodec }), object);
26+
});
27+
});
28+
});

0 commit comments

Comments
 (0)