#5294·json

Ideas: exploring ways to reduce compilation time

Author: nlohmannCreated Jul 22, 2026Updated Aug 26, 2026

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.