C# asp.net passing an object with a long property to front end changes it's value

Viewed 491

I'm building an application with a react.js front-end (although I'm fairly sure that's not relevant to the issue) and an asp.net back-end (targeting net5.0, not sure if that's relevant). When I call to the back-end API, I get back an object that includes, in part, an ID it generated based on the data passed in, that is of type long in C# (a 64 bit int). The behavior that I'm seeing is that the variable in C# and the variable read from the response on the front end are different. They appear to drift after about 16-17 digits.

Is this expected? Is there any way to get around it?

Code to reproduce / Pictures of what I see:

C#

[HttpPost]
[Route("test")]
public object TestPassingLongInObject()
{
    /* actual logic omitted for brevity */

    var rv = new
    {
        DataReadIn = new { /* omitted */ },
        ValidationResult = new ValidationResult(),
        GeneratedID =  long.Parse($"922337203{new Random().Next(int.MaxValue):0000000000}") // close to long.MaxValue
    };

    Console.WriteLine($"ID in C# controller method: {rv.GeneratedID}");

    return rv;
}

Console output: ID in C# controller method: 9223372030653055062

Chrome Dev Tools:

Response seen by Chrome dev tools

When I try to access the ID on the front-end I get the incorrect ID ending in 000 instead of 062.

Edit 1: It's been suggested that this is because JavaScript's Number.MAX_SAFE_INTEGER is less than the value I'm passing. I don't believe that is the reason, but perhaps I'm wrong and someone can enlighten me. On the JS side, I'm using BigInt, precisely because the number I'm passing is too large for Number. The issue is before I'm parsing the result to a JS object though (unless Chrome is doing that automatically, leading to the picture referenced in the issue).

Edit 2: Based on the answers below, it looks like perhaps JS is parsing the value to a number before I'm parsing it to a BigInt, so I'm still losing precision. Can someone more familiar with the web confirm this?

4 Answers

JavaScript is interpreting that value as a number which is a 64-bit floating-point number. If you want to keep that value, you're better off passing it back as a string.

Example JS to demonstrate the problem:

var value = 9223372030653055062;
console.log(value);
console.log(typeof(value));

JavaScript engines will (apparently) fail to create numbers this big. Try to open your console and enter the following:

var myLong = 9223372030653055062;

console.log(myLong);

You can check 9223372030653055 > Number.MAX_SAFE_INTEGER as well, as a pointer that you're going out of bounds.

The maximum size of a Number in Javascript is 2^53 - 1 which is 9007199254740991. In C# a long is 2^64 - 1, which is bigger. So "9223372030653055062" works as a C# long but not as a JavaScript Number because it is too big. If this ID is being used in a database using a long, I'd suggest you just pass it as a string to JavaScript.

Although the reason is that the number is bigger than JS can accurately represent, you seem to have been distracted be Chrome's Dev Tools Preview. This is only a "helpful" preview and not necessarily the truth.

The dev tools preview shows a helpful view of the response. I presume the AJAX response is transferred with content type of application/json, so Chrome helpfully parses the JSON to a JS object and lets you preview the response. Check the Response tab, this is what will actually be received by your AJAX code.

The chances are that the JSON is being parsed before you have chance to use BigInt on that number, capping the precision. You haven't shown us your AJAX code so this is my best guess.

The only solution would be to serialize it as a string then add a manual step to convert the string to BigInt.

jsObject.generatedId = BigInt(jsObject.generatedId);

If you're using it as an ID then you might as well keep it as a string on the client-side as you're not likely to be using it to do any calculations.

Related