// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT // Decoding into structs: the same types, with from_json() generated by the // NLOHMANN_DEFINE_TYPE_* macros, read three ways. Only nlohmann/json takes // part; the other libraries have no comparable mapping. // // view json_document::parse, then get() on the view (from_json reads the view) // materialize json_document::parse, then materialize().get() (a json value first) // json::parse json::parse, then get() // json::get get() on an already parsed json value (the decoding alone) // // Documents: twitter (the statuses with their users and hashtags), citm_catalog // (the performances with their seat categories), and a synthetic document of // 20,000 nested records. // // Build: see README.md (same flags as bench_view.cpp). #include #include #include #include #include #include #include #include #include #include #include #include using nlohmann::json; using nlohmann::json_document; static volatile std::size_t g_sink; namespace twitter { struct hashtag { std::string text; std::vector indices; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(hashtag, text, indices) struct entities { std::vector hashtags; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(entities, hashtags) struct user { std::uint64_t id = 0; std::string screen_name; std::string description; std::int64_t followers_count = 0; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(user, id, screen_name, description, followers_count) struct status { std::uint64_t id = 0; std::string text; user user; entities entities; std::int64_t retweet_count = 0; std::int64_t favorite_count = 0; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(status, id, text, user, entities, retweet_count, favorite_count) struct document { std::vector statuses; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, statuses) std::size_t digest(const document& d) { std::size_t n = 0; for (const auto& s : d.statuses) { n += s.text.size() + s.user.screen_name.size() + s.entities.hashtags.size() + static_cast(s.retweet_count); } return n; } } // namespace twitter namespace citm { struct area { std::int64_t areaId = 0; std::vector blockIds; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(area, areaId, blockIds) struct seat_category { std::vector areas; std::int64_t seatCategoryId = 0; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(seat_category, areas, seatCategoryId) struct performance { std::int64_t id = 0; std::int64_t eventId = 0; std::vector seatCategories; std::int64_t start = 0; std::string venueCode; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(performance, id, eventId, seatCategories, start, venueCode) struct document { std::vector performances; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, performances) std::size_t digest(const document& d) { std::size_t n = 0; for (const auto& p : d.performances) { n += p.seatCategories.size() + p.venueCode.size() + static_cast(p.start % 1000); for (const auto& c : p.seatCategories) { n += c.areas.size(); } } return n; } } // namespace citm namespace synthetic { struct position { double x = 0; double y = 0; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(position, x, y) struct child { std::int64_t id = 0; std::string label; bool active = false; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(child, id, label, active) struct record { std::int64_t id = 0; std::string name; std::vector tags; position pos; std::vector children; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(record, id, name, tags, pos, children) struct document { std::vector records; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(document, records) std::string make_text(std::size_t n) { std::string s = "{\"records\":["; for (std::size_t i = 0; i < n; ++i) { const std::string id = std::to_string(i); s += (i == 0 ? "" : ","); s += "{\"id\":" + id + ",\"name\":\"record number " + id + "\",\"tags\":[\"alpha\",\"beta\",\"gamma\"],"; s += "\"pos\":{\"x\":" + id + ".25,\"y\":-" + id + ".5},\"children\":["; for (std::size_t k = 0; k < 3; ++k) { s += (k == 0 ? "" : ","); s += "{\"id\":" + std::to_string(i * 3 + k) + ",\"label\":\"child " + std::to_string(k) + "\",\"active\":" + (k % 2 == 0 ? "true" : "false") + "}"; } s += "]}"; } s += "]}"; return s; } std::size_t digest(const document& d) { std::size_t n = 0; for (const auto& r : d.records) { n += r.name.size() + r.tags.size() + r.children.size() + static_cast(r.pos.x); } return n; } } // namespace synthetic namespace { std::string read_file(const std::string& p) { std::ifstream f(p, std::ios::binary); if (!f) { std::fprintf(stderr, "cannot open %s\n", p.c_str()); std::exit(1); } std::stringstream ss; ss << f.rdbuf(); return ss.str(); } double time_ns(const std::function& f) { const auto t0 = std::chrono::steady_clock::now(); f(); const auto t1 = std::chrono::steady_clock::now(); return static_cast(std::chrono::duration_cast(t1 - t0).count()); } struct engine { std::string name; std::function run; }; template std::vector engines(const std::string& text, const json& parsed) { return { {"view", [&text] { const json_document d = json_document::parse(text); return digest(d.root().get()); }}, {"materialize", [&text] { const json_document d = json_document::parse(text); return digest(d.root().materialize().get()); }}, {"json::parse", [&text] { return digest(json::parse(text).get()); }}, {"json::get", [&parsed] { return digest(parsed.get()); }}, }; } } // namespace int main(int argc, char** argv) { if (argc < 2) { std::fprintf(stderr, "usage: %s [rounds]\n", argv[0]); return 1; } const std::string dir = argv[1]; const int rounds = argc > 2 ? std::max(1, std::atoi(argv[2])) : 30; struct document_case { std::string name; std::string text; json parsed; std::vector engines; }; std::vector cases; cases.push_back({"twitter", read_file(dir + "/nativejson-benchmark/twitter.json"), json(), {}}); cases.push_back({"citm_catalog", read_file(dir + "/nativejson-benchmark/citm_catalog.json"), json(), {}}); cases.push_back({"synthetic", synthetic::make_text(20000), json(), {}}); for (auto& c : cases) { c.parsed = json::parse(c.text); } cases[0].engines = engines(cases[0].text, cases[0].parsed); cases[1].engines = engines(cases[1].text, cases[1].parsed); cases[2].engines = engines(cases[2].text, cases[2].parsed); std::FILE* csv = std::fopen("bench_decode.csv", "w"); if (csv == nullptr) { std::perror("bench_decode.csv"); return 1; } std::fprintf(csv, "doc,bytes,engine,ns\n"); for (const auto& c : cases) { // all engines must decode the same values const std::size_t expected = c.engines[0].run(); bool ok = true; for (const auto& e : c.engines) { ok = ok && e.run() == expected; } std::vector best(c.engines.size(), 1e300); for (int r = 0; r < rounds; ++r) { for (std::size_t k = 0; k < c.engines.size(); ++k) { g_sink = c.engines[k].run(); // untimed: see bench_view.cpp best[k] = std::min(best[k], time_ns([&] { g_sink = c.engines[k].run(); })); } } std::printf("%-13s %-7s %s", c.name.c_str(), "decode", ok ? "" : "[MISMATCH] "); for (std::size_t k = 0; k < c.engines.size(); ++k) { const double us = best[k] / 1000.0; std::printf(" %s %.*fus (%.2fx)", c.engines[k].name.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]); std::fprintf(csv, "%s,%zu,%s,%.1f\n", c.name.c_str(), c.text.size(), c.engines[k].name.c_str(), best[k]); } std::printf("\n"); } std::fclose(csv); return 0; }