Skip to content

Commit eb37834

Browse files
committed
fix: floating-point number serialization issue for non-normal values
1 parent 7923dd8 commit eb37834

File tree

2 files changed

+66
-5
lines changed

2 files changed

+66
-5
lines changed

YamlDotNet.Test/Serialization/SerializationTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,55 @@ public void NullsRoundTrip()
911911
result.MyString.Should().BeNull();
912912
}
913913

914+
[Fact]
915+
public void SerializationOfNumericsAsJsonRountTrip()
916+
{
917+
var serializer = new SerializerBuilder().JsonCompatible().Build();
918+
var deserializer = new DeserializerBuilder().Build();
919+
920+
var data = new
921+
{
922+
FloatValue1 = float.MinValue,
923+
FloatValue2 = float.MaxValue,
924+
FloatValue3 = float.NaN,
925+
FloatValue4 = float.PositiveInfinity,
926+
FloatValue5 = float.NegativeInfinity,
927+
FloatValue6 = 0.0f,
928+
DoubleValue1 = double.MinValue,
929+
DoubleValue2 = double.MaxValue,
930+
DoubleValue3 = double.NaN,
931+
DoubleValue4 = double.PositiveInfinity,
932+
DoubleValue5 = double.NegativeInfinity,
933+
DoubleValue6 = 3.0d,
934+
DecimalValue1 = decimal.MinValue,
935+
DecimalValue2 = decimal.MaxValue,
936+
DecimalValue3 = 1.234567890d,
937+
};
938+
939+
var json = serializer.Serialize(data);
940+
941+
#if NETFRAMEWORK
942+
json.Should().Contain("\"FloatValue3\": \"NaN\"");
943+
json.Should().Contain("\"FloatValue4\": \"Infinity\"");
944+
json.Should().Contain("\"FloatValue5\": \"-Infinity\"");
945+
946+
json.Should().Contain("\"DoubleValue3\": \"NaN\"");
947+
json.Should().Contain("\"DoubleValue4\": \"Infinity\"");
948+
json.Should().Contain("\"DoubleValue5\": \"-Infinity\"");
949+
#else
950+
// Run JSON roundtrip with System.Text.Json and Newtonsoft.Json
951+
var systemTextJson = System.Text.Json.JsonSerializer.Deserialize<System.Text.Json.JsonElement>(json, new System.Text.Json.JsonSerializerOptions { NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals }).ToString();
952+
var newtonsoftJson = Newtonsoft.Json.JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(json).ToString(Newtonsoft.Json.Formatting.None);
953+
954+
// Deserialize JSON with YamlDotNet
955+
var systemTextJsonResult = deserializer.Deserialize<Dictionary<string, object>>(systemTextJson);
956+
var newtonsoftJsonResult = deserializer.Deserialize<Dictionary<string, object>>(newtonsoftJson);
957+
958+
// Assert
959+
systemTextJsonResult.Should().BeEquivalentTo(newtonsoftJsonResult);
960+
#endif
961+
}
962+
914963
[Theory]
915964
[InlineData(typeof(SByteEnum))]
916965
[InlineData(typeof(ByteEnum))]

YamlDotNet/Serialization/EventEmitters/JsonEventEmitter.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// SOFTWARE.
2121

2222
using System;
23+
using System.Globalization;
2324
using System.Text.RegularExpressions;
2425
using YamlDotNet.Core;
2526
using YamlDotNet.Core.Events;
@@ -32,7 +33,6 @@ public sealed class JsonEventEmitter : ChainedEventEmitter
3233
private readonly YamlFormatter formatter;
3334
private readonly INamingConvention enumNamingConvention;
3435
private readonly ITypeInspector typeInspector;
35-
private static readonly Regex NumericRegex = new Regex(@"^-?\d+\.?\d+$", RegexOptions.Compiled);
3636

3737
public JsonEventEmitter(IEventEmitter nextEmitter, YamlFormatter formatter, INamingConvention enumNamingConvention, ITypeInspector typeInspector)
3838
: base(nextEmitter)
@@ -86,15 +86,27 @@ public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
8686
break;
8787

8888
case TypeCode.Single:
89-
case TypeCode.Double:
90-
case TypeCode.Decimal:
91-
eventInfo.RenderedValue = formatter.FormatNumber(value);
89+
var floatValue = (float)value;
90+
eventInfo.RenderedValue = floatValue.ToString("G", CultureInfo.InvariantCulture);
91+
if (float.IsNaN(floatValue) || float.IsInfinity(floatValue))
92+
{
93+
eventInfo.Style = ScalarStyle.DoubleQuoted;
94+
}
9295

93-
if (!NumericRegex.IsMatch(eventInfo.RenderedValue))
96+
break;
97+
98+
case TypeCode.Double:
99+
var doubleValue = (double)value;
100+
eventInfo.RenderedValue = doubleValue.ToString("G", CultureInfo.InvariantCulture);
101+
if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue))
94102
{
95103
eventInfo.Style = ScalarStyle.DoubleQuoted;
96104
}
105+
break;
97106

107+
case TypeCode.Decimal:
108+
var decimalValue = (decimal)value;
109+
eventInfo.RenderedValue = decimalValue.ToString(CultureInfo.InvariantCulture);
98110
break;
99111

100112
case TypeCode.String:

0 commit comments

Comments
 (0)