nlohmann / nlohmann/json

Ideas: exploring ways to reduce compilation time

Open
#5,294 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
50.6k
Forks
7.5k
Avg merge
4d 17h
Merged PRs (30d)
58

Description

Status: brainstorming, not a plan

This is a collection of ideas for reducing compile-time overhead, written up so they are not lost. Nothing here is a sanctioned TODO or a commitment. Each item is a hypothesis to investigate, measure, and discuss before anything is decided. Some may turn out to be not worth it, break API/ABI, or conflict with the single-header philosophy. Treat this as a starting point for discussion.

Context / measurements

Some quick local measurements (single GCC machine, single-header, best of 3; absolute numbers are illustrative, not authoritative — they will differ per compiler/machine):

Scenario C++11 C++17 C++20
#include <nlohmann/json.hpp> + empty main ~0.84s ~1.18s ~1.69s
…of which the ~44 pulled-in STL headers alone ~0.44s ~0.66s ~1.20s
Full parse + dump usage ~2.9s ~3.2s

Two observations worth keeping in mind when weighing ideas:

  • There seem to be two separable costs: a fixed per-TU include cost (paid by any TU that includes the header) and a template-instantiation cost (paid per TU that actually uses conversions/parse/serialize, roughly a quarter of a usage build in -ftime-report).
  • A large share of the include cost appears to come from the standard-library headers the header pulls in, not from our own code — and that share grows sharply at C++20.

Ideas (each unproven; needs measurement + discussion)

A. Trim/guard unconditional STL includes
  • <valarray> and <forward_list> currently appear to be included unconditionally, solely to provide to_json/from_json overloads for those niche container types (detail/conversions/from_json.hpp, detail/conversions/to_json.hpp). Idea: guard those overloads (and their includes) behind an opt-in macro. Question to answer: how much does this actually save, and is silently dropping those overloads acceptable / how to make it discoverable?
  • <any> seems to be pulled in for a single trait usage (around json.hpp is_same<ValueType, std::any>). Idea: investigate whether that check can be expressed without including <any>.
  • The C++20-heavy includes (<format>, <ranges>, <any>) already look guarded behind feature macros — so likely little left to reclaim there, and the C++20 blow-up may be largely intrinsic. Worth confirming rather than assuming.
B. Optionally excluding the binary formats
  • CBOR / MessagePack / BSON / UBJSON live in binary_reader.hpp (~3.1k lines) and binary_writer.hpp (~1.9k lines) — a sizeable fraction of the header that every TU compiles even when only text JSON is used. Idea: a JSON_NO_BINARY_FORMATS-style opt-out, analogous to the existing JSON_NO_IO.
  • Caveat that makes this non-trivial: basic_json has friend/member-typedef coupling to those classes, so member functions would need guarding too, not just the includes. Needs a careful design sketch before it's even clear this is worth pursuing.
C. Better surface the existing json_fwd.hpp
  • A forward-declaration header already exists but may be under-advertised. Idea: documentation/tutorial showing how to keep the full header out of widely-included headers (declare interfaces via json_fwd.hpp, include the full header only in .cpps). This is likely mostly a docs question rather than a code change, and could help the common "the header leaked into a common header and now recompiles everywhere" complaint.
D. Optional precompiled / extern-template path
  • For the common default nlohmann::json specialization, idea: offer explicit instantiation declarations plus a single compiled TU, so the template-instantiation cost is paid once rather than per-TU (the "firewall" pattern people currently hand-roll). Open questions: maintenance burden, how it interacts with the header-only promise, whether it's opt-in only.
E. Longer-term: C++20 modules
  • Idea / research direction only: a module interface could in principle remove much of the repeated header-parse cost. Realistically a large undertaking with fragile toolchain support today — noting it as a direction, not a near-term option.

Framing

Realistic goal, if any of this is pursued, would be materially lower fixed overhead, not parity with minimal/parser-focused libraries — that trade-off is inherent to being a single ergonomic header. Again: these are ideas to evaluate, not decisions.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing json.hpp, detail/conversions/from_json.hpp, detail/conversions/to_json.hpp, binary_reader.hpp, binary_writer.hpp, and json_fwd.hpp. Reproduce the listed include and usage measurements, then evaluate one narrowly scoped idea rather than implementing the whole list. Done would require measured results and a discussed, maintainer-approved direction; this issue defines no patch target.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
build-system, performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.