Do not use Qt's keyword macros as identifiers

The prefix tree used by unflatten() (#5836) had a member named slots, which
Qt defines as an empty macro, so including a Qt header before this library
failed to compile. Rename it to table and add a test that includes the
library with Qt's keyword macros (slots, signals, emit, foreach, forever)
defined.

Fixes #5865

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-17 14:02:41 +02:00
1 parent 0e95fe8200
commit 22497c7128
4 files changed
+91 -26

No files matched your search

@@ -135,6 +135,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
headers are included.
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
- [x] The library is compiled with Qt's keyword macros (`slots`, `signals`, `emit`, `foreach`, `forever`) being defined
to make sure it does not use them as identifiers (see
[`unit-qt_keywords.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-qt_keywords.cpp)).
- [x] The library is compiled with exceptions disabled to support alternative means of error handling.
## Stable public API
+13 -13
View File
@@ -525,7 +525,7 @@ class json_pointer
{}
};
std::vector<slot> slots;
std::vector<slot> table;
std::size_t used = 0;
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
@@ -540,19 +540,19 @@ class json_pointer
void grow()
{
std::vector<slot> old(slots.size() * 2);
old.swap(slots);
const std::size_t mask = slots.size() - 1;
std::vector<slot> old(table.size() * 2);
old.swap(table);
const std::size_t mask = table.size() - 1;
for (const auto& s : old)
{
if (s.token != nullptr)
{
std::size_t i = s.hash & mask;
while (slots[i].token != nullptr)
while (table[i].token != nullptr)
{
i = (i + 1) & mask;
}
slots[i] = s;
table[i] = s;
}
}
}
@@ -565,7 +565,7 @@ class json_pointer
// start with the empty prefix only
prefix_tree()
: slots(16)
: table(16)
, parent(1, 0)
, is_array(1, false)
{}
@@ -575,20 +575,20 @@ class json_pointer
std::size_t add_child(std::size_t id, const string_t& reference_token)
{
const std::size_t hash = hash_of(id, reference_token);
const std::size_t mask = slots.size() - 1;
const std::size_t mask = table.size() - 1;
std::size_t i = hash & mask;
while (slots[i].token != nullptr)
while (table[i].token != nullptr)
{
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
{
return slots[i].id;
return table[i].id;
}
i = (i + 1) & mask;
}
// new prefix
const std::size_t child = parent.size();
slots[i] = slot(hash, id, &reference_token, child);
table[i] = slot(hash, id, &reference_token, child);
if (reference_token == "0")
{
is_array[id] = true;
@@ -597,7 +597,7 @@ class json_pointer
is_array.push_back(false);
// keep the load factor below 1/2
if (++used * 2 > slots.size())
if (++used * 2 > table.size())
{
grow();
}
+13 -13
View File
@@ -21497,7 +21497,7 @@ class json_pointer
{}
};
std::vector<slot> slots;
std::vector<slot> table;
std::size_t used = 0;
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
@@ -21512,19 +21512,19 @@ class json_pointer
void grow()
{
std::vector<slot> old(slots.size() * 2);
old.swap(slots);
const std::size_t mask = slots.size() - 1;
std::vector<slot> old(table.size() * 2);
old.swap(table);
const std::size_t mask = table.size() - 1;
for (const auto& s : old)
{
if (s.token != nullptr)
{
std::size_t i = s.hash & mask;
while (slots[i].token != nullptr)
while (table[i].token != nullptr)
{
i = (i + 1) & mask;
}
slots[i] = s;
table[i] = s;
}
}
}
@@ -21537,7 +21537,7 @@ class json_pointer
// start with the empty prefix only
prefix_tree()
: slots(16)
: table(16)
, parent(1, 0)
, is_array(1, false)
{}
@@ -21547,20 +21547,20 @@ class json_pointer
std::size_t add_child(std::size_t id, const string_t& reference_token)
{
const std::size_t hash = hash_of(id, reference_token);
const std::size_t mask = slots.size() - 1;
const std::size_t mask = table.size() - 1;
std::size_t i = hash & mask;
while (slots[i].token != nullptr)
while (table[i].token != nullptr)
{
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
{
return slots[i].id;
return table[i].id;
}
i = (i + 1) & mask;
}
// new prefix
const std::size_t child = parent.size();
slots[i] = slot(hash, id, &reference_token, child);
table[i] = slot(hash, id, &reference_token, child);
if (reference_token == "0")
{
is_array[id] = true;
@@ -21569,7 +21569,7 @@ class json_pointer
is_array.push_back(false);
// keep the load factor below 1/2
if (++used * 2 > slots.size())
if (++used * 2 > table.size())
{
grow();
}
+62
View File
@@ -0,0 +1,62 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#include "doctest_compatibility.h"
// Qt defines its keywords as macros (see qtmetamacros.h and qforeach.h) unless
// QT_NO_KEYWORDS is defined, and it is common to include Qt headers before this
// library; it must therefore not use these words as identifiers (see #5865)
#define slots
#define signals public
#define emit
#define Q_FOREACH(variable, container) for (variable : container)
#define foreach Q_FOREACH
#define forever for (;;)
#include <nlohmann/json.hpp>
using nlohmann::json;
TEST_CASE("Qt keyword macros")
{
SECTION("parse and dump")
{
const json j = json::parse(R"({"a": [1, 2.5, "x", null, true]})");
CHECK(j.dump() == R"({"a":[1,2.5,"x",null,true]})");
}
SECTION("flatten and unflatten")
{
const json j = {{"a", {{"b", {1, 2}}, {"c", "d"}}}, {"e", nullptr}};
CHECK(j.flatten().unflatten() == j);
}
SECTION("JSON pointer")
{
json j = {{"a", {{"b", {1, 2}}}}};
CHECK(j.at(json::json_pointer("/a/b/1")) == 2);
j[json::json_pointer("/a/c")] = 3;
CHECK(j.contains(json::json_pointer("/a/c")));
}
SECTION("binary formats")
{
const json j = {{"a", {1, 2, 3}}, {"b", "c"}};
CHECK(json::from_cbor(json::to_cbor(j)) == j);
CHECK(json::from_msgpack(json::to_msgpack(j)) == j);
CHECK(json::from_ubjson(json::to_ubjson(j)) == j);
CHECK(json::from_bjdata(json::to_bjdata(j)) == j);
CHECK(json::from_bson(json::to_bson(j)) == j);
}
SECTION("patch and diff")
{
const json source = {{"a", 1}, {"b", {1, 2}}};
const json target = {{"a", 2}, {"c", "d"}};
CHECK(source.patch(json::diff(source, target)) == target);
}
}