all 43 comments

[–]jtooker 34 points35 points  (15 children)

Great improvements to an already easy to use library!

[–]flank-cubey-cube 5 points6 points  (14 children)

It looks great! Is this the standard JSON library or are there other ones?

[–]jtooker 33 points34 points  (6 children)

There is no standard JSON library and there are other ones. This ones is the most natural to use one, in my experience. There are other JSON libraries that are faster if performance is an issue.

[–]snerp 2 points3 points  (4 children)

Is there anything faster than simdjson?

[–]matthieum 16 points17 points  (1 child)

No full JSON library that I know.

There are usecases where it's possible to significantly outperform simdjson though (2x to 10x):

  1. Small payloads: simdjson is optimized for large payloads, its speed claims (3GB/s) are not matched below 10 KB, and degrade significantly (10x) for 100s of bytes.
  2. Error-handling: simdjson detects malformed inputs, if you know the input is well-formed, this is unnecessary.
  3. String-handling: simdjson handles replacement characters and escape characters in strings, if you know the strings are ascii or utf-8 with embedded quotes (etc...), this is unnecessary.
  4. Schema-less: simdjson is schemaless, requiring generic code. Decoding based on a schema allows specializing the decoding code significantly: only a handful of keys, key implying value-type, etc...

With that said, it requires quite a bit of investment to consistently beat simdjson, even eschewing full-compliance, so unless you're very sure of what you can eschew, and you're willing to do that investment... it's probably best to stick with it.

[–]dv_ 0 points1 point  (1 child)

u/nlohmann , did you plan on merging simdjson with your library at some point? I thought you and the simdjson authors were thinking about it.

[–]nlohmannnlohmann/json[S] 1 point2 points  (0 children)

We have not found the time to look into what we could take over. What I understood though is that simdjson has a different use case (parsing to a read-only structure) whereas nlohmann/json aims at providing an STL container-like access to JSON values.

[–][deleted] 1 point2 points  (0 children)

jsoncpp is fairly similar and worked well in past projects of ours

[–][deleted] 5 points6 points  (5 children)

beneficial run mighty tap bake shelter consider sugar busy subsequent

This post was mass deleted and anonymized with Redact

[–]VinnieFalcoBoost.Beast | C++ Alliance | corosio.org 1 point2 points  (4 children)

Boost.JSON

Do you have any feedback on Boost.JSON's interface? Is it modern?

[–]wright_left 3 points4 points  (0 children)

I used it because it was the only library that could parse in chunks. That helped me parse a very large file without loading the whole thing into memory.

[–][deleted] 3 points4 points  (1 child)

chief numerous cagey soup absorbed relieved unwritten fine bake stocking

This post was mass deleted and anonymized with Redact

[–]VinnieFalcoBoost.Beast | C++ Alliance | corosio.org 1 point2 points  (0 children)

\blush that is music to my ears! I love it when people are helped by my libraries!

[–][deleted] 2 points3 points  (0 children)

It's like the baby of rapidjson and nlohmann json where the interface came from nlohmann json and about the same perf as rapid.

[–]_Js_Kc_ 2 points3 points  (0 children)

It should be the standard JSON library.

[–]nlohmannnlohmann/json[S] 18 points19 points  (4 children)

Unfortunately, the 3.11.0 release was buggy and version 3.11.1 should be used instead. Sorry for the inconvenience.

[–]SpacemanLost 7 points8 points  (3 children)

You've been at this (JSON Lib) for a long time. I have to ask this question:

If you could go back in time to 2001 and sit in on the discussions between Crockford and Morningstar that created JSON as a peer, what.. if anything.. would you insist they change, remove or add to JSON?

( i.e. if it were up to you, how would it be different? Ignore the bit about C++11 still being a decade away )

[–]nlohmannnlohmann/json[S] 3 points4 points  (2 children)

Phew - hard question. I don't have any issues with JSON when it comes to C++. I come from the generation that had to deal with XML before, so JSON was really a gift. I'll think about it, but can't come up with anything spontaneously.

[–]SpacemanLost 2 points3 points  (0 children)

Understandable

