Skip to content

Commit

Permalink
feat(utils): json big int support
Browse files Browse the repository at this point in the history
  • Loading branch information
ysfscream authored and Kinplemelon committed Sep 19, 2024
1 parent edb89c9 commit 73253d5
Show file tree
Hide file tree
Showing 4 changed files with 2,967 additions and 2,262 deletions.
73 changes: 72 additions & 1 deletion packages/utils/lib/__test__/jsonUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { parseJSONSafely, stringifyObjSafely, isJSONString } from '../jsonUtils'
import {
parseJSONSafely,
stringifyObjSafely,
isJSONString,
jsonBigIntParse,
jsonBigIntStringify,
} from '../jsonUtils'
import { describe, it, expect } from 'vitest'

describe('parseJSONSafely', () => {
Expand Down Expand Up @@ -54,3 +60,68 @@ describe('isJSONString', () => {
expect(output).toEqual(false)
})
})

describe('jsonBigIntParse', () => {
it('should parse regular JSON correctly', () => {
const json = '{"a": 1, "b": "string", "c": true}'
expect(jsonBigIntParse(json)).toEqual({ a: 1, b: 'string', c: true })
})

it('should parse large integers correctly', () => {
const json = '{"bigInt": 9007199254740991}'
const result = jsonBigIntParse(json)
expect(result.bigInt.toString()).toBe('9007199254740991')
})

it('should parse large floating-point numbers correctly', () => {
const json = '{"bigFloat": 1.2345e+100}'
const result = jsonBigIntParse(json)
expect(result.bigFloat.toString()).toBe('1.2345e+100')
})

it('should handle nested objects with big numbers', () => {
const json = '{"nested": {"bigInt": 9007199254740991, "normal": 42}}'
const result = jsonBigIntParse(json)
expect(result.nested.bigInt.toString()).toBe('9007199254740991')
expect(result.nested.normal).toBe(42)
})

it('should fall back to native JSON.parse for invalid JSON', () => {
const invalidJson = '{"invalid": undefined}'
expect(() => jsonBigIntParse(invalidJson)).toThrow()
})
})

describe('jsonBigIntStringify', () => {
it('should stringify regular objects correctly', () => {
const obj = { a: 1, b: 'string', c: true }
expect(jsonBigIntStringify(obj)).toBe('{"a":1,"b":"string","c":true}')
})

it('should stringify objects with large integers correctly', () => {
const obj = { bigInt: BigInt('9007199254740991') }
expect(jsonBigIntStringify(obj)).toBe('{"bigInt":9007199254740991}')
})

it('should stringify objects with large floating-point numbers correctly', () => {
const obj = { bigFloat: 1.2345e100 }
const result = jsonBigIntStringify(obj)
expect(result).toContain('1.2345e+100')
})

it('should handle nested objects with big numbers', () => {
const obj = { nested: { bigInt: BigInt('9007199254740991'), normal: 42 } }
expect(jsonBigIntStringify(obj)).toBe('{"nested":{"bigInt":9007199254740991,"normal":42}}')
})

it('should use space parameter for formatting', () => {
const obj = { a: 1, b: 2 }
expect(jsonBigIntStringify(obj, undefined, 2)).toBe('{\n "a": 1,\n "b": 2\n}')
})

it('should use replacer function', () => {
const obj = { a: 1, b: 2, c: 3 }
const replacer = (key: string, value: any) => (key === 'b' ? undefined : value)
expect(jsonBigIntStringify(obj, replacer)).toBe('{"a":1,"c":3}')
})
})
55 changes: 55 additions & 0 deletions packages/utils/lib/jsonUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import JSONBig from 'json-bigint'

/**
* Parses a JSON string safely and returns a JavaScript object.
* @param str - The JSON string to parse.
Expand Down Expand Up @@ -43,3 +45,56 @@ export const isJSONString = (str: string): boolean => {
return false
}
}

// JSONBig with default configuration, suitable for handling both integer and floating-point big numbers
const jsonBigNumber = JSONBig

// JSONBig configured to use native BigInt, optimized for integer-only big numbers
const jsonBigInt = JSONBig({
useNativeBigInt: true,
})

/**
* Parse JSON string with enhanced support for big numbers.
* @param value The string to parse as JSON
* @param reviver A function that transforms the results
* @returns Parsed JSON object
*/
export const jsonBigIntParse = (
value: string,
reviver?: (this: any, key: string, value: any) => any,
): any => {
try {
// Attempt to parse using native BigInt for integer-only JSON
return jsonBigInt.parse(value, reviver)
} catch {
try {
// If that fails, try parsing with BigNumber for floating-point numbers
return jsonBigNumber.parse(value, reviver)
} catch {
// If both custom parsers fail, fall back to native JSON.parse
return JSON.parse(value, reviver)
}
}
}

/**
* Stringify JSON with enhanced support for big numbers.
* @param value The value to convert to a JSON string
* @param replacer A function that alters the behavior of the stringification process
* @param space Adds indentation, white space, and line break characters to the return-value JSON text
* @returns JSON string
*/
export const jsonBigIntStringify = (
value: Record<string, unknown>,
replacer?: (this: any, key: string, value: any) => any | (number | string)[] | null,
space?: string | number,
): string => {
try {
// Attempt to stringify using native BigInt for integer-only JSON
return jsonBigInt.stringify(value, replacer as any, space)
} catch {
// If that fails, use BigNumber library (note: integers will be in scientific notation)
return jsonBigNumber.stringify(value, replacer as any, space)
}
}
2 changes: 2 additions & 0 deletions packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
"release": "npm publish"
},
"dependencies": {
"json-bigint": "^1.0.0",
"papaparse": "^5.4.1",
"vue-i18n": "^9.10.2"
},
"devDependencies": {
"@types/json-bigint": "^1.0.4",
"@types/papaparse": "^5.3.14"
}
}
Loading

0 comments on commit 73253d5

Please sign in to comment.