diff --git a/src/utils.ts b/src/utils.ts index a773431b..06185a6b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -18,8 +18,12 @@ export function isJSONSerializable(value: any): boolean { if (value === undefined) { return false; } + // `null` is JSON serializable (`JSON.stringify(null) === "null"`). + if (value === null) { + return true; + } const t = typeof value; - if (t === "string" || t === "number" || t === "boolean" || t === null) { + if (t === "string" || t === "number" || t === "boolean") { return true; } if (t !== "object") { diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 00000000..f3754934 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from "vitest"; +import { isJSONSerializable } from "../src/utils.ts"; + +describe("isJSONSerializable", () => { + it("treats primitives as serializable", () => { + expect(isJSONSerializable("foo")).toBe(true); + expect(isJSONSerializable(123)).toBe(true); + expect(isJSONSerializable(true)).toBe(true); + }); + + it("treats `null` as serializable without throwing", () => { + // Regression: previously this threw `Cannot read properties of null + // (reading 'buffer')` because `null` fell through to the `value.buffer` + // check. `JSON.stringify(null)` is valid (`"null"`). + // eslint-disable-next-line unicorn/no-null + expect(() => isJSONSerializable(null)).not.toThrow(); + // eslint-disable-next-line unicorn/no-null + expect(isJSONSerializable(null)).toBe(true); + }); + + it("treats `undefined` as non-serializable", () => { + expect(isJSONSerializable(undefined)).toBe(false); + }); + + it("treats plain objects and arrays as serializable", () => { + expect(isJSONSerializable({ a: 1 })).toBe(true); + expect(isJSONSerializable([1, 2, 3])).toBe(true); + }); + + it("treats objects with a `toJSON` method as serializable", () => { + expect(isJSONSerializable(new Date())).toBe(true); + }); + + it("treats non-serializable values as non-serializable", () => { + expect(isJSONSerializable(() => {})).toBe(false); + expect(isJSONSerializable(Symbol("x"))).toBe(false); + expect(isJSONSerializable(10n)).toBe(false); + expect(isJSONSerializable(new Uint8Array([1, 2, 3]))).toBe(false); + expect(isJSONSerializable(new FormData())).toBe(false); + expect(isJSONSerializable(new URLSearchParams())).toBe(false); + }); +});