This is an automated email from the ASF dual-hosted git repository.
rskraba pushed a commit to branch branch-1.11
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/branch-1.11 by this push:
new b9ca1b5e30 AVRO-2032: Add support for NaN, Infinity and -Infinity in
JsonDecoder (#3066)
b9ca1b5e30 is described below
commit b9ca1b5e30bb1205174ee65325c5e7771767b061
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 | 24 ++++++++++++
2 files changed, 68 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 c1c38511ab..e666647f18 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
@@ -185,6 +185,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");
}
@@ -197,11 +210,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 54fc420308..8b925aa14a 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
@@ -23,6 +23,8 @@ import org.apache.avro.generic.GenericRecord;
import org.junit.Assert;
import org.junit.Test;
+import java.io.IOException;
+
public class TestJsonDecoder {
@Test
@@ -75,4 +77,26 @@ public class TestJsonDecoder {
Assert.assertEquals(200, in.readLong());
in.skipArray();
}
+
+ @Test
+ public 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);
+ Assert.assertEquals(Float.NaN, r.get("nanFloat"));
+ Assert.assertEquals(Float.POSITIVE_INFINITY, r.get("infinityFloat"));
+ Assert.assertEquals(Float.NEGATIVE_INFINITY,
r.get("negativeInfinityFloat"));
+ Assert.assertEquals(Double.NaN, r.get("nanDouble"));
+ Assert.assertEquals(Double.POSITIVE_INFINITY, r.get("infinityDouble"));
+ Assert.assertEquals(Double.NEGATIVE_INFINITY,
r.get("negativeInfinityDouble"));
+ }
}