This is an automated email from the ASF dual-hosted git repository.
Jefffrey pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/main by this push:
new f7f7545901 fix(arrow-json): render coerced f32 as its value in the
string decoder (#10386)
f7f7545901 is described below
commit f7f754590118d5dda59abbee961543758e236e3c
Author: Haresh Khanna <[email protected]>
AuthorDate: Mon Jul 20 12:52:10 2026 +0100
fix(arrow-json): render coerced f32 as its value in the string decoder
(#10386)
# Which issue does this PR close?
# Rationale for this change
When coercing a float into a string column, `StringArrayDecoder` ran a
`f32` through the integer formatter on its raw bits, so it rendered the
`u32` bit pattern instead of the value (e.g. `1.5f32` → `"1069547520"`).
The adjacent `f64` arm and `tape.rs` already reconstruct via
`from_bits`.
# What changes are included in this PR?
The `F32` arm of `StringArrayDecoder` now uses
`float_formatter.format_finite(f32::from_bits(n))`, matching the `f64`
arm. Plus a unit test.
# Are these changes tested?
Yes — new `test_serialize_f32_into_string` (fails before the fix, passes
after); the full `arrow-json` suite passes.
# Are there any user-facing changes?
Yes (bug fix): an `f32` coerced to string now renders its value, not its
bit pattern. No API changes. Only reachable via `Decoder::serialize`
with `coerce_primitive`; JSON text decoding was already correct.
---
arrow-json/src/reader/mod.rs | 15 +++++++++++++++
arrow-json/src/reader/string_array.rs | 2 +-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/arrow-json/src/reader/mod.rs b/arrow-json/src/reader/mod.rs
index c14c1f9ea0..ea4cbe2664 100644
--- a/arrow-json/src/reader/mod.rs
+++ b/arrow-json/src/reader/mod.rs
@@ -2903,6 +2903,21 @@ mod tests {
);
}
+ #[test]
+ fn test_serialize_f32_into_string() {
+ // Coercing an f32 into a string column must render the value, not its
raw bit pattern.
+ let field = Field::new("f", DataType::Utf8, true);
+ let mut decoder = ReaderBuilder::new_with_field(field)
+ .with_coerce_primitive(true)
+ .build_decoder()
+ .unwrap();
+ decoder.serialize(&[1.5_f32, -2.25_f32]).unwrap();
+ let batch = decoder.flush().unwrap().unwrap();
+ let values = batch.column(0).as_string::<i32>();
+ assert_eq!(values.value(0), "1.5");
+ assert_eq!(values.value(1), "-2.25");
+ }
+
// Parse the given `row` in `struct_mode` as a type given by fields.
//
// If as_struct == true, wrap the fields in a Struct field with name "r".
diff --git a/arrow-json/src/reader/string_array.rs
b/arrow-json/src/reader/string_array.rs
index 6b6abc21ed..42ace8ef7d 100644
--- a/arrow-json/src/reader/string_array.rs
+++ b/arrow-json/src/reader/string_array.rs
@@ -120,7 +120,7 @@ impl<O: OffsetSizeTrait> ArrayDecoder for
StringArrayDecoder<O> {
builder.append_value(int_formatter.format(n));
}
TapeElement::F32(n) if coerce_primitive => {
- builder.append_value(int_formatter.format(n));
+
builder.append_value(float_formatter.format_finite(f32::from_bits(n)));
}
TapeElement::F64(high) if coerce_primitive => match tape.get(p
+ 1) {
TapeElement::F32(low) => {