Skip to content

Commit fe2a6ac

Browse files
author
Micah Zoltu
committed
Adds support for serializing bigints.
I went with serializing bigints as an object with a type and value, but I'm open to other ideas for how to serialize them so they are JSON compatible. I don't know why I needed to change the type of `setInCopy`, but the original type definition appears to have been incorrect. I largely don't understand what most of the properties in `parseProps` are for, so I just tried to copy other thinsg in a way that seemed reasonable. In particular, `editable` I set to `!forceReadonly` but this was really just a guess on my part. I made a few changes to improve the type strictness of `JSONValue`. If these strictness increases are considered undesirable then everything except the addition of `JSONBigint` can be removed without causing problems.
1 parent e4dd359 commit fe2a6ac

File tree

9 files changed

+136
-11
lines changed

9 files changed

+136
-11
lines changed

src/adapter/shared/renderer.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { createCommit, shouldFilter } from "../shared/traverse";
2020
import { PreactBindings, SharedVNode } from "../shared/bindings";
2121
import { inspectVNode } from "./inspectVNode";
2222
import { logVNode } from "../10/log";
23+
import { isSerializedBigint } from "../../view/components/sidebar/inspect/serializeProps";
2324

2425
export interface RendererConfig {
2526
Fragment: FunctionalComponent;
@@ -313,6 +314,9 @@ export function createRenderer<T extends SharedVNode>(
313314
},
314315
onUnmount,
315316
update(id, type, path, value) {
317+
if (isSerializedBigint(value)) {
318+
value = BigInt(value.value);
319+
}
316320
const vnode = getVNodeById(ids, id);
317321
if (vnode !== null) {
318322
if (bindings.isComponent(vnode)) {
@@ -343,6 +347,9 @@ export function createRenderer<T extends SharedVNode>(
343347
}
344348
},
345349
updateHook(id, index, value) {
350+
if (isSerializedBigint(value)) {
351+
value = BigInt(value.value);
352+
}
346353
const vnode = getVNodeById(ids, id);
347354
if (vnode !== null && bindings.isComponent(vnode)) {
348355
const c = bindings.getComponent(vnode);
@@ -357,6 +364,9 @@ export function createRenderer<T extends SharedVNode>(
357364
},
358365

359366
updateSignal(id, index, value) {
367+
if (isSerializedBigint(value)) {
368+
value = BigInt(value.value);
369+
}
360370
const vnode = getVNodeById(ids, id);
361371
if (vnode !== null && bindings.isComponent(vnode)) {
362372
const c = bindings.getComponent(vnode);

src/adapter/shared/serialize.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ describe("jsonify", () => {
3535
name: "Symbol(foo)",
3636
});
3737
});
38+
39+
it("should serialize bigints", () => {
40+
const data = { foo: 3n } as const;
41+
expect(jsonify(data, () => null, new Set())).to.deep.equal({
42+
foo: {
43+
type: "bigint",
44+
value: "3",
45+
},
46+
});
47+
});
3848
});
3949

4050
describe("cleanProps", () => {

src/adapter/shared/serialize.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ export function jsonify(
7979
switch (typeof data) {
8080
case "string":
8181
return data.length > 300 ? data.slice(300) : data;
82+
case "bigint":
83+
return {
84+
type: "bigint",
85+
value: data.toString(10),
86+
};
8287
case "function": {
8388
return {
8489
type: "function",
@@ -134,6 +139,7 @@ export function isEditable(x: any) {
134139
case "string":
135140
case "number":
136141
case "boolean":
142+
case "bigint":
137143
return true;
138144
default:
139145
return false;
@@ -153,7 +159,7 @@ function clone(value: any) {
153159
/**
154160
* Deeply set a property and clone all parent objects/arrays
155161
*/
156-
export function setInCopy<T = any>(
162+
export function setInCopy<T extends Record<string, unknown> = any>(
157163
obj: T,
158164
path: ObjPath,
159165
value: any,

src/view/components/DataInput/parseValue.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,28 @@ describe("parseValue", () => {
3131
expect(isNaN(parseValue("NaN") as any)).to.equal(true);
3232
});
3333

34+
it("should parse bigints", () => {
35+
expect(parseValue("5n")).to.deep.equal({
36+
type: "bigint",
37+
value: "5",
38+
});
39+
expect(parseValue("-5n")).to.deep.equal({
40+
type: "bigint",
41+
value: "-5",
42+
});
43+
// max int + 1
44+
expect(parseValue("18446744073709552000n")).to.deep.equal({
45+
type: "bigint",
46+
value: "18446744073709552000",
47+
});
48+
// bigger than Number.MAX_VALUE (max double)
49+
const reallyBig = (10n ** 309n).toString(10);
50+
expect(parseValue(`${reallyBig}n`)).to.deep.equal({
51+
type: "bigint",
52+
value: reallyBig,
53+
});
54+
});
55+
3456
it("should parse strings", () => {
3557
expect(parseValue('"abc"')).to.equal("abc");
3658
expect(parseValue('"123"')).to.equal("123");
@@ -65,6 +87,7 @@ describe("genPreview", () => {
6587
expect(genPreview("foo")).to.equal('"foo"');
6688
expect(genPreview([1, 2, { a: 3 }])).to.equal("[1, 2, {a: 3}]");
6789
expect(genPreview({ a: 123, b: [1, 2] })).to.equal("{a: 123, b: [1, 2]}");
90+
expect(genPreview({ type: "bigint", value: "3" })).to.equal("3n");
6891

6992
expect(genPreview({ type: "symbol", name: "Symbol(foo)" })).to.equal(
7093
"Symbol(foo)",

src/view/components/DataInput/parseValue.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export function parseValue(v: string) {
2424
throw new TypeError("Invalid input");
2525
} else if (/^[-+.]?\d*(?:[.]?\d*)$/.test(v)) {
2626
return Number(v);
27+
} else if (/^-?\d+n$/.test(v)) {
28+
return {
29+
type: "bigint",
30+
value: v.slice(0, -1),
31+
};
2732
} else if (/^\{.*\}$/.test(v) || /^\[.*\]$/.test(v)) {
2833
try {
2934
return JSON.parse(v);
@@ -65,6 +70,7 @@ export function genPreview(v: any): string {
6570
if (v.type === "blob") return "Blob {}";
6671
if (v.type === "symbol") return v.name;
6772
if (v.type === "html") return v.name;
73+
if (v.type === "bigint") return `${v.value}n`;
6874
}
6975

7076
const obj = Object.entries(v).map(x => {

src/view/components/sidebar/inspect/parseProps.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,27 @@ describe("parseProps", () => {
8787
]);
8888
});
8989

90+
it("should parse bigints", () => {
91+
const big = {
92+
type: "bigint",
93+
value: "3",
94+
};
95+
const tree = parseProps(big, "foo", 2);
96+
97+
expect(serialize(tree)).to.deep.equal([
98+
{
99+
editable: true,
100+
depth: 0,
101+
id: "foo",
102+
name: "foo",
103+
type: "bigint",
104+
value: big,
105+
children: [],
106+
meta: null,
107+
},
108+
]);
109+
});
110+
90111
it("should parse functions", () => {
91112
const fn = {
92113
type: "function",

src/view/components/sidebar/inspect/parseProps.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type PropDataType =
1616
| "symbol"
1717
| "html";
1818

19-
export interface PropData {
19+
export type PropData = {
2020
id: string;
2121
name: string;
2222
type: PropDataType;
@@ -25,7 +25,7 @@ export interface PropData {
2525
depth: number;
2626
meta: any;
2727
children: string[];
28-
}
28+
};
2929

3030
export function parseProps(
3131
data: any,
@@ -99,6 +99,22 @@ export function parseProps(
9999
children: [],
100100
meta: null,
101101
});
102+
} else if (
103+
// Same for bigints
104+
maybeCustom &&
105+
typeof data.value === "string" &&
106+
data.type === "bigint"
107+
) {
108+
out.set(path, {
109+
depth,
110+
name,
111+
id: path,
112+
type: "bigint",
113+
editable: !forceReadonly,
114+
value: data,
115+
children: [],
116+
meta: null,
117+
});
102118
} else if (
103119
// Same for vnodes
104120
maybeCustom &&

src/view/components/sidebar/inspect/serializeProps.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ describe("serializeProps", () => {
2626
it("should serialize functions", () => {
2727
expect(serializeProps({ type: "function", name: "foo" })).to.equal("foo()");
2828
});
29+
30+
it("should serialize bigints", () => {
31+
expect(serializeProps({ type: "bigint", value: "3" })).to.equal("3n");
32+
});
2933
});
Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
export interface JSONVNode {
2-
type: "vnode";
3-
name: string;
2+
readonly type: "vnode";
3+
readonly name: string;
44
}
55

66
export interface JSONFunction {
7-
type: "function";
8-
name: string;
7+
readonly type: "function";
8+
readonly name: string;
9+
}
10+
11+
export interface JSONBigint {
12+
readonly type: "bigint";
13+
readonly value: string;
914
}
1015

1116
export interface JSONSet {
12-
type: "set";
17+
readonly type: "set";
1318
}
1419

1520
export type JSONValue =
@@ -20,23 +25,47 @@ export type JSONValue =
2025
| undefined
2126
| JSONVNode
2227
| JSONFunction
23-
| Record<string, any>;
28+
| JSONBigint
29+
| { readonly [index: string]: JSONValue }
30+
| readonly JSONValue[];
2431

2532
export function serializeProps(value: JSONValue): any {
2633
if (Array.isArray(value)) {
2734
return value.map(serializeProps);
2835
} else if (
2936
value !== null &&
3037
typeof value === "object" &&
31-
Object.keys(value).length === 2
38+
Object.keys(value).length === 2 &&
39+
"type" in value &&
40+
typeof value.type === "string"
3241
) {
33-
if (typeof value.name === "string") {
42+
if ("name" in value && typeof value.name === "string") {
3443
if (value.type === "function") {
3544
return value.name + "()";
3645
} else if (value.type === "vnode") {
3746
return `<${value.name} />`;
3847
}
48+
} else if (
49+
"value" in value &&
50+
typeof value.value === "string" &&
51+
value.type === "bigint"
52+
) {
53+
return `${value.value}n`;
3954
}
4055
}
4156
return value;
4257
}
58+
59+
export function isSerializedBigint(value: unknown): value is JSONBigint {
60+
return (
61+
typeof value === "object" &&
62+
value !== null &&
63+
Object.keys(value).length === 2 &&
64+
"type" in value &&
65+
// @ts-ignore can remove after TypeScript 4.9.x: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#unlisted-property-narrowing-with-the-in-operator
66+
value.type === "bigint" &&
67+
"value" in value &&
68+
// @ts-ignore can remove after TypeScript 4.9.x: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html#unlisted-property-narrowing-with-the-in-operator
69+
typeof value.value === "string"
70+
);
71+
}

0 commit comments

Comments
 (0)