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, bindgen_runtime::Null, check_status, sys, type_of, Error, JsObject, Result, Status, ValueType,
}; };
#[cfg(feature = "napi6")]
use super::BigInt;
use super::{FromNapiValue, Object, ToNapiValue}; use super::{FromNapiValue, Object, ToNapiValue};
impl ToNapiValue for Value { impl ToNapiValue for Value {
@ -111,14 +113,30 @@ impl FromNapiValue for Map<String, Value> {
impl ToNapiValue for Number { impl ToNapiValue for Number {
unsafe fn to_napi_value(env: sys::napi_env, n: Self) -> Result<sys::napi_value> { 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() { 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() { } else if n.is_f64() {
unsafe { f64::to_napi_value(env, n.as_f64().unwrap()) } unsafe { f64::to_napi_value(env, n.as_f64().unwrap()) }
} else { } else {
let n = n.as_u64().unwrap(); let n = n.as_u64().unwrap();
if n > u32::MAX as u64 { 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 { } else {
unsafe { u32::to_napi_value(env, n as u32) } 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 readPackageJson(): PackageJson␊
export function getPackageJsonName(packageJson: PackageJson): string␊ export function getPackageJsonName(packageJson: PackageJson): string␊
export function testSerdeRoundtrip(data: any): any␊ export function testSerdeRoundtrip(data: any): any␊
export function testSerdeBigNumberPrecision(number: string): any␊
export function returnFromSharedCrate(): Shared␊ export function returnFromSharedCrate(): Shared␊
export function contains(source: string, target: string): boolean␊ export function contains(source: string, target: string): boolean␊
export function concatStr(s: string): string␊ export function concatStr(s: string): string␊

View file

@ -90,6 +90,7 @@ import {
getStrFromObject, getStrFromObject,
returnJsFunction, returnJsFunction,
testSerdeRoundtrip, testSerdeRoundtrip,
testSerdeBigNumberPrecision,
createObjWithProperty, createObjWithProperty,
receiveObjectOnlyFromJs, receiveObjectOnlyFromJs,
dateToNumber, dateToNumber,
@ -495,6 +496,23 @@ test('serde-roundtrip', (t) => {
t.is(err!.message, 'JS symbols cannot be represented as a serde_json::Value') 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) => { test('buffer', (t) => {
let buf = getBuffer() let buf = getBuffer()
t.is(buf.toString('utf-8'), 'Hello world') t.is(buf.toString('utf-8'), 'Hello world')

View file

@ -185,6 +185,7 @@ export interface PackageJson {
export function readPackageJson(): PackageJson export function readPackageJson(): PackageJson
export function getPackageJsonName(packageJson: PackageJson): string export function getPackageJsonName(packageJson: PackageJson): string
export function testSerdeRoundtrip(data: any): any export function testSerdeRoundtrip(data: any): any
export function testSerdeBigNumberPrecision(number: string): any
export function returnFromSharedCrate(): Shared export function returnFromSharedCrate(): Shared
export function contains(source: string, target: string): boolean export function contains(source: string, target: string): boolean
export function concatStr(s: string): string 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 { fn test_serde_roundtrip(data: Value) -> Value {
data data
} }
#[napi]
fn test_serde_big_number_precision(number: String) -> Value {
let data = format!("{{\"number\":{}}}", number);
serde_json::from_str(&data).unwrap()
}