When testing av web API created in .Net 6.0, we found that when a user of the API sent decimal number on a int you got a 400 error stating that it fail to parse the json because of the decimal on the int value. But doing the same on a long value worked fine it just removed the decimal numbers.
So to test if this (guessing that MS uses Newonsoft.Json), I made a little cmd test app to test the scenario. And the same thing happens there long pareses loosing it decimals, and int fails.
So is this a bug in the parser or by design? [Edit] Should in not also fail on long?
using Newtonsoft.Json;
var data = JsonConvert.DeserializeObject<SomData>(@"{""aInt"":1, ""ALong"":2.2}");
Console.WriteLine(data.ALong); // output 2
var data2 = JsonConvert.DeserializeObject<SomData>(@"{""aInt"":1.2, ""ALong"":2}"); // exception
Console.WriteLine(data2.AInt);
internal class SomData
{
public int AInt { get; set; }
public long ALong { get; set; }
}
CodePudding user response:
The answer is by design. we can see this discussion Error converting float value to integer in Web API #654. The author's answer was below.
Later versions of Json.NET throw an error when deserializing a floating-point value onto an integer property.
I would always use decimal
when you are using floating-point numbers.
internal class SomData
{
public decimal AInt { get; set; }
public decimal ALong { get; set; }
}
EDIT
I had seen the source code of Json.Net
The Int value will will go into else
part as below code from JsonTextReader in ReadType.ReadAsInt32
which might as author say by design.
ParseResult parseResult = ConvertUtils.Int32TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out int value);
if (parseResult == ParseResult.Success)
{
numberValue = value;
}
else if (parseResult == ParseResult.Overflow)
{
throw ThrowReaderError("JSON integer {0} is too large or small for an Int32.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
else
{
throw ThrowReaderError("Input string '{0}' is not a valid integer.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
but let's see ReadType.ReadAsInt64 that else
part is a lot of different between ReadAsInt32
.
At First, it would get to else
let value(object
type) to store as float value as below code.
ParseResult parseResult = ConvertUtils.Int64TryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out long value);
if (parseResult == ParseResult.Success)
{
numberValue = value;
numberType = JsonToken.Integer;
}
else if (parseResult == ParseResult.Overflow)
{
#if HAVE_BIG_INTEGER
string number = _stringReference.ToString();
if (number.Length > MaximumJavascriptIntegerCharacterLength)
{
throw ThrowReaderError("JSON integer {0} is too large to parse.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
numberValue = BigIntegerParse(number, CultureInfo.InvariantCulture);
numberType = JsonToken.Integer;
#else
throw ThrowReaderError("JSON integer {0} is too large or small for an Int64.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
#endif
}
else
{
if (_floatParseHandling == FloatParseHandling.Decimal)
{
parseResult = ConvertUtils.DecimalTryParse(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length, out decimal d);
if (parseResult == ParseResult.Success)
{
numberValue = d;
}
else
{
throw ThrowReaderError("Input string '{0}' is not a valid decimal.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
}
else
{
string number = _stringReference.ToString();
if (double.TryParse(number, NumberStyles.Float, CultureInfo.InvariantCulture, out double d))
{
numberValue = d;
}
else
{
throw ThrowReaderError("Input string '{0}' is not a valid number.".FormatWith(CultureInfo.InvariantCulture, _stringReference.ToString()));
}
}
numberType = JsonToken.Float;
}
Then the number will be convert to Int64 by JsonSerializerInternalReader.EnsureType
// this won't work when converting to a custom IConvertible
return Convert.ChangeType(value, contract.NonNullableUnderlyingType, culture);
so we can get long
will not get execption but int
will, not sure why ReadAsInt64
allow store as float but int
was not.