Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parsed decimal losing trailing zeroes #2769

Merged
merged 2 commits into from Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
151 changes: 151 additions & 0 deletions Src/Newtonsoft.Json.Tests/Issues/Issue2768.cs
@@ -0,0 +1,151 @@
#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

#if !NET20
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Tests.Documentation.Samples.Serializer;
using System.Globalization;
#if DNXCORE50
using System.Reflection;
using Xunit;
using Test = Xunit.FactAttribute;
using Assert = Newtonsoft.Json.Tests.XUnitAssert;
#else
using NUnit.Framework;
#endif

namespace Newtonsoft.Json.Tests.Issues
{
[TestFixture]
public class Issue2768 : TestFixtureBase
{
[Test]
public void Test_Serialize()
{
decimal d = 0.0m;
string json = JsonConvert.SerializeObject(d);

Assert.AreEqual("0.0", json);
}

[Test]
public void Test_Serialize_NoTrailingZero()
{
decimal d = 0m;
string json = JsonConvert.SerializeObject(d);

Assert.AreEqual("0.0", json);
}

[Test]
public void Test_Deserialize()
{
decimal d = JsonConvert.DeserializeObject<decimal>("0.0");

Assert.AreEqual("0.0", d.ToString());
}

[Test]
public void Test_Deserialize_MultipleTrailingZeroes()
{
decimal d = JsonConvert.DeserializeObject<decimal>("0.00");

Assert.AreEqual("0.00", d.ToString());
}

[Test]
public void Test_Deserialize_NoTrailingZero()
{
decimal d = JsonConvert.DeserializeObject<decimal>("0");

Assert.AreEqual("0", d.ToString());
}

[Test]
public void ParseJsonDecimal()
{
var json = @"{ ""property"": 0.0 }";

var reader = new JsonTextReader(new StringReader(json))
{
FloatParseHandling = FloatParseHandling.Decimal
};

decimal? parsedDecimal = null;

while (reader.Read())
{
if (reader.TokenType == JsonToken.Float)
{
parsedDecimal = (decimal)reader.Value;
break;
}
}

Assert.AreEqual("0.0", parsedDecimal.ToString());
}

[Test]
public void ParseJsonDecimal_IsBoxedInstanceSame()
{
var json = @"[ 0.0, 0.0 ]";

var reader = new JsonTextReader(new StringReader(json))
{
FloatParseHandling = FloatParseHandling.Decimal
};

List<object> boxedDecimals = new List<object>();

// Start array
Assert.IsTrue(reader.Read());

Assert.IsTrue(reader.Read());
boxedDecimals.Add(reader.Value);

Assert.IsTrue(reader.Read());
boxedDecimals.Add(reader.Value);

Assert.IsTrue(reader.Read());
Assert.IsFalse(reader.Read());

// Boxed values will match or not depending on whether framework supports.
#if NET6_0_OR_GREATER
Assert.IsTrue(object.ReferenceEquals(boxedDecimals[0], boxedDecimals[1]));
#else
Assert.IsFalse(object.ReferenceEquals(boxedDecimals[0], boxedDecimals[1]));
#endif

}
}
}
#endif
38 changes: 37 additions & 1 deletion Src/Newtonsoft.Json/Utilities/BoxedPrimitives.cs
Expand Up @@ -23,6 +23,9 @@
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics;

namespace Newtonsoft.Json.Utilities
{
internal static class BoxedPrimitives
Expand Down Expand Up @@ -87,9 +90,42 @@ internal static class BoxedPrimitives
internal static readonly object Int64_7 = 7L;
internal static readonly object Int64_8 = 8L;

internal static object Get(decimal value) => value == decimal.Zero ? DecimalZero : value;
internal static object Get(decimal value)
{
// Decimals can contain trailing zeros. For example 1 vs 1.0. Unfortunatly, Equals doesn't check for trailing zeros.
// There isn't a way to find out if a decimal has trailing zeros in older frameworks without calling ToString.
JamesNK marked this conversation as resolved.
Show resolved Hide resolved
// Don't provide a cached boxed decimal value in older frameworks.

#if NET6_0_OR_GREATER
// Number of bits scale is shifted by.
const int ScaleShift = 16;

if (value == decimal.Zero)
{
Span<int> bits = stackalloc int[4];
int written = decimal.GetBits(value, bits);
MiscellaneousUtils.Assert(written == 4);

byte scale = (byte)(bits[3] >> ScaleShift);
// Only use cached boxed value if value is zero and there is zero or one trailing zeros.
if (scale == 0)
{
return DecimalZero;
}
else if (scale == 1)
{
return DecimalZeroWithTrailingZero;
}
}
#endif

return value;
}

#if NET6_0_OR_GREATER
private static readonly object DecimalZero = decimal.Zero;
private static readonly object DecimalZeroWithTrailingZero = 0.0m;
#endif

internal static object Get(double value)
{
Expand Down