This is an automated email from the ASF dual-hosted git repository. rskraba pushed a commit to branch branch-1.12 in repository https://gitbox.apache.org/repos/asf/avro.git
commit 53e52f9d762152a1598b93e0e9b4a5aff9a8ab3d 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")); + } }
