Repository navigation
Discussion: How to structure the parsing function families #623
Description
I want to discuss a restructuring of the way input is parsed.
For this, we need to consider three dimensions:
- The C++ type to read input from.
- The file format / representation we want to read.
- What to do with the result.
1. Inputs
We currently accept inputs with the following types:
- string literals (
const char []) - char pointers (
const char*orconst uint8_t*) - input streams (
std::ifstream,std::stringstream, ...) - iterator ranges
- contiguous containers (
std::vector<char>orstd::vector<uint8_t>)
The input handling has been abstracted into a class input_adapter which offers a unified interface to get a single character or return multiple characters as a std::string.
2. File formats
We currently support the following inputs:
- JSON (RFC 7159)
- CBOR (RFC 7049)
- MessagePack (http://msgpack.org)
3. What to do with the input
-
For JSON, we support the generation of a
basic_jsonobject from any inputs using theparse()function family. Withoperator<<we also allow to sequentially parse multiple JSON values from the same input stream. In addition, we may pass a callback function that allows to react on certain parse events. -
For CBOR and MessagePack, we also suppor the generation of a
basic_jsonobject.
In either case, a parse error yields an exception. There is currently no way to indicate an error without throwing (or calling abort if exceptions are switched off).
Just supporting all input types for all file formats would need 5 x 3 = 15 functions. If we then want to add support for callbacks, make exceptions optional, allow to provide a JSON object reference to the parser as target, or anything else, we may quickly end up in dozens of similar functions. This is madness.
Instead, I would like to discuss the following approach. We implement a parser class that is exposed to the user that can be constructed with any of the input types listed above. Then, the user can decided what to do by configuring this parser object and by calling its member functions. Having a parser object, we may, for instance, decide to create an object that describes a parse error which is available to the user after parsing. So instead of throwing, we can alternativeley allow to query this object afterward, just like errno, but nicer.
I would like to still offer the static parse functions for JSON so the user can write json j = json::parse("[1,2,3]");. Removing these functions would be too confusing, even for a 3.0.0 release. But these functions would then just offer the default behavior (no callback, throwing exceptions). Anything fancy should be moved to the described parser class. Meaning, I would like to offer the simplest use cases with the old interface, but allow for more complex scenarios without an explosion of functions.
What do you think?
Activity
user1095108 commentedon Jun 17, 2017
For the conditional parse consider a std::pair and/or std::optional or your own std::optional lookalike.
1Hyena commentedon Jun 17, 2017
You could also add a function
bool nlohmann::str2json(const char *, nlohmann::json *)Alternatively you could add a valid/invalid variable to the
nlohmann::jsonclass. It would work out like this:This way you would have something different than yet another parse function.
1Hyena commentedon Jun 17, 2017
I personally prefer the pointer version the most because it is so obvious that if I pass nullptr as an argument then I don't wish to store the parsed json structure anywhere but I am merely validating the string.
nlohmann commentedon Jun 17, 2017
Note that
const char*is just one of the many inputs. Therefore, I would like to have a class that manages all possible inputs and offer an interface to do the actual work. Therefore, I think none of these functions should have the actual input as parameter.With
is_discarded()we already have something similar tois_valid(). It means that no JSON value has been created due to restrictions added by a callback function. I think the parser should rather create no value at all than to create a value that needs to be queried whether it is valid.user1095108 commentedon Jun 17, 2017
1Hyena commentedon Jun 17, 2017
I am a very practical person and C is a very practical language. I don't think we should have bullshit syntactic sugar crust all over the place just to make the code look more C++ for the sake of it. Less is more. Sure the
str2jsonfunction could accept more input types than justconst char *but depending on a parser object each time you need to parse a json string is kind of unnecessary memory allocation overhead.1Hyena commentedon Jun 17, 2017
One more thing is that since your solution is just a single header (which is what I love about nlohmann::json btw) you should avoid bloating the code with too many border use cases. if you want to satisfy any possible potential need of any programmer then you'd better develop a full blown library :P
nlohmann commentedon Jun 17, 2017
Some comments:
parser,lexer, and ainput_adapterobject with each parse. All classes are, however, quite light weight. Maybe the described approach allows us to reuse objects which could be an advantage.1Hyena commentedon Jun 17, 2017
@nlohmann
ok these are fair points you brought out. I like the idea of keeping it consistent with the STL library.
maybe nothrow would be of any help? not sure if this can be used here though.
user1095108 commentedon Jun 17, 2017
🔨 reorganized interfaces for parse/accept functions #623