|
15 | 15 | use core::iter::FromIterator; |
16 | 16 | use std::borrow::Cow; |
17 | 17 |
|
| 18 | +#[cfg(feature = "arbitrary_precision")] |
| 19 | +use ethnum::i256; |
18 | 20 | use ordered_float::OrderedFloat; |
19 | 21 | use serde_json::Map as JsonMap; |
20 | 22 | use serde_json::Number as JsonNumber; |
21 | 23 | use serde_json::Value as JsonValue; |
22 | 24 |
|
23 | | -use super::number::Number; |
24 | | -use super::value::Object; |
25 | | -use super::value::Value; |
| 25 | +#[cfg(feature = "arbitrary_precision")] |
| 26 | +use crate::constants::DECIMAL128_MAX; |
| 27 | +#[cfg(feature = "arbitrary_precision")] |
| 28 | +use crate::constants::DECIMAL128_MIN; |
| 29 | +use crate::value::Object; |
| 30 | +use crate::value::Value; |
| 31 | +#[cfg(feature = "arbitrary_precision")] |
| 32 | +use crate::Decimal128; |
| 33 | +#[cfg(feature = "arbitrary_precision")] |
| 34 | +use crate::Decimal256; |
| 35 | +use crate::Number; |
26 | 36 |
|
27 | 37 | macro_rules! from_signed_integer { |
28 | 38 | ($($ty:ident)*) => { |
@@ -154,12 +164,37 @@ impl From<&JsonValue> for Value<'_> { |
154 | 164 | JsonValue::Null => Value::Null, |
155 | 165 | JsonValue::Bool(v) => Value::Bool(*v), |
156 | 166 | JsonValue::Number(v) => { |
157 | | - if v.is_u64() { |
158 | | - Value::Number(Number::UInt64(v.as_u64().unwrap())) |
159 | | - } else if v.is_i64() { |
160 | | - Value::Number(Number::Int64(v.as_i64().unwrap())) |
| 167 | + if let Some(n) = v.as_u64() { |
| 168 | + return Value::Number(Number::UInt64(n)); |
| 169 | + } else if let Some(n) = v.as_i64() { |
| 170 | + return Value::Number(Number::Int64(n)); |
| 171 | + } |
| 172 | + #[cfg(feature = "arbitrary_precision")] |
| 173 | + { |
| 174 | + if let Some(n) = v.as_i128() { |
| 175 | + if (DECIMAL128_MIN..=DECIMAL128_MAX).contains(&n) { |
| 176 | + return Value::Number(Number::Decimal128(Decimal128 { |
| 177 | + value: n, |
| 178 | + scale: 0, |
| 179 | + })); |
| 180 | + } else { |
| 181 | + return Value::Number(Number::Decimal256(Decimal256 { |
| 182 | + value: n.into(), |
| 183 | + scale: 0, |
| 184 | + })); |
| 185 | + } |
| 186 | + } else if let Some(n) = v.as_u128() { |
| 187 | + return Value::Number(Number::Decimal256(Decimal256 { |
| 188 | + value: n.into(), |
| 189 | + scale: 0, |
| 190 | + })); |
| 191 | + } |
| 192 | + } |
| 193 | + if let Some(n) = v.as_f64() { |
| 194 | + Value::Number(Number::Float64(n)) |
161 | 195 | } else { |
162 | | - Value::Number(Number::Float64(v.as_f64().unwrap())) |
| 196 | + // If the value is NaN or Infinity, fallback to NULL |
| 197 | + Value::Null |
163 | 198 | } |
164 | 199 | } |
165 | 200 | JsonValue::String(v) => Value::String(v.clone().into()), |
@@ -194,17 +229,43 @@ impl<'a> From<Value<'a>> for JsonValue { |
194 | 229 | Value::Null => JsonValue::Null, |
195 | 230 | Value::Bool(v) => JsonValue::Bool(v), |
196 | 231 | Value::Number(v) => match v { |
197 | | - Number::Int64(v) => JsonValue::Number(v.into()), |
198 | | - Number::UInt64(v) => JsonValue::Number(v.into()), |
199 | | - Number::Float64(v) => JsonValue::Number(JsonNumber::from_f64(v).unwrap()), |
200 | | - Number::Decimal64(v) => { |
201 | | - JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap()) |
| 232 | + Number::Int64(n) => JsonValue::Number(n.into()), |
| 233 | + Number::UInt64(n) => JsonValue::Number(n.into()), |
| 234 | + Number::Decimal64(d) if d.scale == 0 => JsonValue::Number(d.value.into()), |
| 235 | + #[cfg(feature = "arbitrary_precision")] |
| 236 | + Number::Decimal128(ref d) if d.scale == 0 => { |
| 237 | + if let Some(n) = JsonNumber::from_i128(d.value) { |
| 238 | + JsonValue::Number(n) |
| 239 | + } else if let Some(n) = JsonNumber::from_f64(v.as_f64()) { |
| 240 | + JsonValue::Number(n) |
| 241 | + } else { |
| 242 | + JsonValue::Null |
| 243 | + } |
202 | 244 | } |
203 | | - Number::Decimal128(v) => { |
204 | | - JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap()) |
| 245 | + #[cfg(feature = "arbitrary_precision")] |
| 246 | + Number::Decimal256(ref d) if d.scale == 0 => { |
| 247 | + if d.value >= i256::ZERO && d.value <= i256::from(u128::MAX) { |
| 248 | + if let Some(n) = JsonNumber::from_u128(d.value.as_u128()) { |
| 249 | + return JsonValue::Number(n); |
| 250 | + } |
| 251 | + } else if d.value >= i256::from(i128::MIN) && d.value < i256::ZERO { |
| 252 | + if let Some(n) = JsonNumber::from_i128(d.value.as_i128()) { |
| 253 | + return JsonValue::Number(n); |
| 254 | + } |
| 255 | + } |
| 256 | + if let Some(n) = JsonNumber::from_f64(v.as_f64()) { |
| 257 | + JsonValue::Number(n) |
| 258 | + } else { |
| 259 | + JsonValue::Null |
| 260 | + } |
205 | 261 | } |
206 | | - Number::Decimal256(v) => { |
207 | | - JsonValue::Number(JsonNumber::from_f64(v.to_float64()).unwrap()) |
| 262 | + _ => { |
| 263 | + if let Some(n) = JsonNumber::from_f64(v.as_f64()) { |
| 264 | + JsonValue::Number(n) |
| 265 | + } else { |
| 266 | + // If the value is NaN or Infinity, fallback to NULL |
| 267 | + JsonValue::Null |
| 268 | + } |
208 | 269 | } |
209 | 270 | }, |
210 | 271 | Value::String(v) => JsonValue::String(v.to_string()), |
@@ -249,3 +310,120 @@ impl<'a> From<Value<'a>> for JsonValue { |
249 | 310 | } |
250 | 311 | } |
251 | 312 | } |
| 313 | + |
| 314 | +#[cfg(test)] |
| 315 | +mod tests { |
| 316 | + #[cfg(feature = "arbitrary_precision")] |
| 317 | + use super::i256; |
| 318 | + use super::*; |
| 319 | + use serde_json::json; |
| 320 | + #[cfg(feature = "arbitrary_precision")] |
| 321 | + use serde_json::Number as JsonNumber; |
| 322 | + |
| 323 | + fn run_float_conversion_suite() { |
| 324 | + let finite_samples = [0.0, -1.5, 42.4242, 1.0e-10, 9_007_199_254_740_992.0]; |
| 325 | + |
| 326 | + for sample in finite_samples { |
| 327 | + let json_from_value = JsonValue::from(Value::from(sample)); |
| 328 | + match &json_from_value { |
| 329 | + JsonValue::Number(num) => { |
| 330 | + assert_eq!(num.as_f64(), Some(sample), "failed for {sample}"); |
| 331 | + } |
| 332 | + other => panic!("expected number for {sample}, got {other:?}"), |
| 333 | + } |
| 334 | + |
| 335 | + match Value::from(&json_from_value) { |
| 336 | + Value::Number(Number::Float64(value)) => { |
| 337 | + assert_eq!(value, sample, "round-trip mismatch for {sample}"); |
| 338 | + } |
| 339 | + other => panic!("expected float number for {sample}, got {other:?}"), |
| 340 | + } |
| 341 | + |
| 342 | + // Cover the direct JsonValue -> Value path using serde_json's json! macro. |
| 343 | + match Value::from(&json!(sample)) { |
| 344 | + Value::Number(Number::Float64(value)) => { |
| 345 | + assert_eq!(value, sample, "json! conversion mismatch for {sample}"); |
| 346 | + } |
| 347 | + other => panic!("expected float number for {sample}, got {other:?}"), |
| 348 | + } |
| 349 | + } |
| 350 | + |
| 351 | + for edge in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] { |
| 352 | + let json_value = JsonValue::from(Value::from(edge)); |
| 353 | + assert_eq!( |
| 354 | + json_value, |
| 355 | + JsonValue::Null, |
| 356 | + "non-finite value should map to null" |
| 357 | + ); |
| 358 | + } |
| 359 | + } |
| 360 | + |
| 361 | + #[test] |
| 362 | + #[cfg(feature = "arbitrary_precision")] |
| 363 | + fn float_conversions_with_arbitrary_precision() { |
| 364 | + run_float_conversion_suite(); |
| 365 | + } |
| 366 | + |
| 367 | + #[test] |
| 368 | + #[cfg(not(feature = "arbitrary_precision"))] |
| 369 | + fn float_conversions_without_arbitrary_precision() { |
| 370 | + run_float_conversion_suite(); |
| 371 | + } |
| 372 | + |
| 373 | + #[test] |
| 374 | + #[cfg(feature = "arbitrary_precision")] |
| 375 | + fn big_integer_conversion_suite() { |
| 376 | + let i128_samples = [DECIMAL128_MIN, DECIMAL128_MAX]; |
| 377 | + for sample in i128_samples { |
| 378 | + let json_value = JsonValue::Number(JsonNumber::from_i128(sample).unwrap()); |
| 379 | + match Value::from(&json_value) { |
| 380 | + Value::Number(Number::Decimal128(decimal)) => { |
| 381 | + assert_eq!( |
| 382 | + decimal.value, sample, |
| 383 | + "Decimal128 value mismatch for {sample}" |
| 384 | + ); |
| 385 | + assert_eq!(decimal.scale, 0, "Decimal128 scale mismatch for {sample}"); |
| 386 | + } |
| 387 | + other => panic!("expected Decimal128 for {sample}, got {other:?}"), |
| 388 | + } |
| 389 | + |
| 390 | + let json_from_value = JsonValue::from(Value::Number(Number::Decimal128(Decimal128 { |
| 391 | + value: sample, |
| 392 | + scale: 0, |
| 393 | + }))); |
| 394 | + |
| 395 | + assert_eq!( |
| 396 | + json_from_value.to_string(), |
| 397 | + sample.to_string(), |
| 398 | + "precise JSON mismatch for {sample}" |
| 399 | + ); |
| 400 | + } |
| 401 | + |
| 402 | + let u128_samples = [i128::MAX as u128, u128::MAX]; |
| 403 | + for sample in u128_samples { |
| 404 | + let json_value = JsonValue::Number(JsonNumber::from_u128(sample).unwrap()); |
| 405 | + match Value::from(&json_value) { |
| 406 | + Value::Number(Number::Decimal256(decimal)) => { |
| 407 | + assert_eq!( |
| 408 | + decimal.value, |
| 409 | + i256::from(sample), |
| 410 | + "Decimal256 value mismatch for {sample}" |
| 411 | + ); |
| 412 | + assert_eq!(decimal.scale, 0, "Decimal256 scale mismatch for {sample}"); |
| 413 | + } |
| 414 | + other => panic!("expected Decimal256 for {sample}, got {other:?}"), |
| 415 | + } |
| 416 | + |
| 417 | + let json_from_value = JsonValue::from(Value::Number(Number::Decimal256(Decimal256 { |
| 418 | + value: i256::from(sample), |
| 419 | + scale: 0, |
| 420 | + }))); |
| 421 | + |
| 422 | + assert_eq!( |
| 423 | + json_from_value.to_string(), |
| 424 | + sample.to_string(), |
| 425 | + "precise JSON mismatch for {sample}" |
| 426 | + ); |
| 427 | + } |
| 428 | + } |
| 429 | +} |
0 commit comments