diff --git a/src/index.test.ts b/src/index.test.ts index 00917c97..78ffab54 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -676,6 +676,30 @@ describe('stringify & parse', () => { }, }, + 'works with BigInt typed arrays': { + input: { + a: new BigInt64Array([1n, 2n, -3n, 9007199254740993n]), + b: new BigUint64Array([0n, 18446744073709551615n]), + }, + output: { + // bigint isn't valid JSON, so values are stored as strings + a: ['1', '2', '-3', '9007199254740993'], + b: ['0', '18446744073709551615'], + }, + outputAnnotations: { + values: { + a: [['typed-array', 'BigInt64Array']], + b: [['typed-array', 'BigUint64Array']], + }, + }, + customExpectations: (value: any) => { + expect(value.a).toBeInstanceOf(BigInt64Array); + expect([...value.a]).toEqual([1n, 2n, -3n, 9007199254740993n]); + expect(value.b).toBeInstanceOf(BigUint64Array); + expect([...value.b]).toEqual([0n, 18446744073709551615n]); + }, + }, + 'works for undefined, issue #48': { input: undefined, output: null, diff --git a/src/is.ts b/src/is.ts index 438ea498..a2d7504d 100644 --- a/src/is.ts +++ b/src/is.ts @@ -79,7 +79,13 @@ export type TypedArrayConstructor = | Float32ArrayConstructor | Float64ArrayConstructor; -export type TypedArray = InstanceType; +export type BigIntTypedArrayConstructor = + | BigInt64ArrayConstructor + | BigUint64ArrayConstructor; + +export type TypedArray = InstanceType< + TypedArrayConstructor | BigIntTypedArrayConstructor +>; export const isTypedArray = (payload: any): payload is TypedArray => ArrayBuffer.isView(payload) && !(payload instanceof DataView); diff --git a/src/transformer.ts b/src/transformer.ts index 69dedef1..00062898 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -12,6 +12,7 @@ import { isError, isTypedArray, TypedArrayConstructor, + BigIntTypedArrayConstructor, isURL, } from './is.js'; import { findArr } from './util.js'; @@ -221,19 +222,41 @@ const constructorToName = [ return obj; }, {}); +// BigInt-backed typed arrays hold `bigint` elements, which JSON.stringify +// cannot represent. They're serialized/deserialized as strings instead. +const bigIntConstructorToName: Record = {}; +if (typeof BigInt64Array !== 'undefined') { + bigIntConstructorToName[BigInt64Array.name] = BigInt64Array; +} +if (typeof BigUint64Array !== 'undefined') { + bigIntConstructorToName[BigUint64Array.name] = BigUint64Array; +} + const typedArrayRule = compositeTransformation( isTypedArray, v => ['typed-array', v.constructor.name], - v => [...v].map(n => { - // Handle special float values that JSON.stringify converts to null - if (typeof n === 'number') { - if (Number.isNaN(n)) return 'NaN'; - if (n === Infinity) return 'Infinity'; - if (n === -Infinity) return '-Infinity'; - } - return n; - }), + v => + [...v].map(n => { + // bigint values (from BigInt64Array / BigUint64Array) are not valid JSON, + // so they're stored as strings. + if (typeof n === 'bigint') { + return n.toString(); + } + // Handle special float values that JSON.stringify converts to null + if (typeof n === 'number') { + if (Number.isNaN(n)) return 'NaN'; + if (n === Infinity) return 'Infinity'; + if (n === -Infinity) return '-Infinity'; + } + return n; + }), (v, a) => { + const bigIntCtor = bigIntConstructorToName[a[1]]; + if (bigIntCtor) { + const values = v.map((n: string | number | bigint): bigint => BigInt(n)); + return new bigIntCtor(values); + } + const ctor = constructorToName[a[1]]; if (!ctor) {