This is an automated email from the ASF dual-hosted git repository.

rskraba pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/main by this push:
     new 8a22cef7fb AVRO-2032: Add support for NaN, Infinity and -Infinity in 
JsonDecoder (#3066)
8a22cef7fb is described below

commit 8a22cef7fb532fa202e2175040f082d43c8e07c1
Author: Raphael Rösch <[email protected]>
AuthorDate: Wed Dec 3 20:39:35 2025 +0100

    AVRO-2032: Add support for NaN, Infinity and -Infinity in JsonDecoder 
(#3066)
    
    JsonEncoder uses special string values to represent NaN, Infinity and
    -Infinity values for float and double values, but JsonDecoder does not
    accept these string values. This change adds support for these special
    values to JsonDecoder.
---
 .../main/java/org/apache/avro/io/JsonDecoder.java  | 44 ++++++++++++++++++++++
 .../java/org/apache/avro/io/TestJsonDecoder.java   | 22 +++++++++++
 2 files changed, 66 insertions(+)

diff --git a/lang/java/avro/src/main/java/org/apache/avro/io/JsonDecoder.java 
b/lang/java/avro/src/main/java/org/apache/avro/io/JsonDecoder.java
index 64e7ba9faf..1876f87aaa 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/io/JsonDecoder.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/io/JsonDecoder.java
@@ -199,6 +199,19 @@ public class JsonDecoder extends ParsingDecoder implements 
Parser.ActionHandler
       float result = in.getFloatValue();
       in.nextToken();
       return result;
+    } else if (in.getCurrentToken() == JsonToken.VALUE_STRING) {
+      String stringValue = in.getText();
+      in.nextToken();
+      if (isNaNString(stringValue)) {
+        return Float.NaN;
+      }
+      if (isNegativeInfinityString(stringValue)) {
+        return Float.NEGATIVE_INFINITY;
+      }
+      if (isPositiveInfinityString(stringValue)) {
+        return Float.POSITIVE_INFINITY;
+      }
+      throw error("float");
     } else {
       throw error("float");
     }
@@ -211,11 +224,42 @@ public class JsonDecoder extends ParsingDecoder 
implements Parser.ActionHandler
       double result = in.getDoubleValue();
       in.nextToken();
       return result;
+    } else if (in.getCurrentToken() == JsonToken.VALUE_STRING) {
+      String stringValue = in.getText();
+      in.nextToken();
+      if (isNaNString(stringValue)) {
+        return Double.NaN;
+      }
+      if (isNegativeInfinityString(stringValue)) {
+        return Double.NEGATIVE_INFINITY;
+      }
+      if (isPositiveInfinityString(stringValue)) {
+        return Double.POSITIVE_INFINITY;
+      }
+      throw error("double");
     } else {
       throw error("double");
     }
   }
 
+  // check whether the given string represents an IEEE 754 'NaN' string value 
as
+  // serialized by Jackson
+  private static boolean isNaNString(String value) {
+    return "NaN".equals(value);
+  }
+
+  // check whether the given string represents an IEEE 754 'Infinity' string 
value
+  // as serialized by Jackson
+  private static boolean isPositiveInfinityString(String value) {
+    return "Infinity".equals(value) || "INF".equals(value);
+  }
+
+  // check whether the given string represents an IEEE 754 '-Infinity' string
+  // value as serialized by Jackson
+  private static boolean isNegativeInfinityString(String value) {
+    return "-Infinity".equals(value) || "-INF".equals(value);
+  }
+
   @Override
   public Utf8 readString(Utf8 old) throws IOException {
     return new Utf8(readString());
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java 
b/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java
index 1f44344e3a..03b79f8a5f 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/io/TestJsonDecoder.java
@@ -118,4 +118,26 @@ public class TestJsonDecoder {
     JsonDecoder decoder = DecoderFactory.get().jsonDecoder(schema, record);
     Assertions.assertThrows(AvroTypeException.class, () -> reader.read(null, 
decoder));
   }
+
+  @Test
+  void testIeee754SpecialCases() throws IOException {
+    String def = "{\"type\":\"record\",\"name\":\"X\",\"fields\": [" + 
"{\"type\":\"float\",\"name\":\"nanFloat\"},"
+        + "{\"type\":\"float\",\"name\":\"infinityFloat\"},"
+        + "{\"type\":\"float\",\"name\":\"negativeInfinityFloat\"}," + 
"{\"type\":\"double\",\"name\":\"nanDouble\"},"
+        + "{\"type\":\"double\",\"name\":\"infinityDouble\"},"
+        + "{\"type\":\"double\",\"name\":\"negativeInfinityDouble\"}" + "]}";
+    Schema schema = new Schema.Parser().parse(def);
+    DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);
+
+    String record = "{\"nanFloat\":\"NaN\", \"infinityFloat\":\"Infinity\", 
\"negativeInfinityFloat\":\"-Infinity\", "
+        + "\"nanDouble\":\"NaN\", \"infinityDouble\":\"Infinity\", 
\"negativeInfinityDouble\":\"-Infinity\"}";
+    Decoder decoder = DecoderFactory.get().jsonDecoder(schema, record);
+    GenericRecord r = reader.read(null, decoder);
+    assertEquals(Float.NaN, r.get("nanFloat"));
+    assertEquals(Float.POSITIVE_INFINITY, r.get("infinityFloat"));
+    assertEquals(Float.NEGATIVE_INFINITY, r.get("negativeInfinityFloat"));
+    assertEquals(Double.NaN, r.get("nanDouble"));
+    assertEquals(Double.POSITIVE_INFINITY, r.get("infinityDouble"));
+    assertEquals(Double.NEGATIVE_INFINITY, r.get("negativeInfinityDouble"));
+  }
 }

Reply via email to