JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.76k stars 3.25k forks source link

Deserialize decimal same code results not match #2928

Open HarveyJiang opened 8 months ago

HarveyJiang commented 8 months ago

Source/destination types

var str = JsonConvert.SerializeObject(new { Data = 422.3262M });

Source/destination JSON

{"message":"Place your serialized or deserialized JSON here"}

Expected behavior

output: {"Data":422.3262}

Actual behavior

output: {"Data":422.3219999999997}

Steps to reproduce

Newtonsoft.Json:13.0.2 NF4.8

Same code, var str = JsonConvert.SerializeObject(new { Data = 422.3262M }); var obj = JsonConvert.DeserializeObject(str);

Web application obj output: 422.3219999999997 wpf or console obj output:422.3262

but web application set FloatParseHandling =FloatParseHandling.Decimal,output :422.3262,

Why do the same code results not match?


var str = JsonConvert.SerializeObject(new { Data = 422.3262M });
JsonSerializerSettings setting = new JsonSerializerSettings();
setting.FloatParseHandling = FloatParseHandling.Decimal;
var obj = JsonConvert.DeserializeObject<object>(str, setting);

web app output error: web_error wfp output: wpf_ok

web application ok: web_ok

elgonzo commented 8 months ago

There is no error in the output of the web application.

Rather, the Intellisense output of the WPF app does not align with Newtonsoft.Json's default deserialization settings.

By default (unless configured otherwise), Newtonsoft.Json uses the FloatParseHandling.Double setting, which parses/deserializes floating point numbers as double values. But the value 422.3262 cannot be accurately represented by floating point types such as float or double. The value closest to 422.3262 that can be represented by a double value is 422.3219999999997.

This however poses the question of why you did not see 422.3219999999997 in the Intellisense inspector for your WPF app. I can only speculate here:

  • In your WPF app, you applied a global Newtonsoft.Json configuration with FloatParseHandling.Decimal through the JsonConvert.DefaultSettings somewhere in the app prior to OnStartup being invoked.
  • Or, the Intellisense inspector for the WPF app works slightly differently from the one used in the web app project, showing floating point numbers as rounded values, thus effectively not showing the real (System.Double) value.

I suggest you do a more fine-grained inspection of what Newtonsoft.Json did actually deserialize in your WPF app (and that bypasses possible Intellisense inspector presentation issues like the one i speculated about):

var jvalue = (JValue)((JObject)obj)["Data"];

var tokenType = jvalue.Type;
var valueType = jvalue.Value.GetType().FullName;
var fullPrecisionValue = $"{jvalue.Value:G17}";

If the Newtonsoft.Json default settings are used (i.e. no custom JsonSerializerSettings), the variables should contain the following:

tokenType: JTokenType.Float valueType: "System.Double" fullPrecisionValue: "422.32619999999997"

If the token type or the value type indicate a System.Decimal value being deserialized instead of a System.Double, then this is indicative of your WPF app having set up custom JsonSerializerSettings through JsonConvert.DefaultSettings prior to invoking OnStartup.

HarveyJiang commented 8 months ago

@elgonzo , Thank you for your reply。 (WPF、Console)、Web application global not set anything.

Currently, the issue is being fixed on the web by setting FloatParseHandling=FloatParseHandling.Decimal; Based on what considerations, double is used by default。

Thanks.

HarveyJiang commented 8 months ago

image

NF 4.8 , console output:422.3262, .net core 8 console output: 422.3219999999997, NF4.8 web application output: 422.3219999999997

var number = "422.3262";
double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double value);

It looks like it's due to different framework double.TryParse results are inconsistent;