* Speed up number parsing in the lexer (fast paths from the fast_float/simdjson world)
The number scanner converted its already-validated digit buffer with
std::strtoull/std::strtoll/std::strtod. Those pull in locale and errno
machinery and dominate number-heavy parsing (strtod runs at ~6 M/s).
Replace them with dedicated parsers over the validated buffer:
- parse_integer_unsigned / parse_integer_signed: accumulate digits with
overflow detection, falling back to the float path on overflow exactly
as the strtoull/strtoll round-trip check did. Overflow behavior is
unchanged for narrower or wider custom number types.
- parse_float_fast: Clinger's exact fast path for `double` (<=19 significant
digits, |exp10| <= 22, significand < 2^53), where significand * 10^exp is
exact under IEEE round-to-nearest. This is the same fast path used by
fast_float/simdjson. It is bit-identical to strtod on this subset and
declines (falling back to strtod) otherwise. Only `double` uses it; float
and long double keep std::strtof/std::strtold via a templated overload.
Measured on representative data (g++ 13, -O3):
- integers: DOM parse +11%, SAX +25-34%
- floats: DOM parse +37%, SAX +70% (clang: float DOM ~1.9x)
No dependencies added; header-only and C++11-clean. Existing parser,
lexer, conversion and deserialization unit tests pass unchanged; a
3M-value random-double fuzz matches strtod bit-for-bit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Add SWAR bulk string scanning for contiguous input (simdjson-style)
scan_string() read the input one character at a time through the input
adapter and classified every byte with a large switch. For contiguous
byte buffers we can instead scan 8 bytes at a time with a SWAR word test
that finds the first byte needing individual handling (the closing quote,
an escape, a control character, or a non-ASCII UTF-8 byte) and bulk-append
the ordinary run in one go.
- input adapters expose supports_bulk_scan / bulk_data / bulk_remaining /
bulk_skip for provably-contiguous, same-type, 1-byte iterator ranges
(raw pointers in every standard; std::string/std::vector/std::array and
friends additionally in C++20 via std::contiguous_iterator).
- the lexer gains a bulk_scan capability (gated on lazy_token_string so
bypassing the per-character capture cannot lose error diagnostics) and a
scan_string_bulk() fast path; streaming/wide/user adapters are unchanged
and keep the byte-at-a-time scanner.
The run contains no newline (all bytes < 0x20 are treated as special), so
position bookkeeping stays exact, and error tokens are still reconstructed
lazily from the consumed byte range. The SWAR special-byte test is pure
uint64_t arithmetic - no intrinsics, no runtime dispatch, C++11-clean.
Measured on representative data, pointer input, g++ 13 -O3
(string values discarded by accept() see the largest gains):
long ASCII strings: DOM +4.5x, SAX +14x, accept +17x (to ~2 GB/s)
short strings: DOM +15%, SAX +62%, accept +85%
escape-heavy: DOM +31%, SAX +26%, accept +28%
Same-input parity verified: 200k randomized documents (escapes, multibyte
UTF-8, surrogate pairs) accept/parse identically via the contiguous SWAR
path and the streaming byte path; unit lexer/parser/diagnostic-position/
deserialization/conversions suites pass unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Validate UTF-8 in the bulk string scanner (portable ~2-3x on non-ASCII text)
The SWAR bulk string path stopped at the first non-ASCII byte and handed
every multibyte character to the byte-at-a-time scanner, whose per-byte
get()/next_byte_in_range()/add() machinery runs at roughly half the speed
of validating straight from the buffer. As a result, dense non-ASCII text
(CJK, emoji, accented Latin) parsed ~10-15x slower than ASCII.
Fold well-formed UTF-8 into the bulk run: scan_string_bulk() now, on a
non-ASCII lead byte, validates one sequence with validate_one_utf8() -
which mirrors scan_string()'s per-byte switch ranges exactly (rejecting
overlong forms, surrogates, and out-of-range code points) - and appends it
in place, continuing until the closing quote, an escape, a control byte,
or an ill-formed sequence. All error handling still defers to the byte
path, so error messages and positions are byte-for-byte unchanged.
Because only well-formed content is fast-pathed and every rejection falls
through to the existing scanner, behavior is identical; the win is purely
throughput. Measured on pointer input (accept, string values discarded):
content g++ 13 clang 18
dense CJK 277 -> 648 ~605 MB/s (~2.3x)
dense emoji 299 -> 857 ~702 MB/s (~2.6-2.9x)
mixed 90% ASCII 246 -> 331 ~334 MB/s (~1.35x)
pure ASCII unchanged (~3.2 / 4.1 GB/s)
Verified: 2,000,000 randomized documents built from arbitrary bytes
(overlong, surrogate, truncated, out-of-range sequences) accept/reject and
parse identically via the contiguous path and the streaming byte path;
lexer/parser/diagnostic-position/deserialization/conversions suites pass
unchanged. Pure C++11, no intrinsics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Route contiguous byte containers through the pointer adapter (fast paths in C++11)
json::parse(std::string) - the most common entry point - did not benefit
from the contiguous fast paths (bulk string scanning, UTF-8 bulk
validation, memcpy for binary formats) in C++11..17: std::string::iterator
is a library wrapper, not a raw pointer, and pre-C++20 there is no portable
way to prove it contiguous, so supports_bulk_scan was false. Only raw
pointers, string literals, and C-arrays (and, in C++20, anything modelling
std::contiguous_iterator) took the fast path.
Detect contiguous single-byte containers (std::string, std::vector<char>,
std::vector<std::uint8_t>, std::string_view, ...) via is_contiguous_byte_
container and route them through an iterator_input_adapter built from
data()/data()+size(). The generic iterator-based container overload is
constrained to exclude these, so the two overloads are disjoint and there
is no ambiguity (a plain competing overload loses to the greedy
forwarding-reference container overload on reference binding, and a factory
partial-specialization is ambiguous - both were tried and rejected).
The pointer keeps the container's own element type, so char_type - and
therefore all parsing behavior - is byte-for-byte identical to the iterator
path (const char* for std::string, const std::uint8_t* for
std::vector<std::uint8_t>); only the raw pointer additionally turns on the
fast paths. Lifetimes are unchanged: the container outlives the adapter for
the full parse expression, exactly as the iterators it replaces did.
Measured, C++11, json::parse/accept(std::string), g++ 13:
long ASCII strings: accept 201 -> 3200 MB/s (~16x), parse 174 -> 1444
dense CJK: accept 263 -> 697 MB/s (~2.6x)
short strings: accept 163 -> 243 MB/s (~1.5x)
Verified: char_type preserved for std::string (char) and
std::vector<std::uint8_t> (uint8_t); CBOR/MsgPack round-trips from
std::vector<std::uint8_t> unchanged; 1,000,000 randomized documents accept
and parse identically via std::string and via std::istream;
deserialization/user-defined-input/parser/lexer/conversions/diagnostic-
position suites pass (20,480 assertions); warning-clean on g++ and clang in
C++11/17/20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Add optional simdutf backend for bulk UTF-8 validation (JSON_USE_SIMDUTF)
The bulk string scanner validates UTF-8 straight from a contiguous buffer.
The scalar validator caps at ~0.3-0.7 GB/s on non-ASCII text; a SIMD
validator reaches several GB/s. Rather than hand-rolling SIMD UTF-8
validation (easy to get subtly wrong - a from-scratch SSE attempt rejected
valid CJK), wire in the vetted simdutf library behind an opt-in switch.
simdutf is not header-only (it ships simdutf.cpp and uses runtime CPU
dispatch), so it is not vendored: defining JSON_USE_SIMDUTF includes
<simdutf.h> and routes the bulk validator through simdutf::validate_utf8;
the project supplies and links simdutf. Undefined (the default), nothing
external is included and the portable C++11 scalar path is used, so the
library stays header-only and its baseline behavior is unchanged.
Design keeps behavior identical either way:
- scan_string_bulk() now finds the run up to the next quote/escape/control
byte (non-ASCII allowed) and validates it in one shot; on the rare
validation failure it recomputes the exact valid prefix with the scalar
helper, so ill-formed input still falls through to the byte path and is
reported at the same position with the same message.
- the per-sequence scalar path is factored into scalar_string_bulk_run()
and is the default backend; the refactor is behavior-preserving and does
not change scalar throughput.
Verified: default and JSON_USE_SIMDUTF builds accept/reject/parse
identically across 2,000,000 arbitrary-byte documents and 1,000,000
mixed-escape/UTF-8 documents (differential fuzz vs the streaming byte
path); lexer/parser/diagnostic-position/deserialization suites pass under
both configurations (20,188 assertions with the backend enabled);
warning-clean on g++ and clang, C++11 and C++20, both configurations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Add a contiguous fast path for scanning numbers
scan_number() reads a number one character at a time through the input
adapter (get()) and appends each byte to token_buffer (add()) before
converting. For contiguous input, the per-character get()/add() overhead
dominates: it is roughly two thirds of the time spent on number-heavy
parsing, far more than the value conversion itself.
Add scan_number_bulk_contiguous(), which parses the whole number token
straight from the input buffer: it validates and classifies the extent
with the same grammar as scan_number()'s state machine, materializes
token_buffer in one copy (substituting the locale decimal point exactly as
scan_number() does), advances the adapter, and reuses the shared
convert_number() tail. On anything it does not recognize as a well-formed
number it makes no state change and returns token_type::uninitialized, so
the caller falls back to scan_number(), which then produces the exact
diagnostic. Errors and their positions are therefore unchanged.
The conversion tail is factored out of scan_number() into convert_number()
so both scanners share it; the fast path is selected by tag dispatch on the
existing bulk_scan capability, so streaming/wide/user adapters are
unaffected.
Measured on pointer input, g++ 13 -O3:
- integers: parse +65%, accept +98%
- floats: parse +39%, accept +70%
Verified: 2,000,000 randomized number documents (including overflow-range
integers, long digit strings and %.17g doubles) parse identically via the
contiguous path and the streaming byte path, matching value, type and
round-trip text; the locale suite and existing parser/lexer/conversions/
deserialization tests pass; a new "lexer number fast path" test checks
contiguous-vs-streaming parity, token classification, and that malformed
numbers are rejected identically. Pure C++11, no intrinsics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Move byte-level scan/parse helpers out of lexer.hpp
lexer.hpp had grown by ~600 lines of byte-level helpers that have no
dependency on the lexer's template parameters and clutter the state
machine. Move them, unchanged, into two focused headers as free functions
in namespace detail:
- number_parse.hpp: parse_integer_unsigned/parse_integer_signed (now
templated on the number type) and parse_float_fast (Clinger's exact
double fast path, with the decimal point passed as an argument instead of
read from a lexer member).
- string_scan.hpp: the SWAR string helpers (is_string_special,
swar_string_special, find_string_special, validate_one_utf8,
scalar_string_bulk_run) and the backend-dispatched string_bulk_run,
including the optional simdutf include and find_string_delimiter.
lexer.hpp now includes these and calls the free functions; the methods that
touch lexer state (scan_string, scan_number, scan_string_bulk,
scan_number_bulk_contiguous, convert_number) stay put. This is a pure code
move with no behavior change: lexer.hpp drops from 2357 to 1934 lines, the
now-unused <cstdint>/<cstring>/<limits> includes are removed, and the
free-function form makes the SWAR helpers reusable elsewhere (e.g. the
serializer's string escaping).
Verified: default and JSON_USE_SIMDUTF builds compile; 2,000,000 number and
2,000,000 arbitrary-byte-string differential-fuzz documents parse
identically to before; lexer/parser/conversions/deserialization/locale/
diagnostic-position suites pass (20,576 assertions); warning-clean on g++
and clang in C++11/17/20; the amalgamation regenerates and passes
check-amalgamation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix clang-tidy findings and document JSON_USE_SIMDUTF in the nav
- number_parse.hpp: use std::array for the powers-of-ten table
(avoid-c-arrays) and `auto` for the cast-initialized result
(modernize-use-auto), matching the codebase style (cf. the serializer's
utf8d table). Indexing casts keep the -Wsign-conversion build clean.
- add JSON_USE_SIMDUTF to the mkdocs navigation so the macro page is
reachable.
No behavior change; clang-tidy is clean on the new headers and the
amalgamation is regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix number fast path for custom string types without assign()
The contiguous number fast path materialized token_buffer with
token_buffer.assign(data, len), but string_t is only required to provide
the minimal interface the rest of the lexer uses (push_back, append,
clear, operator[], ...). Custom string types such as the test's alt_string
do not implement assign(), so scan_number_bulk_contiguous() failed to
compile for them (unit-alt-string), breaking the gcc/clang standards and
old-compiler CI jobs.
reset() already clears token_buffer, so fill it with append() - which
alt_string and std::string both provide and which the string fast path
already relies on - instead of assign().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Satisfy clang-tidy: parenthesize math and drop unused forwarding reference
The CI clang-tidy (newer than the locally available version) reported two
additional checks on the new code:
- readability-math-missing-parentheses: parenthesize the (a * b) + c digit
accumulations in number_parse.hpp.
- cppcoreguidelines-missing-std-forward: the contiguous-byte-container
input_adapter overload took a forwarding reference but only reads
data()/size() and never forwards it. It is already disjoint from the
generic container overload via SFINAE, so a plain const& is correct and
clearer (and keeps the container alive for the whole parse just as before).
No behavior change; char_type and routing are unchanged (std::string and
std::vector<std::uint8_t> still take the pointer adapter with char/uint8_t
char_type), CBOR/MsgPack round-trips and the 2M number fuzz still pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Use std::from_chars (Eisel-Lemire) for float conversion when available
The Clinger fast path is exact only for the "easy" subset (<=19 significant
digits, |exp10| <= 22); high-precision and scientific floats fall through to
strtod, where the failed Clinger attempt actually makes parsing a net loss.
std::from_chars implements the Eisel-Lemire algorithm in modern standard
libraries: locale-independent, correctly rounded, and fast over the whole
value range.
convert_number() now tries parse_float_from_chars() first (guarded by
__cpp_lib_to_chars, so C++11 and libc++-without-float-support keep the
Clinger + strtod path unchanged), then Clinger, then strtof. from_chars is
used only when it consumes the entire token; a partial parse means a non-'.'
locale decimal point, and an under-/overflow (result_out_of_range) also
declines - in both cases the existing strtod fallback supplies the exact
value and the well-defined +/-inf/0 the parser expects, side-stepping the
P4168 divergence between implementations. float and long double now get the
fast path too (Clinger was double-only).
Measured, C++17, g++ 13 -O3, json::parse/accept:
- canada-style floats: ~unchanged (Clinger already covered them)
- high-precision (17 digits): parse 2.1x, accept 2.5x
- scientific (17 digits + exp): parse 3.6x, accept 4.1x
Verified: C++11 (Clinger/strtod) and C++17 (from_chars) parse every value -
including subnormals, boundary values, and 1e9999/1e-9999 over-/underflow -
to bit-identical results; 2M number-fuzz clean; conversions/deserialization/
locale/number-fast-path suites pass in both C++11 and C++17; clang-tidy
clean; warning-clean on g++ and clang in C++11/17/20.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Guard from_chars use on JSON_HAS_CPP_17, not just __cpp_lib_to_chars
libstdc++ 15 defines __cpp_lib_to_chars even in C++14 mode (via
bits/version.h pulled in by other headers), but <charconv> is only included
under JSON_HAS_CPP_17. That made parse_float_from_chars() reference
std::from_chars without the header in C++14 builds, breaking gcc-latest,
icpx, and the offline-testdata jobs.
Gate the use on JSON_HAS_CPP_17 && __cpp_lib_to_chars so it matches the
include condition exactly; C++11/14 always take the scalar fallback.
Verified by forcing __cpp_lib_to_chars in a C++14 build: the guard
suppresses std::from_chars and it compiles. C++17 behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Disable Clinger float fast path under extended FP precision (x87)
The contiguous number fast path uses a Clinger-style exact algorithm
(significand * 10^scale in double arithmetic), which is only correctly
rounded when double operations are evaluated in true 53-bit precision.
On the x87 FPU used by 32-bit x86 (FLT_EVAL_METHOD == 2) the single
multiply/divide is computed in 80-bit and then double-rounded to double,
so a small fraction of values land 1 ULP off.
This surfaced as test-cbor_cpp11 and test-msgpack_cpp11 failing on the
mingw (x86) job for regression/floats.json: the C++17 builds pass because
they take the correctly-rounded std::from_chars path, while C++11 falls
back to parse_float_fast(). A 5M-sample check over shortest round-trip
decimals reproduces it: 0 divergences with 53-bit doubles, ~1 in 25 000
with 80-bit intermediates; declining to std::strtod fixes all of them.
Guard parse_float_fast() on FLT_EVAL_METHOD so it declines whenever the
platform evaluates doubles in extended precision, letting the caller use
the correctly-rounded std::from_chars / std::strtod path instead. On
mainstream x86-64/ARM64 (FLT_EVAL_METHOD == 0) the fast path is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Guard <charconv> include with __has_include for GCC 7
GCC 7 sets __cplusplus to the C++17 value under -std=gnu++1z, so
JSON_HAS_CPP_17 is defined, but its libstdc++ ships no <charconv> header
(added in GCC 8; floating-point from_chars in GCC 11). The unconditional
"#if defined(JSON_HAS_CPP_17) #include <charconv>" therefore failed to
compile there: "fatal error: charconv: No such file or directory" in the
ci_test_compilers_gcc (7) job.
Wrap the include in __has_include(<charconv>), mirroring the library's
existing handling of <version> and <filesystem> in macro_scope.hpp. When
the header is absent, __cpp_lib_to_chars stays undefined and
parse_float_from_chars() takes its scalar fallback, so the from_chars use
site (already gated on __cpp_lib_to_chars) is never reached. GCC 8-10,
which have <charconv> but no floating-point from_chars, are unaffected:
they include the header but still take the fallback. GCC 11+ is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXcDtEma2PjxgmPS9cQGzA
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Restore the column when ungetting a newline
The contiguous number fast path never reads the character that terminates
a number token, while scan_number() reads it and then ungets it. When that
character is a newline, get() has already cleared chars_read_current_line,
and unget() could only restore lines_read - leaving the column at 0. The
two paths therefore reported different columns for the same document:
json::parse("[01\n]") -> line 1, column 3
json::parse(stringstream) -> line 1, column 0
Remember the column the newline was read at so unget() can restore it.
Both paths now report the position the offending token actually starts at,
which also fixes the pre-existing column-0 artifact for streaming input.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Extend the bulk scan fast paths to sized sentinels
supports_bulk_scan required IteratorType and SentinelType to be the same
type, which excluded std::counted_iterator paired with std::default_sentinel_t
- the combination #5268 had already enabled for the memcpy fast path. Such
input fell back to the byte-at-a-time scanner even though it is contiguous
and its remaining length is computable in O(1).
Factor the "distance is computable in O(1)" test into sentinel_is_sized and
use it for iterator_is_contiguous, supports_seek, and supports_bulk_scan
alike, and share the std::ranges::distance/std::distance dispatch through a
remaining_count() helper.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Document JSON_USE_SIMDUTF on the macro overview page
The macro was only listed in the API macro index; add it to the supported
macros overview alongside the other JSON_USE_* macros, and note that it
selects between two definitions of the same inline function and so must be
defined identically in every translation unit.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Amalgamate source code
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Cover the counted-iterator bulk scan paths
Sized sentinels newly reach the bulk string/number scanners and the
seek-based token reconstruction, so exercise both:
- diagnostics that quote the offending token, which are rebuilt from the
consumed input via copy_consumed_range()
- inputs whose count ends before the underlying buffer does, including a
closing quote that exists only behind the count, a cut inside an 8-byte
SWAR stride, and a cut inside a UTF-8 sequence
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix the SentinelType example on iterator_input_adapter
The comment offered "a C++20 sentinel or counted_iterator" as examples of a
SentinelType, but std::counted_iterator is the IteratorType - the sentinel it
pairs with is std::default_sentinel_t. #5268 corrected the same wording in the
API documentation and left the code comment behind.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Do not discard the parse result in the error position check
json::parse is declared warn_unused_result, and CHECK_THROWS_WITH_AS
evaluates its expression as a discarded statement, so the assertion broke
the -Werror builds (GCC -Werror=unused-result, MSVC C4834 under /WX).
Compare against the helper that already captures the message instead.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Lock the two number grammars together with a parity test
The JSON number grammar is encoded twice: as the scan_number() state machine
and as the contiguous fast path. The fast path declining on anything it does
not recognize keeps most divergence harmless, but if it ever accepted
something the state machine rejects the result would be a silent correctness
bug, and the existing test only pinned a hand-written list of numbers.
Enumerate every string of length 1..4 over "01.eE+-" (2800 tokens) and
require both paths to agree on the parsed value and on the exact error
message. Verified to fail if the fast path's grammar is perturbed.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Skip token_buffer for integers on the contiguous path
An integer token does not need token_buffer: the number_integer and
number_unsigned SAX callbacks take only the value, and the overflow
diagnostic rebuilds the text from the input via get_token_string(). Convert
straight from the input buffer and materialize the token only for the
floating-point tail, which still needs a NUL-terminated buffer for strtod.
JSON_DIAGNOSTIC_POSITIONS derives a number's start position from
get_string().size(), so the copy is kept when that is enabled.
The integer dispatch is factored into convert_integer() and shared with
convert_number(), so both scanners keep using one implementation.
Integer-heavy input, 400k values, -O3:
parse accept
gcc 16 +14% +23%
clang +15% +22%
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Amalgamate source code
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Cover the bulk string and UTF-8 scanners
These paths had no dedicated tests and rested on differential fuzzing only.
Add three sections, all comparing the contiguous scanner against the
byte-at-a-time one on the parsed value and on the exact error message:
- every string of length 1..3 over an alphabet of ordinary ASCII, both
specials, a control byte, escape characters, UTF-8 lead and continuation
bytes, and a byte that is never valid - each at offset 0 and offset 9, so
the bulk scanner sees them with and without a run behind them
- every kind of run-ending byte at each offset across two 8-byte SWAR words,
so multibyte sequences also straddle the word boundary
- the boundaries of every range validate_one_utf8() recognizes: shortest and
longest encodings, overlongs, both ends of the surrogate block, U+10FFFF
and just past it, and truncated sequences
Verified to fail if the bulk validator accepts surrogates, and if the SWAR
word test stops detecting control characters.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Format the new string fast path test with astyle
The pinned astyle expands a braced-init-list used as a range-for range onto
several lines; hoist the two offsets into a named vector instead, which reads
better and leaves nothing for astyle to reformat.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix shadowed locals and guard the exception-dependent tests
Two problems in the tests added for the bulk scanners, both found by CI:
- the inner `const json j` in the counted-iterator diagnostics shadowed the
one declared at test-case scope, which -Wshadow rejects on GCC and clang
and C4456 rejects on MSVC under /WX; rename them
- the new parity checks parse deliberately invalid input, which calls
std::abort() rather than throwing when JSON_NOEXCEPTION is defined, so
they would have crashed the no-exception build; guard them the way the
other tests do
json::accept() does not abort, so the UTF-8 range assertions stay compiled
without exceptions and keep covering validate_one_utf8() there.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Gate the C++20 iterator classification on JSON_HAS_RANGES
Making supports_bulk_scan depend on iterator_is_contiguous meant the trait
is now instantiated for every adapter, not only when get_elements() is
called. On standard libraries with an incomplete <ranges> that is fatal:
libstdc++ 10 evaluates std::contiguous_iterator<std::counted_iterator<T*>>
by calling std::to_address, which needs an operator-> its counted_iterator
does not have, so satisfaction checking is a hard error rather than false.
Reported by clang 14 + libstdc++ 10.
JSON_HAS_RANGES already encodes exactly this ("libstdc++ < 11 has incomplete
C++20 ranges", #4440), so require it for the C++20 branch. Affected
toolchains fall back to the pointer-only test and the byte-at-a-time
scanner, which parses identically, just without the bulk fast paths.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Amalgamate source code
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Satisfy clang-tidy in the new bulk scanner tests
- give the helper lambdas an explicit std::string return type and return
braced initializer lists (modernize-return-braced-init-list)
- replace the C-style array of test cases with a std::vector
(modernize-avoid-c-arrays)
- silence pro-type-member-init on the two brace-initialized aggregates;
default member initializers would stop them being aggregates in C++11
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Avoid escaped literals in the counted-iterator diagnostics list
clang-tidy reads "[\"\\ud834\"]" as a literal better written raw, and the
two literals written next to each other in "[\"a\x01""b\"]" as a missing
comma. The concatenation was there to stop the hex escape swallowing the
following character; build those documents from explicit bytes instead and
use raw strings elsewhere. The byte sequences are unchanged.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Reattach convert_number's documentation
@gregmarr spotted that convert_integer() was inserted between convert_number()
and its doc block, leaving convert_integer() with two stacked blocks and
convert_number() with none. Comment only; no code change.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Address review comments on the simdutf backend
string_bulk_run() had the same `return scalar_string_bulk_run(...)` in both
arms of the `#if`. The simdutf arm already falls through when validation
fails, so a single return after the `#endif` says the same thing.
The JSON_USE_SIMDUTF example showed `#include <simdutf.h>`, which
string_scan.hpp already does under the same guard; users only have to put the
header on the include path and link the library, not include it themselves.
Set the version history entry to 3.13.0, matching the other macro pages
documenting unreleased features.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Pin the number error position against a non-newline terminator
The comment claimed the reported column is the one the offending token starts
at. It is the column reached after the token's last character - which is the
actual point of the unget() change: a number terminated by a newline now
reports what the same number terminated by a space always did.
Assert that equality directly, and add a multi-character token where the start
and end columns differ, so the invariant cannot be read off a single-character
example.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Do not repeat the integer conversion that just failed
scan_number_bulk_contiguous() converts an integer token straight from the input
buffer. When the value does not fit, it materializes token_buffer and calls
convert_number(), which tried the very same integer conversion again before
falling back to floating point.
Recording the outcome in number_type skips the second attempt. The resulting
token type and value are unchanged: convert_number() reached the float tail
either way.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Require a container's value_type to match what data() points at
is_contiguous_byte_container accepted any type with a data() returning a
pointer to a single-byte integral plus a size(). That is duck typing: the two
members say nothing about size() counting the units data() points at.
A type where it does not - fixed-size records, say - was routed to the
pointer-based adapter and parsed as [data(), data() + size()) bytes, silently
truncating input the iterator-based adapter had read in full:
struct record_buffer {
using value_type = std::array<char, 4>;
std::string bytes;
const char* data() const; // raw bytes
std::size_t size() const; // in records
const char* begin() const; const char* end() const;
};
json::parse(record_buffer{"[1,2,3,4,5]"}); // parse error at column 3
Requiring the container's own value_type to be that same element type ties the
two together. Every contiguous standard container satisfies it, so std::string,
std::vector<char>, std::array<char, N> and std::string_view keep the fast path;
anything else falls back to the iterator-based adapter, which is always correct.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Only compile the simdutf backend from C++17 on
simdutf.h rejects anything below C++17 with an #error, so defining
JSON_USE_SIMDUTF in a C++11 or C++14 translation unit did not fail with a
message about simdutf being unavailable - it failed to compile at all, taking
the library's C++11 support with it. Nothing caught this because no build ever
compiled that path.
Gate the include and both uses on JSON_HAS_CPP_17, the same way number_parse.hpp
gates std::from_chars. Below C++17 the macro now has no effect and the scalar
validator runs; it accepts and rejects exactly the same input, so the macro is
safe to set project-wide even when some translation units use an older standard.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Test the simdutf backend in CI
JSON_USE_SIMDUTF was documented and shipped but never compiled by anything in
the repository, so nothing held the backend to the behavior the docs promise.
Add JSON_TestSimdutf (OFF by default), which fetches simdutf and defines
JSON_USE_SIMDUTF for every test target, and a ci_test_simdutf target that runs
the whole suite in that configuration. Because simdutf needs C++17, the suite is
built at C++11 as well, so one job covers both the scalar fallback with the
macro defined and simdutf itself.
The dependency hangs off test_main, whose usage requirements every test target
inherits. The library target and the installed CMake package are deliberately
untouched: making nlohmann_json link simdutf would put a find_dependency() in
the exported package, which is a separate decision.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Warn when JSON_TestSimdutf cannot reach the simdutf backend
simdutf needs C++17: without it the dependency does not even compile, and with
a C++17 compiler but no C++17-or-later standard under test it builds and then
goes unused. Either way the option silently did nothing useful, or broke the
configure step outright.
Resolve the tested standards first, then check them: when none of them can
reach simdutf, skip the dependency and say so, naming which of the two reasons
applies and how to fix it. The tests then run against the scalar validator,
which is what would have happened anyway.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Use record_buffer::data() so clang does not flag it unneeded
The record_buffer test type declares data() and size() so the
is_contiguous_byte_container trait can see both and still reject the
type on its value_type. data() was never called, so clang's
-Wunneeded-member-function (under -Weverything -Werror) failed the
C++20 build. Assert that data() points at the underlying bytes: it
ODR-uses the member and documents the property the type is meant to
demonstrate.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XAYM1qhSA2FDaDcGfPW3fG
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Skip the float fast path when it cannot succeed
parse_float_fast() (Clinger) needs a significand below 2^53, so it always
declines once the mantissa has 17 or more significant digits. convert_number()
called it unconditionally, so those numbers were walked an extra time before
strtod had to run anyway. On streaming input, where scanning is byte-at-a-time
and there is no compensating win, that made canada.json about 6% slower than
develop.
Derive the significant-digit count from token_buffer indices - the digits are
not scanned again - and skip the call when it is guaranteed to decline. Both
scanners pass the offset where the mantissa ends; the count only has to be
corrected for a leading "0", which the JSON grammar admits nowhere else. The
integer path returns before the check, so integer-heavy input is unaffected.
Values are unchanged: this only avoids an attempt that would have failed.
Verified bit-exact against develop over every number in canada.json,
floats.json, signed_ints.json, unsigned_ints.json, small_signed_ints.json,
citm_catalog.json and twitter.json, for both the contiguous and the streaming
scanner.
parse, streaming develop before after
canada.json 19.4ms 20.5ms 19.3ms
floats.json 135.9ms 131.8ms 128.0ms
parse, contiguous develop before after
canada.json 15.5ms 12.9ms 11.7ms
floats.json 98.6ms 69.8ms 66.7ms
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Undefine the four JSON_HEDLEY_* macros that leak after including json.hpp
include/nlohmann/detail/macro_unscope.hpp includes hedley_undef.hpp to
#undef every JSON_HEDLEY_* macro so none of them leak into the including
translation unit. Four macros were missing from that list and therefore
stayed defined after #include <nlohmann/json.hpp>:
- JSON_HEDLEY_PRAGMA
- JSON_HEDLEY_PREDICT_TRUE
- JSON_HEDLEY_PREDICT_FALSE
- JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE
hedley_undef.hpp is generated (via `make update_hedley`) by grepping
hedley.hpp for its own internal `#undef JSON_HEDLEY_X` redefinition
guards. JSON_HEDLEY_PRAGMA/PREDICT_TRUE/PREDICT_FALSE have no such guard
in upstream Hedley, so they were never picked up. The guard for
JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE also has an upstream typo
(`JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE`), so hedley_undef.hpp
was undefining the wrong (never-defined) name.
Fixes:
- include/nlohmann/thirdparty/hedley/hedley_undef.hpp: corrected the
DECLSPEC_ATTRIBUTE typo and added the three missing #undef lines,
keeping the file's alphabetical ordering.
- Makefile (update_hedley target): changed hedley_undef.hpp generation to
extract macro names directly from every `#define JSON_HEDLEY_...` in
hedley.hpp instead of from existing `#undef` guards, so a future
`make update_hedley` run undefines every macro Hedley actually defines,
even ones without a pre-existing redefinition guard. This was not run
in this PR (it would also pull in an unrelated upstream Hedley sync);
hedley_undef.hpp was hand-patched instead and single_include was
regenerated with `make amalgamate`.
- tests/src/unit-no-macro-leak.cpp: new regression test (picked up
automatically by tests/CMakeLists.txt's existing unit-*.cpp glob) that
includes json.hpp and then #ifdef/#error-checks every JSON_HEDLEY_*
macro name, so any future leak of any of the 151 vendored macros fails
the build, not just the four fixed here.
Fixes#5408.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Derive the JSON_HEDLEY_* leak-check test from hedley.hpp at build time
tests/src/unit-no-macro-leak.cpp previously hardcoded a static list of
~151 #ifdef/#error checks, one per JSON_HEDLEY_* macro name known at the
time it was written. That list would silently go stale the next time
`make update_hedley` pulls in a vendor update that adds, removes, or
renames a macro, since nothing would force it to be regenerated.
Add cmake/scripts/gen_hedley_undef_check.cmake, which derives the full
list of JSON_HEDLEY_* macro names directly from
include/nlohmann/thirdparty/hedley/hedley.hpp:
- tests/CMakeLists.txt uses it (MODE=checks) to (re)generate
hedley_undef_checks.inc at configure and build time, and wires the
generating custom target as a dependency of the test-no-macro-leak_cpp*
targets so it can never build against a stale copy. unit-no-macro-leak.cpp
now just #include-s the generated file inside its TEST_CASE instead of
carrying the checks itself.
- The Makefile's `update_hedley` target now delegates hedley_undef.hpp
generation to the same script (MODE=undef, new `update_hedley_undef`
target), so the vendored header, the generated #undef list, and the
generated test checks are all derived from the same extraction logic and
cannot drift apart.
This mirrors the approach taken independently in #5415 for the same
issue (#5408), credited there to a self-regenerating mechanism that
"can never drift again" -- ported into this branch instead of the
static list originally proposed here.
Verified with a local CMake configure + build + ctest, both against
include/ (JSON_MultipleHeaders=ON) and against the amalgamated
single_include/nlohmann/json.hpp (JSON_MultipleHeaders=OFF), and by
temporarily deleting a #undef line from hedley_undef.hpp to confirm the
generated test actually fails on a real leak.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix REUSE compliance failure in gen_hedley_undef_check.cmake
The generated file's embedded banner contains the literal text
'SPDX-License-Identifier: MIT' as part of the *content* being written
to hedley_undef.hpp, not as this .cmake script's own REUSE header (it
is already covered by the blanket 'Files: *' rule in .reuse/dep5).
The reuse tool matched that embedded line as an SPDX tag for the
script itself and failed to parse the trailing 'MIT\n")' as a valid
SPDX License Expression, breaking ci_reuse_compliance. Wrap the
embedded banner in REUSE-IgnoreStart/REUSE-IgnoreEnd comments, as
recommended by the tool's own diagnostic output.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Reformat parser_callback_t example with astyle
The file uses "json & /*parsed*/" in three lambda parameter lists, which
astyle rewrites to "json& /*parsed*/" per --align-reference=type. The
drift went unnoticed because CI never format-checked the documentation
examples; "make pretty" does cover them.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Format-check the documentation examples in CI
The examples live in docs/mkdocs/docs/examples, but both format checks
still referenced the long-gone docs/examples path:
- check_amalgamation.yml passed it to find, which printed an error for
the missing path and carried on, so astyle only ever saw include and
tests. The step still exited 0.
- ci.cmake globbed it into INDENT_FILES, and a GLOB_RECURSE over a
missing directory silently yields nothing, so the ci_test_amalgamation
target skipped the examples too.
Either way the 231 example files have never been format-checked. Point
both at the real path, and guard the workflow with an explicit directory
check so a future rename fails the job instead of quietly shrinking the
file list again.
Also drop the dead docs/examples/** path filter from
publish_documentation.yml; docs/mkdocs/** already covers the examples.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* docs: use HTTPS for the astyle and cppcheck links in README
Both links were still `http://`. `astyle.sourceforge.net` serves HTTPS
directly; `cppcheck.sourceforge.net` redirects to
`https://cppcheck.sourceforge.io`, which is also the URL already used in
`docs/mkdocs/docs/community/quality_assurance.md`, so the redirect is
skipped here.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* CI: suppress -Wc2y-extensions for Clang
Clang 22.1 (now shipped by silkeh/clang:latest) diagnoses __COUNTER__ as a
C2y extension, and does so in C++ mode as well. Under -Weverything -Werror
this breaks every ci_test_clang_cxx* / ci_test_clang_libcxx_cxx* target,
independently of the code under test.
The library itself does not use __COUNTER__; all diagnostics originate in
vendored Doctest (DOCTEST_ANONYMOUS, used by TEST_CASE and SECTION).
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix stale Clang -Weverything suppression comments; eliminate -Wno-missing-noreturn
cmake/clang_flags.cmake claimed -Wno-unsafe-buffer-usage was needed only
for Doctest and that -Wno-missing-noreturn had "no way to silence...
otherwise" (PR #4871, which never actually attempted a source fix).
Neither held up under investigation (todo 130):
- -Wno-unsafe-buffer-usage is pervasive (208 distinct sites across 19
files measured with clang trunk in silkeh/clang:dev), spanning the
library's own low-level numeric/buffer code (to_chars, serializer,
lexer, binary reader/writer, input adapters, json_pointer) as well as
vendored Doctest itself (96 of the 208 sites). A source-level fix is
not feasible at this scale; the comment now says so instead of
blaming Doctest alone.
- -Wno-missing-noreturn had exactly two real trigger sites, both
genuinely and unconditionally non-returning: a test-only throwing
allocator (tests/src/unit-allocator.cpp) and, previously undiscovered,
wide_string_input_adapter::get_elements<T>() in
include/nlohmann/detail/input/input_adapters.hpp. Verified this isn't
a wider pattern by checking all 160 JSON_THROW call sites in the
library for functions whose entire body is an unconditional throw.
Annotated both ([[noreturn]] in the test file, since JSON_HEDLEY_NO_RETURN
is #undef'd by the time test code runs; JSON_HEDLEY_NO_RETURN in the
library file, its first real use anywhere in the codebase) and
dropped the suppression entirely.
single_include/nlohmann/json.hpp regenerated via `make amalgamate`;
`make check-amalgamation` passes.
Verified in Docker (silkeh/clang:dev, matching the ci_static_analysis_clang
CI job): baseline builds clean, and the full 194-target test suite builds
with zero warnings under the corrected CLANG_CXXFLAGS (-Wno-missing-noreturn
no longer in the list). Also sanity-compiled and ran unit-allocator.cpp and
unit-wstring.cpp on host Apple Clang to confirm behavior is unchanged.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix MSVC C4702 warning caused by JSON_HEDLEY_NO_RETURN on get_elements()
PR #5250 annotated wide_string_input_adapter::get_elements<T>() with
JSON_HEDLEY_NO_RETURN (it unconditionally throws). On MSVC this expands to
__declspec(noreturn), and MSVC correctly determined that the code following
its call in binary_reader.hpp is unreachable for that instantiation, firing
C4702 under /W4 /WX in the msvc, msvc-vs2026, and msvc-arm64 Debug jobs.
Clang doesn't flag this case, so the Docker verification for #5250 (which
only checked Clang -Weverything) didn't catch it.
This is the same warning class already tolerated for Release builds since
PR #5216, where MSVC's optimizer independently found the same dead code
after /Od was removed. Extend that existing /wd4702 suppression to Debug
builds too, instead of reverting the noreturn annotation.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Test ci_cuda_example against a CUDA version matrix at C++20 (#3907)
The ci_cuda_example job compiled against the json-ci image's CUDA
11.0 toolkit at cuda_std_11, which cannot exercise #3907 (a c++20
parse error in iteration_proxy.hpp's enable_borrowed_range reported
under nvcc). Switch the job to pull official nvidia/cuda devel images
directly and matrix across CUDA 11.8-12.6 at cuda_std_20 so CI can
empirically confirm which versions are actually affected before any
source-level fix is attempted.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix nvcc CUDA 12.0/12.1 C++20 ranges parse error (#3907)
The diagnostic matrix in this PR confirmed the affected range exactly:
nvcc 12.0.1 and 12.1.1 both fail with "expected initializer before
'<' token" on iteration_proxy.hpp's enable_borrowed_range variable
template specialization at -std=c++20; 12.2.2 and newer already build
cleanly. Guard JSON_HAS_RANGES off for that narrow nvcc version range,
matching the existing GCC-11/libstdc++ carve-outs in the same ifdef
chain, and regenerate single_include accordingly.
Broaden the CUDA smoke test to also exercise comparisons
(operator==/operator<=>, gated independently by
JSON_HAS_THREE_WAY_COMPARISON) and range-based iteration, not just
dump()/erase(), so the fix's actual scope is evidenced by CI rather
than assumed from the single reported symptom.
Have tests/cuda_example/CMakeLists.txt pick the newest C++ standard
the detected nvcc version actually supports (20/17/11) instead of
hard-requiring C++20, so older toolkits build at a lower standard
instead of failing CMake configure outright. This is test-project-local
only; the JSON_HAS_RANGES guard is what protects real client code,
since a header can't control what -std= flag it's compiled with.
Right-size the CI matrix from the 8-version diagnostic sweep down to
11.8.0 (C++17 fallback path) / 12.1.1 (permanent #3907 regression
guard) / 12.6.3 (recent coverage), and update the compiler-version
table in the quality assurance docs to match.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* Fix ci_cuda_example CUDA 11.8 build after C++17 fallback (#3907)
The 11.8.0 leg's graceful C++17 fallback (added in the previous commit)
worked correctly, but the broadened smoke test used the <=> operator
unconditionally, which isn't valid syntax pre-C++20 — nvcc rejected it
with "expected an expression" once the CMake logic picked cuda_std_17
for the older toolkit. Gate those two lines behind
JSON_HAS_THREE_WAY_COMPARISON like the library itself does internally.
Sanity-compiled the file as plain C++ at both -std=c++17 (skips the
guarded block) and -std=c++20 (includes it) locally; the actual nvcc
build is verified via CI on PR #5248.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
---------
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
* fix: treat single-element brace-init as copy/move
When passing a json value using brace initialization with a single element
(e.g., `json j{someObj}` or `foo({someJson})`), C++ always prefers the
initializer_list constructor over the copy/move constructor. This caused
the value to be unexpectedly wrapped in a single-element array.
This bug was previously compiler-dependent (GCC wrapped, Clang did not),
but Clang 20 started matching GCC behavior, making it a universal issue.
Fix: In the initializer_list constructor, when type deduction is enabled
and the list has exactly one element, copy/move it directly instead of
creating a single-element array.
Before:
json obj = {{"key", 1}};
json j{obj}; // -> [{"key":1}] (wrong: array)
foo({obj}); // -> [{"key":1}] (wrong: array)
After:
json j{obj}; // -> {"key":1} (correct: copy)
foo({obj}); // -> {"key":1} (correct: copy)
To explicitly create a single-element array, use json::array({value}).
Fixes the issue #5074
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* fix: regenerate amalgamated single_include/nlohmann/json.hpp
- Add missing comment from include/nlohmann/json.hpp explaining the
single-element brace-init fix (issue #5074)
- Fix extra 4-space indentation in embedded json_fwd.hpp section
Regenerated by running: make amalgamate
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* Revert brace-init semantics change and fix amalgamation
The single-element brace-init change was a breaking change that cannot be accepted upstream. Reverted all related source, test, and doc changes, then regenerated single_include with correct indentation to pass the amalgamation CI check.
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* Fix: add JSON_BRACE_INIT_COPY_SEMANTICS opt-in macro for issue #5074
Single-element brace initialization wrapping in an array cannot be fixed without breaking existing code. Added JSON_BRACE_INIT_COPY_SEMANTICS as an opt-in macro (default 0) so users can enable copy/move semantics for single-element brace init without affecting anyone relying on the current behavior.
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* docs: add dedicated macro page and CI test target for JSON_BRACE_INIT_COPY_SEMANTICS
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* fix: remove compiler-dependent assertions from #5074 regression test
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* fix: use defined() guard for JSON_BRACE_INIT_COPY_SEMANTICS to satisfy -Wundef
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* docs: fix section name in json_brace_init_copy_semantics.md to pass style check
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* docs: move Default definition section before Notes to fix style check order
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
---------
Signed-off-by: Samaresh Kumar Singh <ssam3003@gmail.com>
* 🔧 add more GCC warning flags
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 👷 use GCC image for GCC test
* 🔧 adjust flags
* 👷 adjust jobs
* 👷 adjust jobs
* CMake: configure pkg-config with @only
And use @ replacement instead of {}, since ${} is significant in
pkg-config's format
* CMake: use conventional prefix and incluedir variables in pkg-config
This is more standard, and has some advantages when cross compiling.
This also means that the pkg-config files generated by Meson and CMake
now match.
* Move UDLs into nlohmann::literals::json_literals namespace
* Add 'using namespace' to unit tests
* Add 'using namespace' to examples
* Add 'using namespace' to README
* Move UDL mkdocs pages out of basic_json/
* Update documentation
* Update docset index
* Add JSON_GlobalUDLs CMake option
* Add unit test
* Build examples without global UDLs
* Add CI target