fix(napi): big numbers losing precision on serde_json::Value (#1538)

* fix(napi): big numbers losing precision on serde_json::Value

* fix(napi): add feature flags for bigint on number conversion

* chore(napi): change MAX_SAFE_INT to constant and convert big numbers to string when bigint is not available

* chore(napi): change how the check for safe integer range is made
This commit is contained in:
Gabriel Francisco 2023-03-23 02:34:34 -03:00 committed by GitHub
parent 2e865cad29
commit 3983be23f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 46 additions and 2 deletions

View file

@ -4,6 +4,8 @@ use crate::{
bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
};
#[cfg(feature = "napi6")]
use super::BigInt;
use super::{FromNapiValue, Object, ToNapiValue};
impl ToNapiValue for Value {
@ -111,14 +113,30 @@ impl FromNapiValue for Map<String, Value> {
impl ToNapiValue for Number {
unsafe fn to_napi_value(env: sys::napi_env, n: Self) -> Result<sys::napi_value> {
#[cfg(feature = "napi6")]
const MAX_SAFE_INT: i64 = 9007199254740991i64; // 2 ^ 53 - 1
if n.is_i64() {
unsafe { i64::to_napi_value(env, n.as_i64().unwrap()) }
let n = n.as_i64().unwrap();
#[cfg(feature = "napi6")]
{
if !(-MAX_SAFE_INT..=MAX_SAFE_INT).contains(&n) {
return unsafe { BigInt::to_napi_value(env, BigInt::from(n)) };
}
}
unsafe { i64::to_napi_value(env, n) }
} else if n.is_f64() {
unsafe { f64::to_napi_value(env, n.as_f64().unwrap()) }
} else {
let n = n.as_u64().unwrap();
if n > u32::MAX as u64 {
todo!("impl BigInt")
#[cfg(feature = "napi6")]
{
return unsafe { BigInt::to_napi_value(env, BigInt::from(n)) };
}
#[cfg(not(feature = "napi6"))]
return unsafe { String::to_napi_value(env, n.to_string()) };
} else {
unsafe { u32::to_napi_value(env, n as u32) }
}

View file

@ -195,6 +195,7 @@ Generated by [AVA](https://avajs.dev).
export function readPackageJson(): PackageJson␊
export function getPackageJsonName(packageJson: PackageJson): string␊
export function testSerdeRoundtrip(data: any): any␊
export function testSerdeBigNumberPrecision(number: string): any␊
export function returnFromSharedCrate(): Shared␊
export function contains(source: string, target: string): boolean␊
export function concatStr(s: string): string␊

View file

@ -90,6 +90,7 @@ import {
getStrFromObject,
returnJsFunction,
testSerdeRoundtrip,
testSerdeBigNumberPrecision,
createObjWithProperty,
receiveObjectOnlyFromJs,
dateToNumber,
@ -495,6 +496,23 @@ test('serde-roundtrip', (t) => {
t.is(err!.message, 'JS symbols cannot be represented as a serde_json::Value')
})
test('serde-large-number-precision', (t) => {
t.is(testSerdeBigNumberPrecision('12345').number, 12345)
t.is(
testSerdeBigNumberPrecision('123456789012345678901234567890').number,
1.2345678901234568e29,
)
t.is(
testSerdeBigNumberPrecision('123456789012345678901234567890.123456789')
.number,
1.2345678901234568e29,
)
t.is(
testSerdeBigNumberPrecision('109775245175819965').number.toString(),
'109775245175819965',
)
})
test('buffer', (t) => {
let buf = getBuffer()
t.is(buf.toString('utf-8'), 'Hello world')

View file

@ -185,6 +185,7 @@ export interface PackageJson {
export function readPackageJson(): PackageJson
export function getPackageJsonName(packageJson: PackageJson): string
export function testSerdeRoundtrip(data: any): any
export function testSerdeBigNumberPrecision(number: string): any
export function returnFromSharedCrate(): Shared
export function contains(source: string, target: string): boolean
export function concatStr(s: string): string

View file

@ -30,3 +30,9 @@ fn get_package_json_name(package_json: PackageJson) -> String {
fn test_serde_roundtrip(data: Value) -> Value {
data
}
#[napi]
fn test_serde_big_number_precision(number: String) -> Value {
let data = format!("{{\"number\":{}}}", number);
serde_json::from_str(&data).unwrap()
}