Skip to content

Commit

Permalink
AVRO-2032: [C#] Add support for NaN, Infinity and -Infinity in JsonDe…
Browse files Browse the repository at this point in the history
…coder (#3070)

* Add support for IEEE 754 strings in json decoder

* Remove redundent casting

---------

Co-authored-by: Zoltan Csizmadia <[email protected]>
  • Loading branch information
zcsizmadia and Zoltan Csizmadia authored Nov 22, 2024
1 parent b1ec3b9 commit 4f61326
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 4 deletions.
55 changes: 51 additions & 4 deletions lang/csharp/src/apache/main/IO/JsonDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,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");
}

/// <inheritdoc />
Expand All @@ -206,10 +221,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");
}

/// <inheritdoc />
Expand Down Expand Up @@ -766,6 +796,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);
Expand Down
54 changes: 54 additions & 0 deletions lang/csharp/src/apache/test/IO/JsonCodecTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,60 @@ 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", 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<GenericRecord> reader = new GenericDatumReader<GenericRecord>(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;
}
}
}

[TestCase("{ \"s\": \"1900-01-01T00:00:00Z\" }", "1900-01-01T00:00:00Z")]
[TestCase("{ \"s\": \"1900-01-01T00:00:00.0000000Z\" }", "1900-01-01T00:00:00.0000000Z")]
[TestCase("{ \"s\": \"1900-01-01T00:00:00\" }", "1900-01-01T00:00:00")]
Expand Down

0 comments on commit 4f61326

Please sign in to comment.