JSON.isParseableAsJSON()

Right now, if you’re not sure if a particular string is parseable as JSON, most of the solutions basically are all “try to parse the string and have exception handling if it’s not parseable.” This is suboptimal because a string is not guaranteed to be JSON parseable, so it’s a legitimate condition, and one shouldn’t use exception handling for normal non-exception control flow.

Right now, we mostly do things like this:

const handleString = function(str) {
  try {
    const parsed = JSON.parse(str);
    //Do whatever with parsed.
  } catch(e) {
    //Do whatever if str is not valid JSON.
  }
};

What I’m proposing is introducing JSON.isParseableAsJSON() . It takes a possible parameter and returns a boolean. The resulting value is true if the function has a first parameter defined, said parameter is a string, and said string is valid JSON text. The value is false otherwise.

const handleString = function(str) {
  const treatAsObject JSON.isParseableAsJSON(str);
  if (treatAsObject) {
    const parsed = JSON.parse(str);
    //Do whatever with parsed.
    return;
  }
  //Do whatever if str is not valid JSON.
};

This allows for normal control flow to be used for normal conditions.

This would be inefficient. To know if a string is valid JSON it needs to be fully parsed. If it is valid then it is going to do most of that same work again in JSON.parse.

GitHub - Jack-Works/proposal-json-tryParse: JSON.canParse has been presented in plenary but did not move forward at the time.

There is also

1 Like

I acknowledge that it would be inefficient. This just happened to be the only solution I could think of.

I'd also argue that those other proposals are for different problems. Those are more for code simplification, and this one is more for allowing the appropriate code structure in certain cases. Most style guides dislike the use of exception handling for non-exception code branches.

Yes. However those style guides could make an exception for JSON.parse as it is built in to the language and is defined to throw for invalid input. So you have no choice but to use try to contain the control flow.

I’ve also been told that some people would prefer it be called “JSON.canParse()“.

You cannot, in general, tell whether a value can be parsed as JSON without parsing as JSON and seeing if it succeeds. Adding a “can this parse as JSON?” function would just encourage people to parse things twice - once to see if it’s parseable, then again to actually get the value. There’s almost no reason to ever need to know whether something’s parseable without immediately wanting it parsed, so you should just parse it, instead. See, for example, this blogpost about the Node semver library parsing semvers twice for validation before parsing them for use.

Having it fail to parse is an exceptional situation; using try/catch for this is perfectly appropriate. You can wrap this in a helper if you want, which can, say, return undefined when it fails to parse. Up to you.

1 Like

Note that there's many APIs built into JavaScript and Node that don't follow this idea - it doesn't seem to be something practiced in JavaScript very much.

Another example would be Node’s API to read a file. What if the file does not exist, and you want to handle that the same as, say, the file being empty? I presume you would define that as “normal control flow”, but Node throws an error, expecting you to catch and handle it if you need to do so. You could try checking if the file exists before reading it, but that puts you at risk of race conditions - the file being deleted between the time you read and accessed the file. Really, the only way to design this API after the principle you're describing is to have it return the error instead of throwing it, and that's not very common for JavaScript APIs to do.

There's tons of these sorts of examples. Updating a single function won't really solve the larger problem. I'm not even sure it's worth it to try and solve the larger problem. Some things are so baked into the language that it does more harm than good to try and pivot.

1 Like

Adding onto this, here is where Node’s team discourages checking for a file’s existence before reading it:

Do not use fs.access() to check for the accessibility of a file before calling fs.open(), fs.readFile(), or fs.writeFile(). Doing so introduces a race condition, since other processes may change the file's state between the two calls. Instead, user code should open/read/write the file directly and handle the error raised if the file is not accessible.

This is also part of why the Node team deprecated fs.exists.

However, it is also notable that fs.existsSync was later undeprecated.

See also these Node issues:

There’s maybe an analogy to JSON parsing here. Note that the last linked issue is a very long argument over similar “let me use a boolean predicate” API prescriptivism matters. And the commit that undeprecated existsSync even says:

As noted in https://github.com/nodejs/node/pull/7455#issuecomment-228961530, while this API is still generally considered an anti-pattern, there are still use-cases it is best suited for, such as checking if a git rebase is in progress by looking if ".git/rebase-apply/rebasing" exists.

I do personally think that, unlike fs.existsSync, a separate JSON.isValid function is probably unnecessary. Even fs.existsSync should generally be avoided except in fairly rare cases.

2 Likes

JSON.canParse() would also have side effects (or rather lack of desired side effects) in the web platform, as Request#json() and Response#json() explicitly call JSON.parse().

(Also a naive implementation of Request#canJSON() would be impossible because the body would be marked consumed just by test-parsing. There would have to be an implicit tee to consume it again. Obviously this isn’t ES business but for context.)

Same in PHP - it returns NULL if incoming is NULL and also if parsing is failed
I’ve solved it writing wrapper function with first parameter: fallback

if fallback is NULL - function returns Result<T,E> object
if fallback is empty array - function throws exception
if fallback has non-empty array - function returns ZERO key of the array - so any data can be returned AS-IS without conversion

Then it looks like

myJson.parse([], anyVariable); // may throw exception
myJson.parse([ NAN ], anyVariable); // returned NAN if error
myJson.parse([ NULL ], anyVariable); // returned NULL if error
myJson.parse([ this ], anyVariable); // returned this if error (awesome check that - if (this === result) - ideal error match, because this can be only inside current scope. Otherwise it is “other this”.
myJson.parse(null, anyVariable).get{Value|Errors}();

1 Like