tc39 / proposal-json-parse-with-source

Proposal for extending JSON.parse to expose input source text.
https://tc39.github.io/proposal-json-parse-with-source
MIT License
211 stars 9 forks source link

A polyfill/ponyfill implementation #40

Open slavafomin opened 1 year ago

slavafomin commented 1 year ago

Hello!

Thank you for this proposal and your hard work!

While waiting for the proposal to be generally available, I've implemented a custom JSON parser that is future-compatible with the proposal. It doesn't look feasible to make it a proper polyfill, considering that JS implementation will probably be slower and we can't detect new features usage, but it's a ponyfill that can be used today where needed.

I'm not sure that I've implemented all the features correctly, it's not that easy to follow the standard, but all the serialization examples should work correctly. I will be very grateful if someone could review the implementation.

Thanks!

ljharb commented 1 year ago

You should certainly be able to detect this feature, since it’s API and not syntax.

slavafomin commented 1 year ago

You should certainly be able to detect this feature, since it’s API and not syntax.

Could you recommend an approach to do this?

Maybe I don't see something obvious here, but as far as I know JS doesn't have a reflection API that will allow me to examine the number of arguments of the passed callback function. Of course I can turn the function to a string and then parse it's code in order to detect whether it has a third argument, but it looks pretty brittle. And this definitely won't work with the arguments.

In general, the detection could only happen when the user accesses the context.source or context.keys properties of the reviver function argument, but when this happens it's already too late to change the underlying parser implementation.

The only approach I see is that we can use JSON.parse initially and then discard the parsed result and re-parse the content using our custom implementation when we detect access to the context argument. This will lead to parsing the JSON document two times in a worst-case scenario and we will need to count the number of callbacks that was already fired in order to not call reviver multiple times for the same property.

This option is viable, but looks too hacky and not optimal to me.

slavafomin commented 1 year ago

@gibson042 @mathiasbynens @ljharb could you please take a look at the implementation or at least at the API of the library? There are also some tests for this proposal. I just need to know if I got the proposal correctly. Thanks in advance!

ljharb commented 1 year ago

@slavafomin you wouldn't need to do it every time - you just do it once with a known source string, and cache the result forever. For example, from the readme, JSON.stringify({ tooBigForNumber }, bigIntToRawJSON) === '{"tooBigForNumber":9007199254740993}' will be true when the feature is supported.

slavafomin commented 1 year ago

@slavafomin you wouldn't need to do it every time - you just do it once with a known source string, and cache the result forever. For example, from the readme, JSON.stringify({ tooBigForNumber }, bigIntToRawJSON) === '{"tooBigForNumber":9007199254740993}' will be true when the feature is supported.

I'm not sure I follow, could you elaborate please? What exactly should we cache?

ljharb commented 1 year ago

the boolean result of that comparison - either true if it's supported, or false if it's not.

HolgerJeromin commented 1 year ago

Maybe I don't see something obvious here, but as far as I know JS doesn't have a reflection API that will allow me to examine the number of arguments of the passed callback function. Of course I can turn the function to a string and then parse it's code in order to detect whether it has a third argument, but it looks pretty brittle. And this definitely won't work with the arguments.

Does https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length solve the problem?

slavafomin commented 1 year ago

Maybe I don't see something obvious here, but as far as I know JS doesn't have a reflection API that will allow me to examine the number of arguments of the passed callback function. Of course I can turn the function to a string and then parse it's code in order to detect whether it has a third argument, but it looks pretty brittle. And this definitely won't work with the arguments.

Does https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length solve the problem?

Didn't know that, thanks for mentioning! I'm gonna test it out.

slavafomin commented 1 year ago

Does https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length solve the problem?

By the way it won't work with arguments, but I think it's a pretty rare use case so we can ignore (document) it for now.

slavafomin commented 1 year ago

Thanks to the @HolgerJeromin suggestion, I've implemented a proper polyfill. Here's the usage details. And here's the implementation.

I've already released a beta-version of the package that can be used for testing: @ton.js/json-parse-polyfill@beta.

Still, will be very happy for implementation review!

jcubic commented 8 months ago

You can use this code to detect if the new proposal is implemented:

let parse = function parse(string, callback) {
    callback(1, 2, { source: 3 });
};

function detect() {
  return new Promise(resolve => {
    // use JSON.parse here
    parse('"x"', function(key, value, x) {
      resolve(typeof x !== 'undefined');
    });
  });
}

const supported = await detect();

console.log({supported: await detect()});

parse = function parse(string, callback) {
    callback(1, 2);
};

console.log({supported: await detect()});

the parse function is JSON.parse mock that executes the receiver with an extra argument that is tested.

EDIT: And since the JSON.parse is not async you can use this version:

function detect() {
  let result;
  JSON.parse('"x"', function(key, value, x) {
      result = typeof x !== 'undefined';
  });
  return result;
}