From eaf4486d535eac26ea47064b77dd882de3806080 Mon Sep 17 00:00:00 2001 From: Zoltan Csizmadia Date: Tue, 6 Aug 2024 22:43:17 -0500 Subject: [PATCH 1/2] Add support for IEEE 754 strings in json decoder --- lang/csharp/src/apache/main/IO/JsonDecoder.cs | 57 +++++++++++++++++-- .../src/apache/test/IO/JsonCodecTests.cs | 55 ++++++++++++++++++ 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/lang/csharp/src/apache/main/IO/JsonDecoder.cs b/lang/csharp/src/apache/main/IO/JsonDecoder.cs index 48d726e3083..4c2a773ffeb 100644 --- a/lang/csharp/src/apache/main/IO/JsonDecoder.cs +++ b/lang/csharp/src/apache/main/IO/JsonDecoder.cs @@ -1,4 +1,4 @@ -/* +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -182,10 +182,25 @@ public override float ReadFloat() reader.Read(); return result; } - else + else if (reader.TokenType == JsonToken.String) { - throw TypeError("float"); + string str = Convert.ToString(reader.Value); + reader.Read(); + if (IsNaNString(str)) + { + return float.NaN; + } + else if (IsPositiveInfinityString(str)) + { + return float.PositiveInfinity; + } + else if (IsNegativeInfinityString(str)) + { + return float.NegativeInfinity; + } } + + throw TypeError("float"); } /// @@ -198,10 +213,25 @@ public override double ReadDouble() reader.Read(); return result; } - else + else if (reader.TokenType == JsonToken.String) { - throw TypeError("double"); + string str = Convert.ToString(reader.Value); + reader.Read(); + if (IsNaNString(str)) + { + return double.NaN; + } + else if (IsPositiveInfinityString(str)) + { + return double.PositiveInfinity; + } + else if (IsNegativeInfinityString(str)) + { + return double.NegativeInfinity; + } } + + throw TypeError("double"); } /// @@ -757,6 +787,23 @@ public override bool Read() } } + private bool IsNaNString(string str) + { + return str.Equals("NaN", StringComparison.Ordinal); + } + + private bool IsPositiveInfinityString(string str) + { + return str.Equals("Infinity", StringComparison.Ordinal) || + str.Equals("INF", StringComparison.Ordinal); + } + + private bool IsNegativeInfinityString(string str) + { + return str.Equals("-Infinity", StringComparison.Ordinal) || + str.Equals("-INF", StringComparison.Ordinal); + } + private AvroTypeException TypeError(string type) { return new AvroTypeException("Expected " + type + ". Got " + reader.TokenType); diff --git a/lang/csharp/src/apache/test/IO/JsonCodecTests.cs b/lang/csharp/src/apache/test/IO/JsonCodecTests.cs index 1c909275594..2c943fe520a 100644 --- a/lang/csharp/src/apache/test/IO/JsonCodecTests.cs +++ b/lang/csharp/src/apache/test/IO/JsonCodecTests.cs @@ -268,6 +268,61 @@ public void TestJsonDecoderNumeric(string type, object value) } } + [Test] + [TestCase("float", "0", (float)0)] + [TestCase("float", "1", (float)1)] + [TestCase("float", "1.0", (float)1.0)] + [TestCase("double", "0", (double)0)] + [TestCase("double", "1", (double)1)] + [TestCase("double", "1.0", (double)1.0)] + [TestCase("float", "\"NaN\"", float.NaN)] + [TestCase("float", "\"Infinity\"", float.PositiveInfinity)] + [TestCase("float", "\"INF\"", float.PositiveInfinity)] + [TestCase("float", "\"-Infinity\"", float.NegativeInfinity)] + [TestCase("float", "\"-INF\"", float.NegativeInfinity)] + [TestCase("double", "\"NaN\"", double.NaN)] + [TestCase("double", "\"Infinity\"", double.PositiveInfinity)] + [TestCase("double", "\"INF\"", double.PositiveInfinity)] + [TestCase("double", "\"-Infinity\"", double.NegativeInfinity)] + [TestCase("double", "\"-INF\"", double.NegativeInfinity)] + [TestCase("float", "\"\"", null)] + [TestCase("float", "\"unknown\"", null)] + [TestCase("float", "\"nan\"", null)] + [TestCase("float", "\"infinity\"", null)] + [TestCase("float", "\"inf\"", null)] + [TestCase("float", "\"-infinity\"", null)] + [TestCase("float", "\"-inf\"", null)] + [TestCase("double", "\"\"", null)] + [TestCase("double", "\"unknown\"", null)] + [TestCase("double", "\"nan\"", null)] + [TestCase("double", "\"infinity\"", null)] + [TestCase("double", "\"inf\"", null)] + [TestCase("double", "\"-infinity\"", null)] + [TestCase("double", "\"-inf\"", null)] + [TestCase("double", "\"-inf\"", null)] + public void TestJsonDecodeFloatDouble(string typeStr, string valueStr, object expected) + { + string def = $"{{\"type\":\"record\",\"name\":\"X\",\"fields\":[{{\"type\":\"{typeStr}\",\"name\":\"Value\"}}]}}"; + Schema schema = Schema.Parse(def); + DatumReader reader = new GenericDatumReader(schema, schema); + + string record = $"{{\"Value\":{valueStr}}}"; + Decoder decoder = new JsonDecoder(schema, record); + try + { + GenericRecord r = reader.Read(null, decoder); + Assert.AreEqual(expected, r["Value"]); + } + catch (AvroTypeException) + { + if (expected != null) + { + throw; + } + } + + } + // Ensure that even if the order of fields in JSON is different from the order in schema, it works. [Test] public void TestJsonDecoderReorderFields() From df711436a1f8ff8569cf28bb75178cff9b301f11 Mon Sep 17 00:00:00 2001 From: Zoltan Csizmadia Date: Tue, 6 Aug 2024 22:56:46 -0500 Subject: [PATCH 2/2] Remove redundent casting --- lang/csharp/src/apache/test/IO/JsonCodecTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/csharp/src/apache/test/IO/JsonCodecTests.cs b/lang/csharp/src/apache/test/IO/JsonCodecTests.cs index 2c943fe520a..958ead56aba 100644 --- a/lang/csharp/src/apache/test/IO/JsonCodecTests.cs +++ b/lang/csharp/src/apache/test/IO/JsonCodecTests.cs @@ -274,7 +274,7 @@ public void TestJsonDecoderNumeric(string type, object value) [TestCase("float", "1.0", (float)1.0)] [TestCase("double", "0", (double)0)] [TestCase("double", "1", (double)1)] - [TestCase("double", "1.0", (double)1.0)] + [TestCase("double", "1.0", 1.0)] [TestCase("float", "\"NaN\"", float.NaN)] [TestCase("float", "\"Infinity\"", float.PositiveInfinity)] [TestCase("float", "\"INF\"", float.PositiveInfinity)]