src/cpp-common: add bt2c::parseJson() functions (listener mode)
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 8 Dec 2023 18:30:18 +0000 (18:30 +0000)
committerSimon Marchi <simon.marchi@efficios.com>
Wed, 4 Sep 2024 19:05:14 +0000 (15:05 -0400)
commit3310affacd07f200476641a96beecdb44ae6c44e
tree450764aff97c28f210554e56b76529254596cb00
parentdc64abe778d9e0dbed08a4f2fc30a8cf18147c2f
src/cpp-common: add bt2c::parseJson() functions (listener mode)

This patch adds the bt2c::parseJson() functions in `parse-json.hpp`.

Those functions wrap the file-internal `bt2c::internal::JsonParser`
class of which an instance can parse a single JSON value, calling
specific methods of a JSON event listener as it processes. Internally,
`bt2c::internal::JsonParser` uses a string scanner (`bt2c::StrScanner`).

In searching for a simple JSON parsing solution, I could not find, as of
this date, any project which satisfies the following requirements out of
the box:

• Is well known, well documented, and well tested.

• Has an MIT-compatible license.

• Parses both unsigned and signed 64-bit integers (range
  -9,223,372,036,854,775,808 to 18,446,744,073,709,551,615).

• Provides an exact text location (offset, line number, column number)
  on parsing error (through logging and the message of an error cause).

• Provides an exact text location (offset, line number, column number)
  for each parsed value.

I believe the text locations are essential as this JSON parser will be
used to decode CTF2‑SPEC‑2.0 [1] metadata streams: because Babeltrace 2
will be a reference implementation of CTF 2, it makes sense to make an
effort to pinpoint the exact location of syntactic and semantic errors.

More specifically:

• JSON for Modern C++ (by Niels Lohmann) [2] doesn't support text
  location access, although there's a pending pull request (draft as of
  this date) to add such support [3].

• The exceptions of JsonCpp [4] don't contain a text location, only
  a message.

• SimpleJSON [5] doesn't offer text location access and seems to be an
  archived project.

• RapidJSON [6] doesn't offer text location access.

• yajl [7] could offer some form of text location access (offset, at
  least) with yajl_get_bytes_consumed(), remembering the last offset on
  our side, although I don't know how nice it would play
  with whitespaces.

  That being said, regarding integers, the `yajl_callbacks`
  structure [8] only contains a `yajl_integer` function pointer which
  receives a `long long` value (no direct 64-bit unsigned integer
  support). It's possible to set the `yajl_number` callback for any
  number, but the `yajl_double` callback gets disabled in that case, and
  the callback receives a string which needs further parsing on our
  side: this is pretty much what's implemented `bt2c::StrScanner`
  anyway.

At this point I stopped searching as I already had a working and tested
string scanner and, as you can see, without comments, `parse-json.hpp`
is only 231 lines of effective code and satisfies all the
requirements above.

You can test bt2c::parseJson() with a simple program like this:

    #include <iostream>
    #include <cstring>

    #include "parse-json.hpp"

    struct Printer
    {
        void onNull(const bt2c::TextLoc&)
        {
            std::cout << "null\n";
        }

        template <typename ValT>
        void onScalarVal(const ValT& val, const bt2c::TextLoc&)
        {
            std::cout << val << '\n';
        }

        void onArrayBegin(const bt2c::TextLoc&)
        {
            std::cout << "[\n";
        }

        void onArrayEnd(const bt2c::TextLoc&)
        {
            std::cout << "]\n";
        }

        void onObjBegin(const bt2c::TextLoc&)
        {
            std::cout << "{\n";
        }

        void onObjKey(const std::string& key,
                      const bt2c::TextLoc&)
        {
            std::cout << key << ": ";
        }

        void onObjEnd(const bt2c::TextLoc&)
        {
            std::cout << "}\n";
        }
    };

    int main(const int, const char * const * const argv)
    {
        Printer printer;

        bt2c::parseJson(argv[1], printer);
    }

Then:

    $ ./test-parse-json 23
    $ ./test-parse-json '"\u03c9 represents angular velocity"'
    $ ./test-parse-json '{"salut": [23, true, 42.4e-9, {"meow": null}]}'
    $ ./test-parse-json 18446744073709551615
    $ ./test-parse-json -9223372036854775808

Also try some parsing errors:

    $ ./test-parse-json '{"salut": [false, 42.4e-9, "meow": null}]}'
    $ ./test-parse-json 18446744073709551616
    $ ./test-parse-json -9223372036854775809
    $ ./test-parse-json '"invalid \u8dkf codepoint"'

[1]: https://diamon.org/ctf/CTF2-SPEC-2.0.html
[2]: https://github.com/nlohmann/json
[3]: https://github.com/nlohmann/json/pull/3165
[4]: https://github.com/open-source-parsers/jsoncpp
[5]: https://github.com/nbsdx/SimpleJSON
[6]: https://rapidjson.org/
[7]: https://github.com/lloyd/yajl
[8]: https://lloyd.github.io/yajl/yajl-2.1.0/structyajl__callbacks.html

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: Id32c2b64723ca50b044369c424fe046c0a183cce
Reviewed-on: https://review.lttng.org/c/babeltrace/+/7411
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12682
src/Makefile.am
src/cpp-common/bt2c/parse-json.hpp [new file with mode: 0644]
This page took 0.025407 seconds and 4 git commands to generate.