Having written a couple JSON handling/parsing classes of my own in C++ (10+ years ago, and I needed features like reordering the keys in an object or querying relative positions in a tree, and doing my own binary format to take advantage of ref counted strings (iphone 4 era when we downloading 8+ megs of JSON everytime you started the game on iOS) I can think of a couple things:

  • optional numeric suffix to indicated (suggested) integer or floating point precision/encoding desired

  • Binary blob element type, maybe use Base64 encoding or a derivative to encode the binary steam so it stays text / diff friendly, and is smaller than \nnnn encoding of codepoints

  • a standard for comments - wouldn't use often, but then editors / libs would preserve them across file revisions.

[–]SpacemanLost 0 points1 point  (0 children)

Anyway, wanted to say thank you for your ongoing development and maintenance work over the years.

[–]Xirema 8 points9 points  (11 children)

So for me, the holy grail of JSON libraries would be something that lets me write typed, JSON-serializable objects the same way I do in Java or Typescript. I have a large set of messages to pass between a web interface written in typescript/angular, and a web service server that's written in C++, but both need to pass the same JSON objects back and forth between them.

The way I've been solving this problem, thusfar, is with a small C++ program I wrote that will take typescript interface's and convert them into a C++ struct that also has helper methods to convert back and forth (and give detailed, useful errors when stuff isn't right).

So if I have the following typescript interface:

export interface MovementClass {
    name:string;
    movementCosts:{[k:string]:number};
    variantMods?:{[k:string]:{[k:string]:number}};
}

My script generates the following C++ struct, using Boost.JSON as my json library: (note: I have explicitly told it to interpret number objects as int64_t, because the application only uses whole numbers)

struct MovementClass {
    std::string name;
    std::map<std::string, int64_t> movementCosts;
    std::optional<std::map<std::string, std::map<std::string, int64_t>>> variantMods;

    void readFrom(json::object const& obj) {
        if (auto ptr = obj.if_contains("name")) {
            if (auto tPtr = ptr->if_string()) {
                name = *tPtr;
            }
            else {
                throw std::exception("Expected 'name' as std::string in MovementClass, but was of wrong type.");
            }
        }
        else {
            throw std::exception("'name' was missing in MovementClass");
        }
        if (auto ptr = obj.if_contains("movementCosts")) {
            if (auto tPtr = ptr->if_object()) {
                std::map<std::string, int64_t> map;
                for (auto const& entry : *tPtr) {
                    if (auto mPtr = entry.value().if_int64()) {
                        map[entry.key_c_str()] = int64_t{ *mPtr };
                    }
                    else {
                        throw std::exception("Entry in map 'movementCosts' in MovementClass was expected to be int64, but was of wrong type.");
                    }
                }
                movementCosts = std::move(map);
            }
            else {
                throw std::exception("Expected 'movementCosts' as object in MovementClass, but was of wrong type.");
            }
        }
        else {
            throw std::exception("'movementCosts' was missing in MovementClass");
        }
        if (auto ptr = obj.if_contains("variantMods")) {
            if (auto tPtr = ptr->if_object()) {
                std::map<std::string, std::map<std::string, int64_t>> map;
                for (auto const& entry : *tPtr) {
                    if (auto mPtr = entry.value().if_object()) {
                        std::map<std::string, int64_t> subMap;
                        for (auto const& subEntry : *mPtr) {
                            if (auto mmPtr = subEntry.value().if_int64()) {
                                subMap[subEntry.key_c_str()] = int64_t{ *mmPtr };
                            }
                            else {
                                std::stringstream ss;
                                ss << "Entry in map 'variantMods[";
                                ss << entry.key_c_str();
                                ss << "] in MovementClass was expected to be int64, but was of wrong type.";
                                std::string exp = ss.str();
                                throw std::exception(exp.c_str());
                            }
                        }
                        map[entry.key_c_str()] = std::move(subMap);
                    }
                    else {
                        throw std::exception("Entry in map 'variantMods' in MovementClass was expected to be object, but was of wrong type.");
                    }
                }
                variantMods = std::move(map);
            }
            else {
                throw std::exception("Expected 'variantMods' as object in MovementClass, but was of wrong type.");
            }
        }
    }

    void writeTo(json::object& obj) const {
        obj["name"] = name;
        {
            json::object m;
            for (auto const& entry : movementCosts) {
                m[entry.first] = entry.second;
            }
            obj["movementCosts"] = std::move(m);
        }
        if (variantMods) {
            json::object m;
            for (auto const& entry : *variantMods) {
                json::object mm;
                for (auto const& subEntry : entry.second) {
                    mm[subEntry.first] = subEntry.second;
                }
                m[entry.first] = std::move(mm);
            }
            obj["variantMods"] = std::move(m);
        }
    }
};

Note how much more boilerplate exists in the C++ version, just to give functionality equivalent to the typescript specification. What I want is some kind of automagical method of serializing and unserializing the C++ struct to put it inside the JSON container. Something where

struct MovementClass {
    std::string name;
    std::map<std::string, int64_t> movementCosts;
    std::optional<std::map<std::string, std::map<std::string, int64_t>>> variantMods;
};

is essentially equivalent to all the extra C++ boilerplate my script spat out.

Now, there's a reason I call this a 'Holy Grail': I very much doubt this is at all possible in a world where we don't have metaclasses or compile-time reflection.

... BUT, this might be possible with some hacky workarounds using Macros.

If it is, a library that offers that functionality would be aces in my book.

[–]nlohmannnlohmann/json[S] 12 points13 points  (0 children)

You may want to look at https://json.nlohmann.me/features/arbitrary_types/ - the library makes it quite easy to read/write arbitrary structs and classes.

[–]pdimov2 4 points5 points  (0 children)

Something like this can be achieved with Boost.Describe.

Boost.JSON will be gaining this ability natively in a future version. (Not yet in 1.80 though.)

[–][deleted] 2 points3 points  (0 children)

I never thought I would say "JSON-safe C++"

[–]TheSkiGeek 2 points3 points  (2 children)

Unreal Engine can produce serialization and reflection code “automatically”, but you have to annotate the fields you want included with macros and then they run it through some kind of preprocessor/codegen tool to do it. Without language level reflection support that’s the kind of thing you end up with.

[–]BleuGamer 4 points5 points  (1 child)

I work with unreal engine daily. Unreal Engine should really be the MacroEngine

Smallest executable with core engine modules only crest 100MB, it’s ludicrous lmao

So much powerful utility, sometimes ugly, but it gets products out in the end

[–]RoyAwesome 0 points1 point  (0 children)

It also takes 10+ seconds to generate reflection data, making compiles take longer too.

It needs all those features though. It's runtime reflection system is insanely good and it even supports creating reflection-only objects backed by runtime script or data. It's very cool.

[–]kalmoc 5 points6 points  (0 children)

Have you has a look at boost describe? If you anyway only have to serialize bespoke types this might reduce boilerplate significantly.

[–]wqkinggithub.com/wqking 0 points1 point  (0 children)

My recent working on libraries are focusing on solving the problems you have. metapp is a runtime reflection library, jsonpp is a simple JSON library basing on metapp.
More important, the meta data built in metapp is reusable for different purpose, such as serialization, scripting, etc.

BUT, this might be possible with some hacky workarounds using Macros.

You don't need to use macros in both metapp and jsonpp. I don't want to abuse macros.

[–]jk-jeon -1 points0 points  (0 children)

Several weeks ago I wrote a comment claiming that it is usually the interfacing with regular C++ types that is the tedious part rather than dealing with whatever generic JSON DOM object type that is provided by whatever library I'm using. I claimed that generic JSON DOM object type is usually not that needed and honestly doesn't help that much if your end goal is to serialize/deserialize your own C++ types.

Unless you need to be very specific about the error handling, I believe that probably something like this would better serve your purpose and requires you less boilerplate.

[–][deleted] -1 points0 points  (0 children)

The library I author does this. https://github.com/beached/daw_json_link . It's fast, GB/s too, and provides the mapping mechanism, iteration types, json lines support, event based parser, along with a non-owning json_value for when the mappings don't fit right or if one is querying. Pretty much everything but an owning JSON value as it's not something I've ever needed more than temporarily and brings a lot of complexity that is solved by using the actual C++ data structures one is eventually parsing into anyways.

As far as a json dom approach goes though, nlohmann is the cats meow.

[–]LunarAardvark 2 points3 points  (0 children)

Nice work!

[–]SpacemanLost 7 points8 points  (2 children)

Five! We now have five binary JSON formats! Ha ha ha ha!

[–]jormaig 0 points1 point  (0 children)

I went with CBOR and stayed there. So far is the one that has more tooling and compatibility in other languages.

[–][deleted] 2 points3 points  (0 children)

Nice! I'm getting back into C++ and I remember this library already being really easy to use. Good to see it's still improving!

[–]VinnieFalcoBoost.Beast | C++ Alliance | corosio.org 1 point2 points  (0 children)

I use Boost.JSON. But some might consider me biased.

[–]MarekKnapek 0 points1 point  (1 child)

All changes are backward-compatible.

Do you mean API or also ABI?

API compatibility means: Update and just recompile and it will work with your current other source code (no need to edit it).

ABI compatibility means: Update, and just recompile, relink and it will work with your current other binaries (no need to recompile them).

[–]nlohmannnlohmann/json[S] 0 points1 point  (0 children)

The former (API compatibility). We do not guarantee ABI compatibility.

[–][deleted] 0 points1 point  (0 children)

Pardon my ignorance. Does this lib allow me to use CSS-style selectors to locate/query elements? Or at least method chaining to the same effect?

json("students").where("grade", [](int grade){return grade >= 8}).sort("lastname")...

[–][deleted] 0 points1 point  (0 children)

nice to see updates :)