Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utils): json big int support #61

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading