This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git
The following commit(s) were added to refs/heads/main by this push:
new 07f11467 fix(variant): emit valid JSON for non-finite floats in
to_json (#927)
07f11467 is described below
commit 07f114676066972523d791ea2f45aebaaa36bdee
Author: jackylee <[email protected]>
AuthorDate: Thu Sep 24 10:28:44 2026 +0800
fix(variant): emit valid JSON for non-finite floats in to_json (#927)
---
crates/paimon/src/variant.rs | 47 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 45 insertions(+), 2 deletions(-)
diff --git a/crates/paimon/src/variant.rs b/crates/paimon/src/variant.rs
index 5f4ed890..b7530841 100644
--- a/crates/paimon/src/variant.rs
+++ b/crates/paimon/src/variant.rs
@@ -1103,6 +1103,20 @@ fn get_decimal(value: &[u8], pos: usize) ->
Result<VariantDecimal> {
})
}
+/// A non-finite float/double serializes as a quoted `"Infinity"` /
+/// `"-Infinity"` / `"NaN"` token, matching Java `GenericVariant.toJson`
+/// (`appendQuoted(sb, Double.toString(d))`). Rust's `to_string` would emit a
+/// bare `inf`/`NaN`, which is not valid JSON and breaks any downstream parser.
+fn non_finite_json_token(d: f64) -> &'static str {
+ if d.is_nan() {
+ "\"NaN\""
+ } else if d > 0.0 {
+ "\"Infinity\""
+ } else {
+ "\"-Infinity\""
+ }
+}
+
fn write_json(value: &[u8], metadata: &[u8], pos: usize, out: &mut String) ->
Result<()> {
match value_kind(value, pos)? {
VariantKind::Object => {
@@ -1160,9 +1174,23 @@ fn write_json(value: &[u8], metadata: &[u8], pos: usize,
out: &mut String) -> Re
source: Some(Box::new(e)),
})?,
),
- VariantKind::Double => out.push_str(&get_double(value,
pos)?.to_string()),
+ VariantKind::Double => {
+ let d = get_double(value, pos)?;
+ if d.is_finite() {
+ out.push_str(&d.to_string());
+ } else {
+ out.push_str(non_finite_json_token(d));
+ }
+ }
VariantKind::Decimal => out.push_str(&get_decimal(value,
pos)?.to_plain_string()),
- VariantKind::Float => out.push_str(&get_float(value,
pos)?.to_string()),
+ VariantKind::Float => {
+ let f = get_float(value, pos)?;
+ if f.is_finite() {
+ out.push_str(&f.to_string());
+ } else {
+ out.push_str(non_finite_json_token(f as f64));
+ }
+ }
VariantKind::Binary => {
let encoded = general_purpose::STANDARD.encode(get_binary(value,
pos)?);
out.push_str(
@@ -3413,6 +3441,21 @@ mod tests {
);
}
+ #[test]
+ fn to_json_quotes_non_finite_floats_like_java() {
+ // A non-finite double/float must serialize as a quoted "Infinity" /
+ // "-Infinity" / "NaN" token, matching Java `GenericVariant.toJson`
+ // (`appendQuoted(sb, Double.toString(d))`). A bare `inf`/`NaN` from
+ // Rust's `to_string` is invalid JSON that breaks any downstream
parser.
+ let pos_inf = GenericVariant::parse_json("1e400").unwrap();
+ assert_eq!(pos_inf.to_json().unwrap(), r#""Infinity""#);
+ let neg_inf = GenericVariant::parse_json("-1e400").unwrap();
+ assert_eq!(neg_inf.to_json().unwrap(), r#""-Infinity""#);
+ // Finite values are unchanged (still bare JSON numbers).
+ let finite = GenericVariant::parse_json("1.5").unwrap();
+ assert_eq!(finite.to_json().unwrap(), "1.5");
+ }
+
#[test]
fn parse_json_rejects_duplicate_object_keys() {
let err = GenericVariant::parse_json(r#"{"a":1,"a":2}"#).unwrap_err();