From c5c040ab16a95d13be59a40a74ad820d1d4f7c44 Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Mon, 2 Sep 2024 15:47:51 +0100 Subject: [PATCH] Add `stringifyJsonWithBigInts` helper --- .../stringify-json-with-bigints-test.ts | 62 +++++++++++++++++++ .../src/stringify-json-with-bigints.ts | 22 +++++++ 2 files changed, 84 insertions(+) create mode 100644 packages/rpc-transport-http/src/__tests__/stringify-json-with-bigints-test.ts create mode 100644 packages/rpc-transport-http/src/stringify-json-with-bigints.ts diff --git a/packages/rpc-transport-http/src/__tests__/stringify-json-with-bigints-test.ts b/packages/rpc-transport-http/src/__tests__/stringify-json-with-bigints-test.ts new file mode 100644 index 000000000000..1140610ccac4 --- /dev/null +++ b/packages/rpc-transport-http/src/__tests__/stringify-json-with-bigints-test.ts @@ -0,0 +1,62 @@ +import { stringifyJsonWithBigints } from '../stringify-json-with-bigints'; + +const MAX_SAFE_INTEGER = BigInt(Number.MAX_SAFE_INTEGER); +const MAX_SAFE_INTEGER_PLUS_ONE = BigInt(Number.MAX_SAFE_INTEGER) + 1n; + +describe('stringifyJsonWithBigints', () => { + it.each` + input | expectedString + ${0n} | ${'0'} + ${-0n} | ${'0'} + ${1n} | ${'1'} + ${-1n} | ${'-1'} + ${42n} | ${'42'} + ${-42n} | ${'-42'} + ${100000n} | ${'100000'} + ${-100000n} | ${'-100000'} + ${123n * 10n ** 32n} | ${'12300000000000000000000000000000000'} + ${-123n * 10n ** 32n} | ${'-12300000000000000000000000000000000'} + ${MAX_SAFE_INTEGER} | ${MAX_SAFE_INTEGER.toString()} + ${MAX_SAFE_INTEGER_PLUS_ONE} | ${MAX_SAFE_INTEGER_PLUS_ONE.toString()} + `('strigifies bigint $input as a numerical value', ({ expectedString, input }) => { + expect(stringifyJsonWithBigints(input)).toBe(expectedString); + }); + it('strigifies BigInts within nested structures', () => { + const input = { + alice: 42n, + bob: [3.14, BigInt(3e8), { baz: 1234567890123456789012345678901234567890n }], + }; + expect(stringifyJsonWithBigints(input)).toBe( + '{"alice":42,"bob":[3.14,300000000,{"baz":1234567890123456789012345678901234567890}]}', + ); + }); + it.each` + input | expectedString + ${0.5} | ${'0.5'} + ${-0.5} | ${'-0.5'} + ${3.14159265} | ${'3.14159265'} + ${-3.14159265} | ${'-3.14159265'} + ${1e-5} | ${'0.00001'} + ${-1e-5} | ${'-0.00001'} + ${1e-32} | ${'1e-32'} + ${-1189e-32} | ${'-1.189e-29'} + `('strigifies number $input as a numerical value', ({ expectedString, input }) => { + expect(stringifyJsonWithBigints(input)).toBe(expectedString); + }); + it.each([ + null, + false, + true, + [], + [null, true, false], + {}, + { foo: 'bar' }, + 'Hello World', + '42 apples', + 'He said: "I will eat 3 bananas"', + { message_100: 'Hello to the 1st World' }, + { message_200: 'Hello to the "2nd World"' }, + ])('does not alter the value of $input', input => { + expect(stringifyJsonWithBigints(input)).toBe(JSON.stringify(input)); + }); +}); diff --git a/packages/rpc-transport-http/src/stringify-json-with-bigints.ts b/packages/rpc-transport-http/src/stringify-json-with-bigints.ts new file mode 100644 index 000000000000..9f8e30e7bbd4 --- /dev/null +++ b/packages/rpc-transport-http/src/stringify-json-with-bigints.ts @@ -0,0 +1,22 @@ +/** + * Transforms a value into a JSON string, whilst rendering bigints as large unsafe integers. + */ +export function stringifyJsonWithBigints(value: unknown, space?: number | string): string { + return unwrapBigIntValueObject( + JSON.stringify(value, (_, v) => (typeof v === 'bigint' ? wrapBigIntValueObject(v) : v), space), + ); +} + +type BigIntValueObject = { + // `$` implies 'this is a value object'. + // `n` implies 'interpret the value as a bigint'. + $n: string; +}; + +function wrapBigIntValueObject(value: bigint): BigIntValueObject { + return { $n: `${value}` }; +} + +function unwrapBigIntValueObject(value: string): string { + return value.replace(/\{\s*"\$n"\s*:\s*"(-?\d+)"\s*\}/g, '$1'); +}