Javascript’s base type (POD) and JSON actually have only one numeric type: Number. Number is usually represented by the 64-bit floating-point standard in IEEE-754 in mainstream browser implementations (i.e., double-precision floating-point), which represents valid numbers in the range \(-(2^{53} - 1)\) ~ \(2^{53} - 1\)
. While 64-bit data types are often used in Go language, e.g., int64/uint64, such values are not safe to use in Javascript.
If you look at the JSON specification document, there is no limit to the number of bits, and any large value can be placed in JSON.
Behavior of json large numbers in Go
Actually tested with the Go language json
package, which also does output very large numbers directly
|
|
Output results.
These numbers are clearly larger than \(2^{53}\)
, and Go’s json package still encodes them directly in JSON, which is compliant with the specification.
Safe numbers in Javascript
The values above are not safe numbers in Javascript
As you can see, the first two sentences output false
; the third parse result is not equal to the original number directly.
A number is not safe in Javascript, which means data error, precision loss, and arithmetic error.
Another example is the following official MDN example.
|
|
Serializing large numbers with strings in Go
While JSON itself supports numbers of arbitrary size, JSON implementations do not necessarily support them. For example, JSON objects in browsers do not support it. So, in order to use JSON as a data interchange format safely across languages and platforms, data of types like int64 in Go should be encoded using strings.
Obviously, Go must have already taken this into account: all we need to do is to add a string
to the JSON TAG of the structure field, and that’s it.
Output results.
As you can see, the values are encoded as strings and are deserialized correctly.
One unfortunate thing is that this string
TAG is only for the following types: string, floating point, integer, boolean.
So N
in types like the following will not be stringified and need to do it themselves (not done in this case).
|
|
Output.
Deserialize to interface{}
There is another deserialization issue worth mentioning: when using interface{}
as the target store to be deserialized, the value type is float64 . This is like the behavior of a browser, and again may result in data loss (float64 is not large enough to hold int64)
Output results.
|
|
It is obvious that some data is missing (the original number has 19 valid digits, its floating point number has only 16 valid digits), but no error is reported.
One possible way to do this is to have json
use its json.Number
type to store the values.
|
|
Output results.
If this value is too large, Int64()
will report an error, Float64()
will not (a bit of a weird behavior)
|
|
Output results.
Or, can we simply determine Float64() == String()
?
Protocol Buffers’ handling of int64
As seen in golang/protobuf, Protocol Buffers treats all int64/uint64 serialized as strings by default.
Performance issues?
Some people may think that transferring values as strings is not a big performance impact?
I don’t think so, because we’re talking about JSON, and what’s the difference between a string and a value in JSON? It’s just that strings have two more quotes. Using a string to represent a numeric value takes up more memory, but at the transfer level, it’s just two more bytes. Theoretically, this has less impact on performance.