Live data from Hacker News

Parsing JSON is a Minefield

seriot.ch

261–270 of 301 posts

Re: Parsing JSON is a Minefield

#261
post #93

Earlier quoted context omitted.

Exceptions should only be used for exceptional cases. For a parser, bad input should be expected.

You say that as fact but you must know it is a matter of opinion: I would say you should match the language idioms. For example, Python iterators and generators work by raising/throwing when there are no more items: it is fully expected and will always happen when you write a for-in loop.

That seems like bad design. Rust iterators return an Option, for example.

Re: Parsing JSON is a Minefield

#262

Earlier quoted context omitted.

Throwing an exception when parsing fails, sounds like a case of exception-handling as flow of control: a bad thing even when commonly done, having a lot in common with GOTO statements. (See http://softwareengineering.stackexchange.com/questions/18922... , and Ward's Wiki when it comes back up.)

Exceptions are for flow control. That's their entire purpose. Throwing an exception when parsing fails is a near perfect example of where exceptions produce clarity of code. That is to say, a parse function is usually a pure function that takes a string and returns some sort of object. As a pure function, it "answers a question". A parser answers the question: "What object does this string represent?" When given bad…

In Rust, it would be Option where T is the type of the object you're supposed to get.

This means that if you don't parse correctly, it would be None, if you do parse correctly it would be Some(T)

Re: Parsing JSON is a Minefield

#263
post #247

Earlier quoted context omitted.

Object literals aren't JSON, though.

JS has stringify and parse, so there ought to be a JSON parser somewhere.

One thing that the article mentions is that there are in fact strings that are valid JSON but not valid JS object literals.

Re: Parsing JSON is a Minefield

#264
post #203

Earlier quoted context omitted.

But exception handling is flow control, by its very nature. So it's clearly a gray area and the right thing to do depends on the common idioms of the language you're using, the expected frequency of parsing failures, and (possibly) runtime performance concerns. In Java for example, the XML parser built into the standard library does throw exceptions for certain types of invalid input.

But see Djikstra on "GOTO statement considered harmful" ( http://david.tribble.com/text/goto.html ). The problem is unstructured control flow, which both GOTOs and exceptions-as-control-flow give you; at least in what I was taught (early-2000s CS degree focused on C++), unstructured flow of control is only acceptable when it's a panic button to quit the program (or a major area of processing). It sounds like the Web…

Thank you for that great link. Thank you twice over, because it refutes your claim.

Dijkstra specifically calls out exceptions as structured control flow, and as being probably-acceptable, and not subject to his concerns.

More broadly, any argument that goes "Exceptions are an extension of GOTO, and therefore bad" has some questions to answer, given that nearly all control structures are implemented as an extension of GOTO.

As to your last sentence, I think you have it backwards. I speculate that of the code written in 2016, most of the code that did not use exceptions for control flow was Javascript async code. (There are of course other non-exception-using languages, but other than C they're mostly pretty fringe, and for good or for ill JS is so damn fecund).

Re: Parsing JSON is a Minefield

#265
post #14

Well, first and most obviously, if you are thinking of rolling your own JSON parser, stop and seek medical attention. Secondly, assume that parsing your input will crash, so catch the error and have your application fail gracefully. This is the number one security issue I encounter in "security audited" PHP. (The second being the "==" vs. "===" debacle that is PHP comparison.) As one example, consider what happens wh…

> Well, first and most obviously, if you are thinking of rolling your own JSON parser, stop and seek medical attention.

As someone who has written his own JSON parser, I must concur. Ahh - are there any doctors here...?

In my defense - I was porting a codebase to a new platform, and needed to replace the existing JSON 'parser'. You see, it was:

  - Single-platform
  - Proprietary
  - Little more than a tokenizer with idiosyncrasies and other warts
Why was it chosen in the first place? Well, it was available as part of the system on the original platform. Not that I would've made the same choice myself. We had wrappers around it - but they didn't really abstract it away in any meaningful manner. So all of it's idiosyncrasies had leaked into all the code that used the wrappers. In the interests of breaking as little existing code as possible, I wrote a bunch of unit tests, and rewrote the wrapper in terms of my own hand rolled tokenizer. Later - either after the port, or as a side project during the port to help out a coworker (I forget) - I added some saner, higher level, easier to use, less idiosyncratic interfaces - basically allowing us to deprecate the old interface and clean it up at our leisure. This basically left us with a full blown parser - and it was all my fault.

> Takeaways: Don't parse JSON yourself, and don't let calls to the parsing functions fail silently.

I'd add to this: Fuzz your formats. All of them. Even those that don't receive malicious data will receive corrupt data.

Many of the same problems also affect e.g. binary formats. And just because you've parsed valid JSON doesn't mean you're safe. I've spent a decent amount of time using e.g. SDL MiniFuzz - fixing invalid enum values, unchecked array indicies, huge array allocations causing OOMs, bad hashmap keys, the works. The OOM case is particularly nasty - you may successfully parse your entire input (because 1.9GB arrays weren't quite enough to OOM your program during parsing), and then later randomly crash anywhere else because you're not handling OOMs throughout the rest of your program. I cap the maximum my parser will allocate to some multiple of the original input, and cap the original input to "something reasonable" (1MB is massive overkill for most of my JSON API needs, for example, so I use it as a default.)

Re: Parsing JSON is a Minefield

#266
post #262

Earlier quoted context omitted.

Exceptions are for flow control. That's their entire purpose. Throwing an exception when parsing fails is a near perfect example of where exceptions produce clarity of code. That is to say, a parse function is usually a pure function that takes a string and returns some sort of object. As a pure function, it "answers a question". A parser answers the question: "What object does this string represent?" When given bad…

In Rust, it would be Option where T is the type of the object you're supposed to get. This means that if you don't parse correctly, it would be None, if you do parse correctly it would be Some(T)

Consider this:

    def load_data():
      with open('some_file.json', 'r') as f_in:
        data = parse_json_stream(f_in)
        data['timestamp'] = some_date_fn() # Do something with the *definitely-valid* data on the next line.
        return data

    def parse_json_stream(io_stream):
      # Some complex parser...
      # at some point...
      if next_char != ']':
        raise JsonException('Expected "]" at line {}, column {}'. format(line, col))
      # More parser code...

A benefit of exceptions here is that you don't have to check the result of "data = parse_json_stream(f_in)" to immediately work with the resulting data. The stack unwinds until it is in a function that can handle the exception.

*edit: Code formatting.

Re: Parsing JSON is a Minefield

#267

> In conclusion, JSON is not a data format you can rely on blindly. What does HN suggest for configuration files (to be written by a human essentially)? I am looking at YAML and TOML. My experience with JSON based config files was horrible.

I would recommend TOML, but I am a bit biased as the author of the toml python package.

Re: Parsing JSON is a Minefield

#268
post #261
post #93

Earlier quoted context omitted.

You say that as fact but you must know it is a matter of opinion: I would say you should match the language idioms. For example, Python iterators and generators work by raising/throwing when there are no more items: it is fully expected and will always happen when you write a for-in loop.

That seems like bad design. Rust iterators return an Option , for example.

In Python a lot of flow control uses exceptions as it's cheaper to ask for forgiveness rather than permission and then still have to deal with errors.

Re: Parsing JSON is a Minefield

#269

There was a great article at some point that explained why 'be liberal in what you accept' is a very bad engineering practice in certain circumstances, such as setting a standard, because it causes users to be confused and annoyed when a value accepted by system A is subsequently not accepted by supposedly compatible system B. Leading to pointless discussions about what the spec 'intended' and subtle incompatibility.…

Thats pretty much my experience when building software as well. A lot of the time I have been liberal to incorporate legacy data, and every time in has ended up being the cause of the majority of bugs in the systems I have built.

Re: Parsing JSON is a Minefield

#270

The correct answer to parsing JSON is... don't. We experimented last hackday with building Netflix on TVs without using JSON serialization (Netflix is very heavy on JSON payloads) by packing the bytes by hand to get a sense of how much the "easy to read" abstraction was costing us, and the results were staggering. On low end hardware, performance was visibly better, and data access was lightening fast. Michael Paulso…

I want to use something like flat buffers in NodeJS for optimizing websocket traffic and implementing a FS database. But I cant find much stuff for it in JavaScript. Do you (de)serialize the flat buffers or use them directly by abstracting get/set for example via Object.defineProperty ?
Post reply on HN