Compare commits

..

1 Commits

Author SHA1 Message Date
Niels Lohmann 282f84cc8e 🤜 Doctest 2.5.3
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-08 11:04:03 +02:00
23 changed files with 7232 additions and 5578 deletions
-12
View File
@@ -4,8 +4,6 @@ updates:
directory: / directory: /
schedule: schedule:
interval: daily interval: daily
cooldown:
default-days: 7
groups: groups:
codeql-action: codeql-action:
patterns: patterns:
@@ -15,33 +13,23 @@ updates:
directory: /docs/mkdocs directory: /docs/mkdocs
schedule: schedule:
interval: daily interval: daily
cooldown:
default-days: 7
- package-ecosystem: pip - package-ecosystem: pip
directory: /tools/astyle directory: /tools/astyle
schedule: schedule:
interval: daily interval: daily
cooldown:
default-days: 7
- package-ecosystem: pip - package-ecosystem: pip
directory: /tools/generate_natvis directory: /tools/generate_natvis
schedule: schedule:
interval: daily interval: daily
cooldown:
default-days: 7
- package-ecosystem: pip - package-ecosystem: pip
directory: /tools/serve_header directory: /tools/serve_header
schedule: schedule:
interval: daily interval: daily
cooldown:
default-days: 7
- package-ecosystem: pip - package-ecosystem: pip
directory: /cmake/requirements directory: /cmake/requirements
schedule: schedule:
interval: daily interval: daily
cooldown:
default-days: 7
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
# SEMGREP_APP_TOKEN is still passed through so registry auth works if a # SEMGREP_APP_TOKEN is still passed through so registry auth works if a
# token is ever added. # token is ever added.
- name: Install Semgrep - name: Install Semgrep
run: python3 -m pip install --user semgrep==1.168.0 run: python3 -m pip install --user semgrep
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error # `semgrep scan --sarif` always exits 0 even with findings; continue-on-error
# is a safety net so the SARIF upload still runs if the scan itself errors. # is a safety net so the SARIF upload still runs if the scan itself errors.
+2 -4
View File
@@ -90,13 +90,11 @@ jobs:
- name: Get latest CMake and ninja - name: Get latest CMake and ninja
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4 uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Set extra CXX_FLAGS for latest std_version - name: Set extra CXX_FLAGS for latest std_version
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
run: | run: |
if [ "${{ matrix.std_version }}" = "latest" ]; then if [ "${{ matrix.std_version }}" = "latest" ]; then
echo "flags=/permissive- /std:c++latest /utf-8 /W4 /WX /wd5285" >> $GITHUB_ENV echo "flags=/permissive- /std:c++latest /utf-8 /W4 /WX" >> $GITHUB_ENV
else else
echo "flags=/W4 /WX /wd5285" >> $GITHUB_ENV echo "flags=/W4 /WX" >> $GITHUB_ENV
fi fi
shell: bash shell: bash
- name: Run CMake (Release) - name: Run CMake (Release)
+1 -1
View File
@@ -5,7 +5,7 @@
# -Wno-extra-semi-stmt The library uses assert which triggers this warning. # -Wno-extra-semi-stmt The library uses assert which triggers this warning.
# -Wno-padded We do not care about padding warnings. # -Wno-padded We do not care about padding warnings.
# -Wno-covered-switch-default All switches list all cases and a default case. # -Wno-covered-switch-default All switches list all cases and a default case.
# -Wno-unsafe-buffer-usage Otherwise Doctest would not compile. # -Wno-unsafe-buffer-usage Otherwise library code (strlen) would not compile.
# -Wno-missing-noreturn We found no way to silence this warning otherwise, see PR #4871 # -Wno-missing-noreturn We found no way to silence this warning otherwise, see PR #4871
set(CLANG_CXXFLAGS set(CLANG_CXXFLAGS
-2
View File
@@ -4,7 +4,6 @@
# -Wno-aggregate-return The library uses aggregate returns. # -Wno-aggregate-return The library uses aggregate returns.
# -Wno-long-long The library uses the long long type to interface with system functions. # -Wno-long-long The library uses the long long type to interface with system functions.
# -Wno-namespaces The library uses namespaces. # -Wno-namespaces The library uses namespaces.
# -Wno-nrvo Doctest triggers this warning.
# -Wno-padded We do not care about padding warnings. # -Wno-padded We do not care about padding warnings.
# -Wno-system-headers We do not care about warnings in system headers. # -Wno-system-headers We do not care about warnings in system headers.
# -Wno-templates The library uses templates. # -Wno-templates The library uses templates.
@@ -232,7 +231,6 @@ set(GCC_CXXFLAGS
-Wnonnull -Wnonnull
-Wnonnull-compare -Wnonnull-compare
-Wnormalized=nfkc -Wnormalized=nfkc
-Wno-nrvo
-Wnull-dereference -Wnull-dereference
-Wodr -Wodr
-Wold-style-cast -Wold-style-cast
+1 -12
View File
@@ -82,13 +82,7 @@ basic_json(basic_json&& other) noexcept;
4. This is a constructor for existing `basic_json` types. It does not hijack copy/move constructors, since the parameter 4. This is a constructor for existing `basic_json` types. It does not hijack copy/move constructors, since the parameter
has different template arguments than the current ones. has different template arguments than the current ones.
The constructor tries to convert the internal `m_value` of the parameter. Each member value (object, array, string, The constructor tries to convert the internal `m_value` of the parameter.
etc.) is serialized via the corresponding `to_json()` overload. For objects and strings, the conversion requires
that the *target* `basic_json` type's `object_t::key_type` (or `string_t`) be directly constructible from the
*source* type's corresponding member type via `is_constructible`. If this requirement is not met, the conversion
does not fail to compile; instead, it silently falls back to the array-conversion path, which represents objects
as arrays of `[key, value]` pairs and strings as arrays of character codes. This is a known limitation tracked in
[issue #3425](https://github.com/nlohmann/json/issues/3425).
5. Creates a JSON value of type array or object from the passed initializer list `init`. In case `type_deduction` is 5. Creates a JSON value of type array or object from the passed initializer list `init`. In case `type_deduction` is
`#!cpp true` (default), the type of the JSON value to be created is deducted from the initializer list `init` `#!cpp true` (default), the type of the JSON value to be created is deducted from the initializer list `init`
@@ -153,11 +147,6 @@ basic_json(basic_json&& other) noexcept;
- `BasicJsonType` is a `basic_json` type. - `BasicJsonType` is a `basic_json` type.
- `BasicJsonType` has different template arguments than `basic_json_t`. - `BasicJsonType` has different template arguments than `basic_json_t`.
**Note:** For cross-`basic_json` conversions to produce correct results, the target `basic_json`'s
`object_t::key_type` and `string_t` must be directly constructible from the source `basic_json`'s
corresponding types. See the description of overload (4) above for details on what happens when
this requirement is not met.
`U`: `U`:
: `uncvref_t<CompatibleType>` : `uncvref_t<CompatibleType>`
@@ -93,15 +93,6 @@ alphabetical order as `std::map` with `std::less` is used by default. Please not
[RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON [RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON
objects. objects.
#### Cross-`basic_json` conversion requirements
When converting an object from one `basic_json` specialization to another via the
[converting constructor](basic_json.md#overload-4), the target `object_t`'s `key_type` must be
directly constructible from the source `basic_json`'s `string_t` type (or more generally, from the
source object's key type). If this requirement is not met, the conversion does not fail; instead,
the object is silently converted as an array of key-value pairs, which is incorrect. See
[issue #3425](https://github.com/nlohmann/json/issues/3425) for details and an example.
## Examples ## Examples
??? example ??? example
+3 -12
View File
@@ -6,18 +6,9 @@ namespace std {
} }
``` ```
Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. To satisfy the Return a hash value for a JSON object. The hash function tries to rely on `std::hash` where possible. Furthermore, the
`std::hash` contract, numeric JSON values that compare equal must hash to the same value. This means: type of the JSON value is taken into account to have different hash values for `#!json null`, `#!cpp 0`, `#!cpp 0U`, and
`#!cpp false`, etc.
- `json(42)`, `json(42u)`, and `json(42.0)` all hash to the same value
- `json(0)`, `json(0u)`, and `json(0.0)` all hash to the same value
Different types hash differently for non-numeric types (e.g., `#!json null`, `#!cpp false`, and strings all have distinct hashes).
**Edge case:** For very large integers outside the exact representable range of the floating-point type (beyond ~2^53 for
typical `double`), the hash values for integer and floating-point values may differ, even if the floating-point value
was obtained by casting the integer (due to precision loss). This is a documented limitation arising from how the
comparison operator normalizes numeric types.
## Examples ## Examples
@@ -45,15 +45,6 @@ This implementation is interoperable as it does compare strings code unit by cod
String values are stored as pointers in a `basic_json` type. That is, for any access to string values, a pointer of type String values are stored as pointers in a `basic_json` type. That is, for any access to string values, a pointer of type
`string_t*` must be dereferenced. `string_t*` must be dereferenced.
#### Cross-`basic_json` conversion requirements
When converting a string value from one `basic_json` specialization to another via the
[converting constructor](basic_json.md#overload-4), the target `string_t` must be directly
constructible from the source `basic_json`'s `string_t` type. If this requirement is not met, the
conversion does not fail; instead, the string is silently converted as an array of character codes,
which is incorrect. See [issue #3425](https://github.com/nlohmann/json/issues/3425) for details
and an example.
## Examples ## Examples
??? example ??? example
-1
View File
@@ -11,7 +11,6 @@ int main()
<< "hash(false) = " << std::hash<json> {}(json(false)) << '\n' << "hash(false) = " << std::hash<json> {}(json(false)) << '\n'
<< "hash(0) = " << std::hash<json> {}(json(0)) << '\n' << "hash(0) = " << std::hash<json> {}(json(0)) << '\n'
<< "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n' << "hash(0U) = " << std::hash<json> {}(json(0U)) << '\n'
<< "hash(0.0) = " << std::hash<json> {}(json(0.0)) << '\n'
<< "hash(\"\") = " << std::hash<json> {}(json("")) << '\n' << "hash(\"\") = " << std::hash<json> {}(json("")) << '\n'
<< "hash({}) = " << std::hash<json> {}(json::object()) << '\n' << "hash({}) = " << std::hash<json> {}(json::object()) << '\n'
<< "hash([]) = " << std::hash<json> {}(json::array()) << '\n' << "hash([]) = " << std::hash<json> {}(json::array()) << '\n'
+4 -5
View File
@@ -1,9 +1,8 @@
hash(null) = 2654435769 hash(null) = 2654435769
hash(false) = 2654436030 hash(false) = 2654436030
hash(0) = 2654436221 hash(0) = 2654436095
hash(0U) = 2654436221 hash(0U) = 2654436156
hash(0.0) = 2654436221 hash("") = 6142509191626859748
hash("") = 11160318156688833227
hash({}) = 2654435832 hash({}) = 2654435832
hash([]) = 2654435899 hash([]) = 2654435899
hash({"hello": "world"}) = 3701319991624763853 hash({"hello": "world"}) = 4469488738203676328
-4
View File
@@ -1,4 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://json.nlohmann.me/sitemap.xml
-25
View File
@@ -1,25 +0,0 @@
"""Copy each documentation page's Markdown source into the built site."""
# Creates a `<path>.md` sibling of each HTML output (for example,
# `features/comments/` becomes `features/comments.md`) so agents and tools can
# fetch the raw Markdown directly instead of parsing rendered HTML.
import os
import shutil
_pages = []
def on_files(files, config):
global _pages
_pages = [f for f in files if f.is_documentation_page()]
return files
def on_post_build(config):
site_dir = config["site_dir"]
for file in _pages:
url = file.url.rstrip("/")
target = os.path.join(site_dir, (url or "index") + ".md")
os.makedirs(os.path.dirname(target), exist_ok=True)
shutil.copyfile(file.abs_src_path, target)
-30
View File
@@ -367,9 +367,6 @@ markdown_extensions:
auto_append: auto_append:
- ../includes/glossary.md - ../includes/glossary.md
hooks:
- hooks/copy_markdown_source.py
plugins: plugins:
- search: - search:
separator: '[\s\-\.]' separator: '[\s\-\.]'
@@ -392,33 +389,6 @@ plugins:
- https://nlohmann.github.io/json/* - https://nlohmann.github.io/json/*
- mailto:* - mailto:*
- privacy - privacy
- llmstxt:
markdown_description: >
JSON for Modern C++ is a C++11 header-only library implementing a JSON
value type with an STL-like API, JSON Pointer/Patch, CBOR/MessagePack/
BSON/UBJSON/BJData binary format support, and a SAX-style parser interface.
sections:
Home:
- index.md
- home/*.md
Features:
- features/*.md
- features/binary_formats/*.md
- features/element_access/*.md
- features/parsing/*.md
- features/types/*.md
Integration:
- integration/*.md
API Documentation:
- api/*.md
- api/basic_json/*.md
- api/adl_serializer/*.md
- api/byte_container_with_subtype/*.md
- api/json_pointer/*.md
- api/json_sax/*.md
- api/macros/*.md
Community:
- community/*.md
extra_css: extra_css:
- css/custom.css - css/custom.css
-1
View File
@@ -7,6 +7,5 @@ mkdocs-material-extensions==1.3.1 # extensions
mkdocs-minify-plugin==0.8.0 # plugin "minify" mkdocs-minify-plugin==0.8.0 # plugin "minify"
mkdocs-redirects==1.2.3 # plugin "redirects" mkdocs-redirects==1.2.3 # plugin "redirects"
mkdocs-htmlproofer-plugin==1.5.0 # plugin "htmlproofer" mkdocs-htmlproofer-plugin==1.5.0 # plugin "htmlproofer"
mkdocs-llmstxt==0.5.0 # plugin "llmstxt"
PyYAML==6.0.3 # linter PyYAML==6.0.3 # linter
+6 -86
View File
@@ -11,8 +11,6 @@
#include <cstdint> // uint8_t #include <cstdint> // uint8_t
#include <cstddef> // size_t #include <cstddef> // size_t
#include <functional> // hash #include <functional> // hash
#include <limits> // numeric_limits
#include <cmath> // isfinite
#include <nlohmann/detail/abi_macros.hpp> #include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/value_t.hpp> #include <nlohmann/detail/value_t.hpp>
@@ -28,63 +26,12 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
return seed; return seed;
} }
// Check if a number_integer_t value is exactly representable as number_float_t
// Returns true if static_cast<number_integer_t>(static_cast<number_float_t>(val)) == val
template<typename BasicJsonType>
inline bool is_exactly_representable_as_float(typename BasicJsonType::number_integer_t val) noexcept
{
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_float_t = typename BasicJsonType::number_float_t;
// If the float type's mantissa covers the integer type's entire range, all values round-trip
constexpr int float_digits = std::numeric_limits<number_float_t>::digits;
constexpr int int_digits = std::numeric_limits<number_integer_t>::digits;
if (float_digits >= int_digits)
{
return true;
}
// For values outside float's exact range, they don't round-trip
// The safe way to check: compute the max magnitude that round-trips
// Using unsigned arithmetic to avoid UB with negating INT_MIN
// Max magnitude representable exactly: 2^(digits-1) - 1 for signed, 2^digits - 1 for unsigned range
// But we're checking a signed value, so use 2^digits as the threshold
constexpr auto max_exact = static_cast<number_integer_t>(1) << (float_digits - 1);
// Check absolute value against this threshold
if (val >= 0)
{
if (val >= max_exact) return false;
}
else
{
// For negative values, check via unsigned wrapping arithmetic
// -val in unsigned domain; if it wraps, the value is too negative
auto unsigned_abs = static_cast<typename BasicJsonType::number_unsigned_t>(-val);
if (unsigned_abs >= static_cast<typename BasicJsonType::number_unsigned_t>(max_exact))
{
return false;
}
}
// For values within the exact range, verify the round-trip
const auto f = static_cast<number_float_t>(val);
return std::isfinite(f) && static_cast<number_integer_t>(f) == val;
}
/*! /*!
@brief hash a JSON value @brief hash a JSON value
The hash function tries to rely on std::hash where possible. Furthermore, the The hash function tries to rely on std::hash where possible. Furthermore, the
type of the JSON value is taken into account to have different hash values for type of the JSON value is taken into account to have different hash values for
most types. However, numeric types (number_integer, number_unsigned, number_float) null, 0, 0U, and false, etc.
are hashed to satisfy the std::hash contract: if two json values compare equal,
they must have equal hash values. This means json(42), json(42u), and json(42.0)
all hash to the same value (since they compare equal). For large integer values
outside the exact representable range of the float type, integer values are hashed
in their own domain to avoid precision loss.
@tparam BasicJsonType basic_json specialization @tparam BasicJsonType basic_json specialization
@param j JSON value to hash @param j JSON value to hash
@@ -143,47 +90,20 @@ std::size_t hash(const BasicJsonType& j)
case BasicJsonType::value_t::number_integer: case BasicJsonType::value_t::number_integer:
{ {
const auto v = j.template get<number_integer_t>(); const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
// Use a shared numeric type tag so all numeric types that are equal hash the same return combine(type, h);
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
if (is_exactly_representable_as_float<BasicJsonType>(v))
{
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v));
return combine(numeric_type, h);
}
else
{
const auto h = std::hash<number_integer_t> {}(v);
return combine(numeric_type, h);
}
} }
case BasicJsonType::value_t::number_unsigned: case BasicJsonType::value_t::number_unsigned:
{ {
const auto v = j.template get<number_unsigned_t>(); const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
// Normalize to signed (matching operator== behavior for U-vs-I comparison) return combine(type, h);
const auto v_as_signed = static_cast<number_integer_t>(v);
// Use a shared numeric type tag so all numeric types that are equal hash the same
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
if (is_exactly_representable_as_float<BasicJsonType>(v_as_signed))
{
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v_as_signed));
return combine(numeric_type, h);
}
else
{
const auto h = std::hash<number_integer_t> {}(v_as_signed);
return combine(numeric_type, h);
}
} }
case BasicJsonType::value_t::number_float: case BasicJsonType::value_t::number_float:
{ {
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>()); const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float); return combine(type, h);
return combine(numeric_type, h);
} }
case BasicJsonType::value_t::binary: case BasicJsonType::value_t::binary:
+9 -29
View File
@@ -465,12 +465,18 @@ class serializer
{ {
if (codepoint <= 0xFFFF) if (codepoint <= 0xFFFF)
{ {
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint)); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
static_cast<std::uint16_t>(codepoint)));
bytes += 6;
} }
else else
{ {
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u))); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))); static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
bytes += 12;
} }
} }
else else
@@ -677,32 +683,6 @@ class serializer
return result; return result;
} }
/*!
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
*
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
* four hex digits) at position @a pos of @a string_buffer via a nibble
* lookup table, avoiding the format-string parsing and locale machinery of
* `snprintf`. Advances @a pos by the number of bytes written (6).
*
* @param[in] pos position in @a string_buffer to write at; there must
* be at least 6 bytes of headroom
* @param[in] codeunit 16-bit value to encode
*/
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
{
JSON_ASSERT(string_buffer.size() - pos >= 6);
constexpr const char* nibble_to_hex = "0123456789abcdef";
string_buffer[pos + 0] = '\\';
string_buffer[pos + 1] = 'u';
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
pos += 6;
}
// templates to avoid warnings about useless casts // templates to avoid warnings about useless casts
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0> template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
bool is_negative_number(NumberType x) bool is_negative_number(NumberType x)
+76 -176
View File
@@ -3712,71 +3712,71 @@ NLOHMANN_JSON_NAMESPACE_END
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ #define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t #include <cstdint> // int64_t, uint64_t
#include <map> // map #include <map> // map
#include <memory> // allocator #include <memory> // allocator
#include <string> // string #include <string> // string
#include <vector> // vector #include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp> // #include <nlohmann/detail/abi_macros.hpp>
/*! /*!
@brief namespace for Niels Lohmann @brief namespace for Niels Lohmann
@see https://github.com/nlohmann @see https://github.com/nlohmann
@since version 1.0.0 @since version 1.0.0
*/ */
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
/*! /*!
@brief default JSONSerializer template argument @brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization. for serialization.
*/ */
template<typename T = void, typename SFINAE = void> template<typename T = void, typename SFINAE = void>
struct adl_serializer; struct adl_serializer;
/// a class to store JSON values /// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/ /// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType = template<template<typename U, typename V, typename... Args> class ObjectType =
std::map, std::map,
template<typename U, typename... Args> class ArrayType = std::vector, template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool, class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t, class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t, class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double, class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator, template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer = template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer, adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void> class CustomBaseClass = void>
class basic_json; class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/ /// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType> template<typename RefStringType>
class json_pointer; class json_pointer;
/*! /*!
@brief default specialization @brief default specialization
@sa https://json.nlohmann.me/api/json/ @sa https://json.nlohmann.me/api/json/
*/ */
using json = basic_json<>; using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order /// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/ /// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator> template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map; struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys /// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/ /// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>; using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
@@ -5749,7 +5749,7 @@ NLOHMANN_JSON_NAMESPACE_END
// #include <nlohmann/detail/macro_scope.hpp> // #include <nlohmann/detail/macro_scope.hpp>
// JSON_HAS_CPP_17 // JSON_HAS_CPP_17
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
#include <optional> // optional #include <optional> // optional
#endif #endif
@@ -6677,8 +6677,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <cstdint> // uint8_t #include <cstdint> // uint8_t
#include <cstddef> // size_t #include <cstddef> // size_t
#include <functional> // hash #include <functional> // hash
#include <limits> // numeric_limits
#include <cmath> // isfinite
// #include <nlohmann/detail/abi_macros.hpp> // #include <nlohmann/detail/abi_macros.hpp>
@@ -6696,63 +6694,12 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
return seed; return seed;
} }
// Check if a number_integer_t value is exactly representable as number_float_t
// Returns true if static_cast<number_integer_t>(static_cast<number_float_t>(val)) == val
template<typename BasicJsonType>
inline bool is_exactly_representable_as_float(typename BasicJsonType::number_integer_t val) noexcept
{
using number_integer_t = typename BasicJsonType::number_integer_t;
using number_float_t = typename BasicJsonType::number_float_t;
// If the float type's mantissa covers the integer type's entire range, all values round-trip
constexpr int float_digits = std::numeric_limits<number_float_t>::digits;
constexpr int int_digits = std::numeric_limits<number_integer_t>::digits;
if (float_digits >= int_digits)
{
return true;
}
// For values outside float's exact range, they don't round-trip
// The safe way to check: compute the max magnitude that round-trips
// Using unsigned arithmetic to avoid UB with negating INT_MIN
// Max magnitude representable exactly: 2^(digits-1) - 1 for signed, 2^digits - 1 for unsigned range
// But we're checking a signed value, so use 2^digits as the threshold
constexpr auto max_exact = static_cast<number_integer_t>(1) << (float_digits - 1);
// Check absolute value against this threshold
if (val >= 0)
{
if (val >= max_exact) return false;
}
else
{
// For negative values, check via unsigned wrapping arithmetic
// -val in unsigned domain; if it wraps, the value is too negative
auto unsigned_abs = static_cast<typename BasicJsonType::number_unsigned_t>(-val);
if (unsigned_abs >= static_cast<typename BasicJsonType::number_unsigned_t>(max_exact))
{
return false;
}
}
// For values within the exact range, verify the round-trip
const auto f = static_cast<number_float_t>(val);
return std::isfinite(f) && static_cast<number_integer_t>(f) == val;
}
/*! /*!
@brief hash a JSON value @brief hash a JSON value
The hash function tries to rely on std::hash where possible. Furthermore, the The hash function tries to rely on std::hash where possible. Furthermore, the
type of the JSON value is taken into account to have different hash values for type of the JSON value is taken into account to have different hash values for
most types. However, numeric types (number_integer, number_unsigned, number_float) null, 0, 0U, and false, etc.
are hashed to satisfy the std::hash contract: if two json values compare equal,
they must have equal hash values. This means json(42), json(42u), and json(42.0)
all hash to the same value (since they compare equal). For large integer values
outside the exact representable range of the float type, integer values are hashed
in their own domain to avoid precision loss.
@tparam BasicJsonType basic_json specialization @tparam BasicJsonType basic_json specialization
@param j JSON value to hash @param j JSON value to hash
@@ -6811,47 +6758,20 @@ std::size_t hash(const BasicJsonType& j)
case BasicJsonType::value_t::number_integer: case BasicJsonType::value_t::number_integer:
{ {
const auto v = j.template get<number_integer_t>(); const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());
// Use a shared numeric type tag so all numeric types that are equal hash the same return combine(type, h);
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
if (is_exactly_representable_as_float<BasicJsonType>(v))
{
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v));
return combine(numeric_type, h);
}
else
{
const auto h = std::hash<number_integer_t> {}(v);
return combine(numeric_type, h);
}
} }
case BasicJsonType::value_t::number_unsigned: case BasicJsonType::value_t::number_unsigned:
{ {
const auto v = j.template get<number_unsigned_t>(); const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());
// Normalize to signed (matching operator== behavior for U-vs-I comparison) return combine(type, h);
const auto v_as_signed = static_cast<number_integer_t>(v);
// Use a shared numeric type tag so all numeric types that are equal hash the same
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float);
if (is_exactly_representable_as_float<BasicJsonType>(v_as_signed))
{
const auto h = std::hash<number_float_t> {}(static_cast<number_float_t>(v_as_signed));
return combine(numeric_type, h);
}
else
{
const auto h = std::hash<number_integer_t> {}(v_as_signed);
return combine(numeric_type, h);
}
} }
case BasicJsonType::value_t::number_float: case BasicJsonType::value_t::number_float:
{ {
const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>()); const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());
const auto numeric_type = static_cast<std::size_t>(BasicJsonType::value_t::number_float); return combine(type, h);
return combine(numeric_type, h);
} }
case BasicJsonType::value_t::binary: case BasicJsonType::value_t::binary:
@@ -20042,12 +19962,18 @@ class serializer
{ {
if (codepoint <= 0xFFFF) if (codepoint <= 0xFFFF)
{ {
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint)); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
static_cast<std::uint16_t>(codepoint)));
bytes += 6;
} }
else else
{ {
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u))); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))); static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),
static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));
bytes += 12;
} }
} }
else else
@@ -20254,32 +20180,6 @@ class serializer
return result; return result;
} }
/*!
* @brief write a lowercase "\uXXXX" escape sequence into @a string_buffer
*
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
* four hex digits) at position @a pos of @a string_buffer via a nibble
* lookup table, avoiding the format-string parsing and locale machinery of
* `snprintf`. Advances @a pos by the number of bytes written (6).
*
* @param[in] pos position in @a string_buffer to write at; there must
* be at least 6 bytes of headroom
* @param[in] codeunit 16-bit value to encode
*/
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
{
JSON_ASSERT(string_buffer.size() - pos >= 6);
constexpr const char* nibble_to_hex = "0123456789abcdef";
string_buffer[pos + 0] = '\\';
string_buffer[pos + 1] = 'u';
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
pos += 6;
}
// templates to avoid warnings about useless casts // templates to avoid warnings about useless casts
template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0> template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>
bool is_negative_number(NumberType x) bool is_negative_number(NumberType x)
@@ -21091,10 +20991,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false const bool ignore_trailing_commas = false
) )
{ {
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter), return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas); std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
} }
private: private:
@@ -21792,8 +21692,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::enable_if_t < detail::enable_if_t <
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 > !detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(), JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
std::forward<CompatibleType>(val)))) std::forward<CompatibleType>(val))))
{ {
JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
set_parents(); set_parents();
@@ -22596,7 +22496,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::has_from_json<basic_json_t, ValueType>::value, detail::has_from_json<basic_json_t, ValueType>::value,
int > = 0 > int > = 0 >
ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept( ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>()))) JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
{ {
auto ret = ValueType(); auto ret = ValueType();
JSONSerializer<ValueType>::from_json(*this, ret); JSONSerializer<ValueType>::from_json(*this, ret);
@@ -22638,7 +22538,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::has_non_default_from_json<basic_json_t, ValueType>::value, detail::has_non_default_from_json<basic_json_t, ValueType>::value,
int > = 0 > int > = 0 >
ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept( ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>()))) JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))
{ {
return JSONSerializer<ValueType>::from_json(*this); return JSONSerializer<ValueType>::from_json(*this);
} }
@@ -22788,7 +22688,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
detail::has_from_json<basic_json_t, ValueType>::value, detail::has_from_json<basic_json_t, ValueType>::value,
int > = 0 > int > = 0 >
ValueType & get_to(ValueType& v) const noexcept(noexcept( ValueType & get_to(ValueType& v) const noexcept(noexcept(
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v))) JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
{ {
JSONSerializer<ValueType>::from_json(*this, v); JSONSerializer<ValueType>::from_json(*this, v);
return v; return v;
-3
View File
@@ -232,9 +232,6 @@ TEST_CASE("algorithms")
// only the first four elements are expected to be sorted, the rest are // only the first four elements are expected to be sorted, the rest are
// unspecified by the standard // unspecified by the standard
const json expected({nullptr, false, true, 3}); const json expected({nullptr, false, true, 3});
// std::equal below only bounds-checks the first range; assert the
// second range is at least as long to rule out an over-read (CWE-126)
CHECK(std::distance(begin(expected), end(expected)) >= 4);
CHECK(std::equal(j.begin(), j.begin() + 4, begin(expected))); CHECK(std::equal(j.begin(), j.begin() + 4, begin(expected)));
} }
} }
+8 -6
View File
@@ -322,12 +322,14 @@ TEST_CASE("alternative string type")
SECTION("JSON pointer") SECTION("JSON pointer")
{ {
// Direct conversion from a json literal to alt_json is not supported due to issue #3425: // conversion from json to alt_json fails to compile (see #3425);
// alt_json's string_t (alt_string) is not directly constructible from std::string, so the // attempted fix(*) produces: [[['b','a','r'],['b','a','z']]] (with each char being an integer)
// cross-basic_json conversion falls back to the array-conversion path, incorrectly representing // (*) disable implicit conversion for json_refs of any basic_json type
// objects as arrays of [key, value] pairs and strings as arrays of character codes. // alt_json j = R"(
// See https://github.com/nlohmann/json/issues/3425 for details. // {
// Workaround: use alt_json::parse() instead of implicit conversion. // "foo": ["bar", "baz"]
// }
// )"_json;
auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})"); auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})");
CHECK(j.at(alt_json::json_pointer("/foo/0")) == j["foo"][0]); CHECK(j.at(alt_json::json_pointer("/foo/0")) == j["foo"][0]);
-26
View File
@@ -168,32 +168,6 @@ TEST_CASE("convenience functions")
CHECK_THROWS_WITH_AS(check_escaped("\xC2"), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&); CHECK_THROWS_WITH_AS(check_escaped("\xC2"), "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0xC2", json::type_error&);
} }
SECTION("string escape with ensure_ascii")
{
// control characters are escaped regardless of ensure_ascii
check_escaped("\x01", "\\u0001", true);
check_escaped("\x1f", "\\u001f", true);
// non-ASCII code points in the Basic Multilingual Plane are emitted as
// a single lowercase \uXXXX escape (exercises every nibble position)
check_escaped("\xC2\x80", "\\u0080", true); // U+0080
check_escaped("\xC3\xBF", "\\u00ff", true); // U+00FF (ÿ)
check_escaped("\xDF\xBF", "\\u07ff", true); // U+07FF
check_escaped("\xE4\xBD\xA0", "\\u4f60", true); // U+4F60 (你)
check_escaped("\xEA\xAF\x8D", "\\uabcd", true); // U+ABCD
check_escaped("\xEF\xBF\xBD", "\\ufffd", true); // U+FFFD (replacement char, all-f nibbles)
// code points outside the BMP are emitted as a UTF-16 surrogate pair
// of two lowercase \uXXXX escapes
check_escaped("\xF0\x90\x80\x80", "\\ud800\\udc00", true); // U+10000 (lowest astral)
check_escaped("\xF0\x9F\x98\x80", "\\ud83d\\ude00", true); // U+1F600 (😀)
check_escaped("\xF4\x8F\xBF\xBF", "\\udbff\\udfff", true); // U+10FFFF (highest code point)
// with ensure_ascii disabled, non-ASCII input is passed through verbatim
check_escaped("\xE4\xBD\xA0", "\xE4\xBD\xA0", false);
check_escaped("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", false);
}
SECTION("string concat") SECTION("string concat")
{ {
using nlohmann::detail::concat; using nlohmann::detail::concat;
+6 -22
View File
@@ -35,10 +35,10 @@ TEST_CASE("hash<nlohmann::json>")
// number // number
hashes.insert(std::hash<json> {}(json(0))); hashes.insert(std::hash<json> {}(json(0)));
hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0)))); // now same hash as json(0) hashes.insert(std::hash<json> {}(json(static_cast<unsigned>(0))));
hashes.insert(std::hash<json> {}(json(0.0))); // now same hash as json(0)
hashes.insert(std::hash<json> {}(json(-1))); hashes.insert(std::hash<json> {}(json(-1)));
hashes.insert(std::hash<json> {}(json(0.0)));
hashes.insert(std::hash<json> {}(json(42.23))); hashes.insert(std::hash<json> {}(json(42.23)));
// array // array
@@ -60,16 +60,7 @@ TEST_CASE("hash<nlohmann::json>")
// discarded // discarded
hashes.insert(std::hash<json> {}(json(json::value_t::discarded))); hashes.insert(std::hash<json> {}(json(json::value_t::discarded)));
// Note: json(0), json(0U), and json(0.0) now hash to the same value CHECK(hashes.size() == 21);
// (to satisfy the std::hash contract: equal values must hash equally)
// So we expect 19 distinct hashes instead of 21
CHECK(hashes.size() == 19);
// Verify the std::hash contract: equal values must hash equally
CHECK(std::hash<json>{}(json(0)) == std::hash<json>{}(json(static_cast<unsigned>(0))));
CHECK(std::hash<json>{}(json(0)) == std::hash<json>{}(json(0.0)));
CHECK(std::hash<json>{}(json(42)) == std::hash<json>{}(json(42u)));
CHECK(std::hash<json>{}(json(42)) == std::hash<json>{}(json(42.0)));
} }
TEST_CASE("hash<nlohmann::ordered_json>") TEST_CASE("hash<nlohmann::ordered_json>")
@@ -93,10 +84,10 @@ TEST_CASE("hash<nlohmann::ordered_json>")
// number // number
hashes.insert(std::hash<ordered_json> {}(ordered_json(0))); hashes.insert(std::hash<ordered_json> {}(ordered_json(0)));
hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0)))); // now same hash as ordered_json(0) hashes.insert(std::hash<ordered_json> {}(ordered_json(static_cast<unsigned>(0))));
hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0))); // now same hash as ordered_json(0)
hashes.insert(std::hash<ordered_json> {}(ordered_json(-1))); hashes.insert(std::hash<ordered_json> {}(ordered_json(-1)));
hashes.insert(std::hash<ordered_json> {}(ordered_json(0.0)));
hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23))); hashes.insert(std::hash<ordered_json> {}(ordered_json(42.23)));
// array // array
@@ -118,12 +109,5 @@ TEST_CASE("hash<nlohmann::ordered_json>")
// discarded // discarded
hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded))); hashes.insert(std::hash<ordered_json> {}(ordered_json(ordered_json::value_t::discarded)));
// Note: ordered_json(0), ordered_json(0U), and ordered_json(0.0) now hash to the same value CHECK(hashes.size() == 21);
CHECK(hashes.size() == 19);
// Verify the std::hash contract for ordered_json as well
CHECK(std::hash<ordered_json>{}(ordered_json(0)) == std::hash<ordered_json>{}(ordered_json(static_cast<unsigned>(0))));
CHECK(std::hash<ordered_json>{}(ordered_json(0)) == std::hash<ordered_json>{}(ordered_json(0.0)));
CHECK(std::hash<ordered_json>{}(ordered_json(42)) == std::hash<ordered_json>{}(ordered_json(42u)));
CHECK(std::hash<ordered_json>{}(ordered_json(42)) == std::hash<ordered_json>{}(ordered_json(42.0)));
} }
+7115 -5102
View File
File diff suppressed because it is too large Load Diff