Home > front end >  Why does JsonConvert deserialize object fail with int but not long
Why does JsonConvert deserialize object fail with int but not long

Time:04-06

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.

  • Related