You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by gi...@apache.org on 2026/10/17 17:24:50 UTC
(arrow-rs) 01/01: fix(parquet-variant-json): preserve whole float formatting (#11513)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11513-d45aaf6b15d527de66ac38330a2e1c4f4f444541
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 6db2d4fd99c766ecd65a90419cea717ad5fbb619
Author: Peter L <ce...@hotmail.com>
AuthorDate: Sat Oct 17 17:24:23 2026 +0000
fix(parquet-variant-json): preserve whole float formatting (#11513)
# Which issue does this PR close?
- N/A
# Rationale for this change
`VariantToJson` writes `Variant::Float` and `Variant::Double` with
`Display`, which drops the fractional part of a whole-valued float.
`Variant::Double(2.0)` renders as `2`, and `Variant::Float(0.0)` as `0`.
The JSON then reads as an integer, so a consumer that distinguishes
integers from floats sees a different type, and a JSON string rebuilt
from a Variant differs from the JSON the Variant was parsed from.
# What changes are included in this PR?
- Float and double values are formatted with `ryu`, as `serde_json` and
`arrow-json` already do. `2.0` renders as `2.0`.
- `ryu` is added to `parquet-variant-json`. It is already a dependency
of `arrow-cast` and `arrow-json`, so the dependency tree does not
change.
`ryu` also uses exponent notation for very large and very small
magnitudes, for example `1e21` and `1e-7`, where `Display` writes all
the digits. Both forms are valid JSON.
# Are these changes tested?
Yes. The `to_json` tests now expect `0.0` for `Variant::Float(0.0)`, add
a `Variant::Double(2.0)` case, and check that an object with a
whole-valued double field renders it as `2.0`.
# Are there any significant user-facing changes?
Yes. The JSON text for whole-valued floats changes: `2.0` renders as
`2.0` instead of `2`. The value is unchanged.
Co-authored-by: Matthew Kim <38...@users.noreply.github.com>
---
Cargo.lock | 1 +
parquet-variant-json/Cargo.toml | 1 +
parquet-variant-json/src/to_json.rs | 22 +++++++++++++++++-----
3 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 045c6a552f..4d22fedb37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2678,6 +2678,7 @@ dependencies = [
"base64 0.23.1",
"chrono",
"parquet-variant",
+ "ryu",
"serde_json",
"uuid",
]
diff --git a/parquet-variant-json/Cargo.toml b/parquet-variant-json/Cargo.toml
index d2983f5794..d4f83f34f9 100644
--- a/parquet-variant-json/Cargo.toml
+++ b/parquet-variant-json/Cargo.toml
@@ -34,6 +34,7 @@ chrono = { workspace = true }
serde_json = "1.0"
base64 = "0.23"
uuid = "1.18.0"
+ryu = "1.0"
# uuid requires the `js` feature to run on wasm
[target.'cfg(target_arch = "wasm32")'.dependencies]
diff --git a/parquet-variant-json/src/to_json.rs b/parquet-variant-json/src/to_json.rs
index 5ae19aebb0..930ba935dc 100644
--- a/parquet-variant-json/src/to_json.rs
+++ b/parquet-variant-json/src/to_json.rs
@@ -175,8 +175,8 @@ impl VariantToJson for Variant<'_, '_> {
Variant::Int16(i) => write!(buffer, "{i}")?,
Variant::Int32(i) => write!(buffer, "{i}")?,
Variant::Int64(i) => write!(buffer, "{i}")?,
- Variant::Float(f) => write!(buffer, "{f}")?,
- Variant::Double(f) => write!(buffer, "{f}")?,
+ Variant::Float(f) => write!(buffer, "{}", ryu::Buffer::new().format(*f))?,
+ Variant::Double(f) => write!(buffer, "{}", ryu::Buffer::new().format(*f))?,
Variant::Decimal4(decimal) => write!(buffer, "{decimal}")?,
Variant::Decimal8(decimal) => write!(buffer, "{decimal}")?,
Variant::Decimal16(decimal) => write!(buffer, "{decimal}")?,
@@ -813,8 +813,10 @@ mod tests {
JsonTest {
variant: Variant::Float(0.0),
- expected_json: "0",
- expected_value: Value::Number(0.into()), // Use integer 0 to match JSON parsing
+ expected_json: "0.0",
+ expected_value: serde_json::Number::from_f64(0.0)
+ .map(Value::Number)
+ .unwrap(),
}
.run();
@@ -836,6 +838,15 @@ mod tests {
}
.run();
+ JsonTest {
+ variant: Variant::Double(2.0),
+ expected_json: "2.0",
+ expected_value: serde_json::Number::from_f64(2.0)
+ .map(Value::Number)
+ .unwrap(),
+ }
+ .run();
+
// Decimals
JsonTest {
variant: Variant::from(VariantDecimal4::try_new(12345, 2).unwrap()),
@@ -1209,7 +1220,7 @@ mod tests {
obj.insert("string_field", "test_string");
obj.insert("int_field", 123i32);
obj.insert("bool_field", true);
- obj.insert("float_field", 2.71f64);
+ obj.insert("float_field", 2.0f64);
obj.insert("null_field", ());
obj.insert("long_field", 999i64);
obj.finish();
@@ -1218,6 +1229,7 @@ mod tests {
let (metadata, value) = builder.finish();
let variant = Variant::try_new(&metadata, &value)?;
let json = variant.to_json_string()?;
+ assert!(json.contains(r#""float_field":2.0"#));
let parsed: Value = serde_json::from_str(&json).unwrap();
let obj = parsed.as_object().expect("expected JSON object");