-
Notifications
You must be signed in to change notification settings - Fork 57
revisit: Support JSON serialisation of BigInt values #162
Comments
This proposal exposed a hook specifically for this purpose: BigInt.prototype.toJSON. Just set that to BigInt.prototype.toString to turn on this behavior. I think this option should be used with caution since it doesn't round-trip: JSON.parse of the serialized form will give you a Number. It's an important goal of this proposal to not implicitly round BigInt to Number, but permit explicit conversion. When you want to include BigInt in JSON, I would recommend using a library to do the serialization and deserialization, such as granola. |
no, JSON.parse would just return a string (copying behavior of Date): JSON.parse(JSON.stringify(new Date())) // '2018-07-28T09:41:47.519Z'
JSON.parse(JSON.stringify(1234n)) // '1234' or '1234n' this is good-enough guidance imo (and let user figure out how to revive BigInt, just like Date). i don't see a simpler, easier-to-use solution than Date-like behavior in the forseeable future. i just played with granola. seems to have bugs stringifying Set and Map. and not sure how to run it in the browser (where i can test BigInt). |
oh, figured out problem with granola (top-level must be object). still, i think having guidance to copy Date-like JSON-serialization behavior for BigInt makes better sense. |
Not sure what you mean by this. Under what conditions would the answer be It seems like we're coming down to a difference in goals: I have been trying to avoid implicit precision-loss, while permitting JSON serialization, and it sounds like you're OK with some implicit conversions but are more bothered by the JSON serialization behavior not being present by default. Is that accurate? |
i'll remove the confusing n-suffix proposal (don't care too much about it). maybe this will clarify? var aa;
// we copy JSON-behavior of Date
aa = 12345678901234567890n // <bigint primitive>
aa = JSON.stringify(aa) // '"12345678901234567890"'
aa = JSON.parse(aa) // '12345678901234567890'
aa = BigInt(aa) // <bigint primitive>
aa = new Date() // <Date object>
aa = JSON.stringify(aa) // '"2018-07-28T09:41:47.519Z"'
aa = JSON.parse(aa) // '2018-07-28T09:41:47.519Z'
aa = new Date(aa) // <Date object> |
OK, I see what you mean. I still would prefer not to go with those semantics by default, for the reason explained in #162 (comment) : Date will "round-trip" through that process accurately, whereas BigInt will be rounded, based on the implicit conversion to a number and back. |
i'm confused now by what you mean by rounded? there's no implicit conversion to number (or implicit precision-loss) for JSON.parse. it remains a string (with full-precision). and JSON-spec remains unchanged. |
just to clarify further: var aa;
// we copy JSON-behavior of Date
aa = 12345678901234567890n // <bigint primitive>
aa = JSON.stringify(aa) // '"12345678901234567890"' (escaped string)
aa = JSON.parse(aa) // '12345678901234567890' (un-escaped string)
aa = BigInt(aa) // <bigint primitive> (no precision-loss)
aa = new Date() // <Date object>
aa = JSON.stringify(aa) // '"2018-07-28T09:41:47.519Z"'' (escaped string)
aa = JSON.parse(aa) // '2018-07-28T09:41:47.519Z' (un-escaped string)
aa = new Date(aa) // <Date object> |
Ah, I see, you are proposing that it be a string that includes the quotes. Interesting idea. How about we consider this as a follow-on proposal, and experiment in userspace for now by overriding BigInt.prototype.toJSON? |
no. i dislike the current behavior (from web-integration perspective), and believe it should be a last-resort option, only if no palatable default-serialization solution is found. and i think Date's JSON-behavior is a palatable solution that is least offensive to everyone, unless someone can think of serious technical-faults for it. people are already familiar with Date's JSON-behavior, so it won't surprise anyone (or at least less-surprising than throwing an error). i agree that for the forseeable future, ecma-404 and JSON.parse should not change for stability-reasons. with that constraint, this is the best possible outcome i can think of for both BigInt and possible future primitives like BigDecimal. |
To put it differently: There is also a bunch of IETF standards using JSON structures like JOSE. As far as I know, none of them are incompatible with |
I think better to get this in and standarized now, rather than as a follow-on proposal. But not with a raw number string, with a number string with
I don't like to say things like "X should" without offering to help where possible, so if I can help, ping me to tell me how. |
@tjcrowder In this particular case tc39 is faced with a bunch of already established solutions so the choice(s) boils down to "best practices" rather than innovation or standardization. |
i also originally wanted @tjcrowder's slightly-modified form, but there was pushback from the champion on issue 160. |
i would be happy with either - anything is better than the current awful-behavior |
If we stick to compatibility with existing practices (why shouldn't we?), a That is, the default/standard |
Replaced by: https://github.com/cyberphone/es6-bigint-json-support#summary-of-changes Something along these lines could probably work: Serializing: // Standard mode
JSON.stringify({big: 555555555555555555555555555555n}); Expected result: // RFC mode
// Note: this could be achieved by an option forcing mandatory JSON Number notation
JSON.stringify({big: 555555555555555555555555555555n},
(k, v) => typeof v === 'bigint' ? JSONNumber(v) : v
); Expected result: Parsing: // Standard mode
JSON.parse('{"big": "555555555555555555555555555555"}',
(k, v) => k == 'big' ? BigInt(v) : v
); Expected result: // RFC mode
// Requires a new method (or option) which returns all numbers as JSONNumber
JSON.parse2('{"big": 555555555555555555555555555555}',
(k, v) => typeof v === 'jsonnumber' ? k == 'big' ? v.getBigInt() : v.getNumber() : v
); Expected result: The |
Looks like the conversation is sort of moving here so I'd like to chip in my latest reply in the es-discuss thread. https://esdiscuss.org/topic/json-support-for-bigint-in-chrome-v8#content-44
|
I'll continue to push for a more compliant JSON.parse alternative being introduced (perhaps its own proposal?). Parsing a JSON number as a JS number has always been a convenient lie and now we are paying the toll. JSONNumber would also pave the way for parsing BigDecimal as well (userland or not). Anyone currently dealing with big numbers serialized in environments with arbitrary precision (Python 3) and without the flexibility to receive them as strings has ditched JSON.parse for a smarter userland solution. As for serialization, BigInt should serialize just how a JSON number would (at least in a strict RFC mode). The fact that JSON.parse does not support it should be irrelevant, and a bigger sign JSONNumber parsing is needed. Not serializing at all is just avoiding the issue which is still present. |
That's a bit of a simplification. There is no other typed information exchange format that uses a single notation for everything that smells like a number, be it a bit or There are (AFAIK) no IETF standards using JSON structures exploiting JSON Number outside of JS Number.
I suggested introducing a |
I should clarify, I don't think having both |
The I believe developers will have to explicitly deal with non JS Numbers no matter what scheme we come up with. My "problem" with strict JSON is that on top of that, you introduce backward incompatibility with at least JS and with pretty limited gain. Neither There are some "unwanted side effects" as well: Oracle's JSON support in Java depends on |
Until proven wrong (I'm not infallible 😏), I believe the only point of disagreement is what the default serialization of a |
For stringifying: const numbers = {
a: 2,
b: 40000000000000000000000n
}
// returns -> '{ "a": 2, "b": 40000000000000000000000 }'
// NOTE: no "n" suffix
JSON.stringify(numbers, null, null, {
bigint: 'raw' // default setting
})
// returns -> '{ "a": 2, "b": '40000000000000000000000' }'
// NOTE: no "n" suffix
JSON.stringify(numbers, null, null, {
bigint: 'string'
}) For parsing: const numbers = '{ "a": 2, "b": 40000000000000000000000 }'
// returns -> {a: 2, b: 4e+22}
JSON.parse(numbers, null, {
bigint: 'never' // default setting
})
// returns -> { a: 2, b: 40000000000000000000000n }
JSON.parse(numbers, null, {
bigint: 'whenNeeded'
})
// returns -> { a: 2n, b: 40000000000000000000000n }
JSON.parse(numbers, null, {
bigint: 'always'
}) The extra nulls are due to existing optional params I only just found out about. |
Granted this does mean the deep clone idea needs to be changed to: JSON.parse(JSON.stringify(x), null, { bigint: 'whenNeeded' }) Another reason to support JSONNumber, for me. |
Does 'whenNeeded' mean that I wouldn't take this path. 'always' seems quite awkward as well, why would anybody want all numbers to be |
@cyberphone basically, yes. It is a flag for the programmer that any numbers they expect could be large (think twitter IDs) might need a I can't think of many use cases for 'always' either, it's just an option for cross property compatibility. Eg. |
@robjtede There seems to be multiple issues here. I don't see how you can use a future a Therefore I stick to my assertion that |
@robjtede your suggestion for modifying JSON.parse is too open-ended and high-maintennance. nobody benefits from the tooling-instability caused by a JSON.parse thats under constant maintennance and revisions by tc39 (which your suggestion would open the floodgates to). |
@kaizhu256 Which part is open ended? Whichever way it works, I'm highly in favor of a literal string-ification (no quotes or "n" suffix) by default with an option to have quotes. I believe that it should be as transparent as possible to start serializing and parsing BigInts in JS but flexible enough to fit most needs without having to resort to userland libraries. This is a primitive after all, not a class (like Date). |
In 4/5 it should correctly serialize to a valid JSON number (not a string). I also agree it should be default rather than throwing, but ultimately some native solution should exist. |
I strongly support keeping the default behavior (keeping the exception) which is what @bakkot suggested |
Since 2 already is fully supported "as is" (including all known and unknown variations), it doesn't appear to qualify as an action item anymore Since 3 is currently not known to be used anybody anywhere it can also safely be removed What's left is support for RFC compatible serialization and parsing |
Pardon for bringing up trivial stuff that probably already is somewhere in the documentation. Anyway, I tried creating a write-up for using quoted Base64Url-encoded There is apparently a |
@kaizhu256, I am reasonably confident this is the best solution, yes. I do not agree that silently doing something unexpected would be easier to debug than loudly throwing an error. @cyberphone, |
@bakkot Thanx! Since Base64/Base64Url is a very common format, would it be too much asking for a |
Patching already posted issue comments began to look awkward so I created a separate proposal: |
Minor note; unless the recipient can deal with bigints, it's not going to be able to run a script expecting bigints. If a script can expect to receive bigints, the the related JSON stringify can just generate bigints. Nothing now (before now) can run scripts the have bigints to require operating with bigints, so it shouldn't be receiving any JSON content that has bigints. if ( !bigint ) receive (bigint = fail) ; this can easily just be the parsing exception on the 1234n if just written as a number. if( bigint ) and not using bigint, JSON.stinrigfy that outputs like if( !bigint ) and not using bigint , JSON.parse would never expect to receive bad numbers. |
again, fundamental philosophical disagreement. javascript is not a language that prioritizes on writing correct-programs. no successful web-product, including facebook, amazon, etc would have shipped if their creators had focused on javascript-correctness ... they would have never launched. pretty much all successful javascript-products start off as steaming-piles of c***, that had to cut many painful corners to ship. their real-world focus has never been correctness, but trade-offs for speed-of-delivery and "good-enough" UX to execute their business model. and that is javascript's value to industy: a simple, high-productivity glue-language where one can quickly create good-enough product-demos to pitch to the market and fail-fast and re-iterate until something sticks. why waste 5x time-and-effort trying to architect "correct" javascript-programs, when you know very well its more than likely going to fail in the market, ending with you having to rewrite everything again? most real-world javascript projects are not high-quality projects. they are throwaway demos/mvps. and if javascript-developers primarily write disposable web-projects (statistically more likely to fail than succeed), how can they justify the cost of complicated, extra tooling/libraries to get JSON-support for bigint, when a default, zero-tooling quoted-string default-behavior is good-enough? |
@kaizhu256 AFAICT, the core of what has been requested is support for JSON Number formatting for The default mode of |
OK, it sounds like this thread has converged on the conclusion that the current JSON behavior is the right default, and we should consider possible follow-on proposals to make it easier to convert BigInts to JSON. Is this an accurate summary? |
Probably, although I can only speak for myself. Slight clarification: Follow-on proposals should deal with both Using the "quoted" mode (IMO) works sufficiently OK right out of the box using the toJSON scheme. Well, using the quite popular Base64Url notation is somewhat awkward but that is another issue and thread. |
i still strongly prefer option 2, quoted-string-mode (without n-suffix) as default. javascript is not a low-level general-purpose language like java/c++, and most web-developer are not expected/prioritized to waste their time, writing the kinds of low-level code expected from java/c++ developers. having tc39 force js-developers to implement their own JSON-solution for native primitives like BigInt, is the kind of low-level nuisance/tech-debt most web-developers would prefer not having to deal with, when they are already overwhelmed with so many high-level (and more business-critical) UX-issues requiring their attention in order to get their products shipped and sold, over their competitors. |
@kaizhu256 Just for my poor understanding: If the idea is that |
i believe we should resolve the Serialization matter now, because i'm skeptical tc39 will make good on its promise of a future standardization thats better/easier-to-use than whats currently available. 2-5 years from now, quoted-string-serialization will likely still be the least controversial/risky option for web-developers who want something both reliable and doesn't slowdown product-development with undue integration/tooling issues. its less harmful to industry to bless it now, then to have web-developers constantly bicker over custom toJSON implementations (especially when integrating products with 3rd parties). Deserialization could be left to a future standard, but again, i'm skeptical tc39 could deliver something actually user-friendly for web-developers. but thats ok, me and most ppl will likely be happy just using Date's well-known deserialization model (an extra-step constructor-call, requiring additional, contextual knowledge of serialized data). |
I decided to leave JSON alone since it is declared immutable. The RFC under parsers says It also adds a feature to define typed-objects, that is define the object and its fields, so later, that object It also adds an internal typed-array called 'ref' which defines the path to some previously existing object or array. The path consists of object property names and/or array indexes. This allows encoding cyclic data structures. Usage of either of the typed features generates output which is beyond the syntax handling of JSON. (as does Date and BigInt encoding, or any of the extended keywords). The typed object/array syntax also is not JS that can be interpreted with Github Repo ( RFC-like documentation ) So for compatibility with legacy JSON, BigInt serialization should probably just be a string, revived similarly to dates. |
I think @kaizhu256 is right that there's a good chance that we won't actually follow up in TC39 and make a built-in solution like JSONNumber. I'd like to spend some more time experimenting with solutions like @d3x0r 's JSOX and @jseijas 's vilanova for now, and we can see over time whether there is something we should standardize. At this point, we've looked into this topic thoroughly and have reaffirmed the current proposal's semantics, so closing this issue. |
@bakkot @littledan @kaizhu256 @ljharb The comparison with If you are considering other JSON solutions, many developers have found schema-driven designs like https://github.com/OAI (aka "Swagger") useful since they address messaging in a structured way while removíng the need for dealing with low-level operations of the kind discussed in this thread. These solutions use JSON "as is" and meet stringent requirements for passing data between applications. |
@bakkot This isn't correct as it doesn't work with negative numbers. At the moment, this spec does not provide any mechanism for serializing negative values to a byte stream. |
#24 mainly discussed literal JSON-serialization (and how it would break JSON-spec).
what about serializing to string-from, like Date? yes its one-way, but it provides needed guidance, and i'm skeptical we'll come up with a better solution 5 years from now.
The text was updated successfully, but these errors were encountered: