Al: Adding numeric values to JsonObject is resulting in strings, rather than numbers

Created on 6 Sep 2017  路  10Comments  路  Source: microsoft/AL

Write this code:

// JObj is JsonObject
JObj.Add('number', 3.1415926);
JObj.WriteTo(Str);
Message(Str);

When you run it, it results in this:

{"number":"3.1415926"}

However, if you do the same using Json.NET, the result is as expected:

{
"number": 3.1415926
}

bug shipped

Most helpful comment

I understand your point with strict following of the IEEE 754 standard, but that's neither how Newtonsoft.Json handles it, neither how our code is already working. With this behavior you are introducing a breaking change for existing applications, because we have absolutely no way of passing an actual decimal value into a JSON, lest by building string manually, rather than through JsonObject variable.

You know - we can only control one end - AL. On the other end there can be a web service listening, which expects a decimal, and we apparently do not have a way to pass a decimal.

I would agree with your choice to convert numbers to string (as the specification asks for) when the actual value you are passing into a JsonObject is of higher precision than can be represented by 64-bit data structure, but if you are passing a number that has an actual value that is of lower-or-equal precision, we should have a way of passing actual strong-typed numbers, not just encoded strings.

Another thing you can do is what Json.NET does by default: https://dotnetfiddle.net/owGCrT

And this is exact same behavior we have currently in 2017 (and earlier) with Json.NET (which we are using, as are you in the built-in NAV stack) - if you use JObject in C/AL as a DotNet variable, you get exactly the same behavior as this DotNetFiddle shows - numbers are truncated to maximum precision if they exceed 64-bit, but are kept as numbers.

If you are providing JsonObject and other types as replacement for our DotNet interop, then you should not break our code. If we had a possibility to actually insert an actual decimal into a JsonObject, I wouldn't complain, but now if I have to pass a decimal number into a web service call, then I can't use JSON api, but I must build string "on foot".

Please, don't break this for us.

@StanislawStempin

All 10 comments

Hi,

What is str if it is a string then you are writing it to a string so it is showing as a string.

Let me know if this is the case

Seriously, bachi1010? Please tell me you are not joking. What does the target type have to do with what the internal JSON type of a JSON value is?

Again, compare the (string) content of JObj from AL, and from C#, (white-space reformatted to make it ever more obvious):

  • AL: { "number" : "3.1415926" }

  • C#: { "number" : 3.1415926 }

The first one is obtained as WriteTo (with string as target), and the second one with ToString (which returns string). Both outputs are strings.

In the first example, the value of the "number" property is itself a string (even though a strong-typed decimal number was added to the JSON object), and in the first example it has retained it's decimal type.

By the way, if you also try adding another property, like this: JObj.Add('boolean', false), you will see that the output of JObj.WriteTo(str) will now contain the following: { "number" : "3.1415926", "boolean" : false }, with "number" being converted to a string, and "boolean" retaining it's original boolean type.

This is a bug.

This is a feature and not a bug. Here is the scenario:
In Javascript, and implicitly JSON, there is only support for 64-bit floating point numbers as specified by the IEEE 754 standard.
This is wonderful, when you are not working with financial data. In AL, we have Decimal and BigInteger, and both these types can contain values that cannot be represented exactly by 64-bit floating point numbers. At that point, the best way to preserve precision for these types was to serialize them to string.


Software which implements IEEE 754-2008 binary64 (double precision) numbers [IEEE754] is generally available and widely used.
Implementations which generate I-JSON messages cannot assume that  receiving implementations can process numeric values with greater
magnitude or precision than provided by those numbers.  I-JSON messages SHOULD NOT include numbers which express greater magnitude
or precision than an IEEE 754 double precision number provides, for example 1E400 or 3.141592653589793238462643383279.
An I-JSON sender cannot expect a receiver to treat an integer whose  absolute value is greater than 9007199254740991 (i.e., that is  outside the range [-(2**53)+1, (2**53)-1]) as an exact value.
For applications which require the exact interchange of numbers with  greater magnitude or precision, it is RECOMMENDED to encode them in JSON string values.

https://tools.ietf.org/html/draft-ietf-json-i-json-06

I understand your point with strict following of the IEEE 754 standard, but that's neither how Newtonsoft.Json handles it, neither how our code is already working. With this behavior you are introducing a breaking change for existing applications, because we have absolutely no way of passing an actual decimal value into a JSON, lest by building string manually, rather than through JsonObject variable.

You know - we can only control one end - AL. On the other end there can be a web service listening, which expects a decimal, and we apparently do not have a way to pass a decimal.

I would agree with your choice to convert numbers to string (as the specification asks for) when the actual value you are passing into a JsonObject is of higher precision than can be represented by 64-bit data structure, but if you are passing a number that has an actual value that is of lower-or-equal precision, we should have a way of passing actual strong-typed numbers, not just encoded strings.

Another thing you can do is what Json.NET does by default: https://dotnetfiddle.net/owGCrT

And this is exact same behavior we have currently in 2017 (and earlier) with Json.NET (which we are using, as are you in the built-in NAV stack) - if you use JObject in C/AL as a DotNet variable, you get exactly the same behavior as this DotNetFiddle shows - numbers are truncated to maximum precision if they exceed 64-bit, but are kept as numbers.

If you are providing JsonObject and other types as replacement for our DotNet interop, then you should not break our code. If we had a possibility to actually insert an actual decimal into a JsonObject, I wouldn't complain, but now if I have to pass a decimal number into a web service call, then I can't use JSON api, but I must build string "on foot".

Please, don't break this for us.

@StanislawStempin

By the way, the preservation of precision should be the decision of the caller, not receiver. We can always do:

JObj.Add("number", Format(A_Number_With_Huge_Precision_))

However, adding a number directly should retain (or truncate), but keep the strong type. You should give us a choice, not force us into real-world scenarios that we cannot support with JSON API.

I see that exactly like @vjekob

I agree with you both.
I assumed that, by default, precision is the most important and we should not lose any decimals by accident , but I understand the constraints that you are facing. I will fix this so that the default behavior is the one JSON.NET offers. If a developer cares about precision, he/she should use FORMAT to transform the decimal to a string before adding it to the JSON tree.
Thank you very much for the feedback!

Thanks very much for the solution :)

Thanks a lot! :)

Was this page helpful?
0 / 5 - 0 ratings