Skip to content

Commit a259ecc

Browse files
authored
Fix to_json for enums when the enum has an unsigned underlying type. (#4237)
* Enhance the UDT unit test to expose the issue Add a new enum type with uint64_t as the underlying type. Use it in the overall UDT. Not strictly needed, but it helps exercise its expected usage. Create an object of this enum type with a large value (negative if cast to int64_t). Perform several checks on this object as converted to `json`, which fail without the fix. * Fix the issue in the relevant `to_json` overload. Select the correct json type depending on the signedness of the enum's underlying type. This fixes the new checks in the unit test. * Add the fix to the single_include I ran `make pretty` but that modified 20 files, performing a significant amount of indentation changes, none of them related to my change. I ran `make amalgamate`, but that did nothing. Apparently, the make rule won't run if the single_include files have already been updated by `make pretty`. I forced `make amalgamate` to do the work by touching the file with the fix. I then decided to keep just the minimal needed change: the addition of the fix to the single_include file. I just am not conversant enough in Linux to know whether I installed astyle correctly (had to clone the source from a beta branch and build, in order to get support for `--squeeze-lines`). * Resolve CI errors and use qualified `std::uint64_t` The fix was relying on implicit conversions in the non-taken branch. - Ordinarily (work on a C++20 codebase) I would have used `if constexpr` here, sidestepping the issue, but that's not available on C++11 so I didn't bother. - So instead of an `if` statement, I used a compile-time constant to select the correct overload. - This is arguably better in this case, anyway. I was using function-style casts for typed constants, which I consider superior for constants, but the CI checks disagree, so changed all to `static_cast`. - For some reason, the CI checks didn't point at all of them, so I hope I caught them all myself. Built with clang14 and all unit tests pass. --------- Co-authored-by: Juan Carlos Arevalo Baeza (JCAB) <jcab@ntdev.microsoft.com>
1 parent 3780b41 commit a259ecc

File tree

3 files changed

+24
-9
lines changed

3 files changed

+24
-9
lines changed

include/nlohmann/detail/conversions/to_json.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,8 @@ template<typename BasicJsonType, typename EnumType,
320320
inline void to_json(BasicJsonType& j, EnumType e) noexcept
321321
{
322322
using underlying_type = typename std::underlying_type<EnumType>::type;
323-
external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));
323+
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
324+
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
324325
}
325326
#endif // JSON_DISABLE_ENUM_SERIALIZATION
326327

single_include/nlohmann/json.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -5697,7 +5697,8 @@ template<typename BasicJsonType, typename EnumType,
56975697
inline void to_json(BasicJsonType& j, EnumType e) noexcept
56985698
{
56995699
using underlying_type = typename std::underlying_type<EnumType>::type;
5700-
external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));
5700+
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
5701+
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
57015702
}
57025703
#endif // JSON_DISABLE_ENUM_SERIALIZATION
57035704

tests/src/unit-udt.cpp

+20-7
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,15 @@ struct contact
6767
contact(person p, address a) : m_person(std::move(p)), m_address(std::move(a)) {}
6868
};
6969

70+
enum class book_id : std::uint64_t;
71+
7072
struct contact_book
7173
{
7274
name m_book_name{};
75+
book_id m_book_id{};
7376
std::vector<contact> m_contacts{};
7477
contact_book() = default;
75-
contact_book(name n, std::vector<contact> c) : m_book_name(std::move(n)), m_contacts(std::move(c)) {}
78+
contact_book(name n, book_id i, std::vector<contact> c) : m_book_name(std::move(n)), m_book_id(i), m_contacts(std::move(c)) {}
7679
};
7780
} // namespace udt
7881

@@ -129,7 +132,7 @@ static void to_json(nlohmann::json& j, const contact& c)
129132

130133
static void to_json(nlohmann::json& j, const contact_book& cb)
131134
{
132-
j = json{{"name", cb.m_book_name}, {"contacts", cb.m_contacts}};
135+
j = json{{"name", cb.m_book_name}, {"id", cb.m_book_id}, {"contacts", cb.m_contacts}};
133136
}
134137

135138
// operators
@@ -161,8 +164,8 @@ static bool operator==(const contact& lhs, const contact& rhs)
161164

162165
static bool operator==(const contact_book& lhs, const contact_book& rhs)
163166
{
164-
return std::tie(lhs.m_book_name, lhs.m_contacts) ==
165-
std::tie(rhs.m_book_name, rhs.m_contacts);
167+
return std::tie(lhs.m_book_name, lhs.m_book_id, lhs.m_contacts) ==
168+
std::tie(rhs.m_book_name, rhs.m_book_id, rhs.m_contacts);
166169
}
167170
} // namespace udt
168171

@@ -219,6 +222,7 @@ static void from_json(const nlohmann::json& j, contact& c)
219222
static void from_json(const nlohmann::json& j, contact_book& cb)
220223
{
221224
cb.m_book_name = j["name"].get<name>();
225+
cb.m_book_id = j["id"].get<book_id>();
222226
cb.m_contacts = j["contacts"].get<std::vector<contact>>();
223227
}
224228
} // namespace udt
@@ -237,7 +241,8 @@ TEST_CASE("basic usage" * doctest::test_suite("udt"))
237241
const udt::person senior_programmer{{42}, {"王芳"}, udt::country::china};
238242
const udt::address addr{"Paris"};
239243
const udt::contact cpp_programmer{sfinae_addict, addr};
240-
const udt::contact_book book{{"C++"}, {cpp_programmer, {senior_programmer, addr}}};
244+
const udt::book_id large_id{static_cast<udt::book_id>(static_cast<std::uint64_t>(1) << 63)}; // verify large unsigned enums are handled correctly
245+
const udt::contact_book book{{"C++"}, static_cast<udt::book_id>(42u), {cpp_programmer, {senior_programmer, addr}}};
241246

242247
SECTION("conversion to json via free-functions")
243248
{
@@ -248,21 +253,26 @@ TEST_CASE("basic usage" * doctest::test_suite("udt"))
248253
CHECK(json("Paris") == json(addr));
249254
CHECK(json(cpp_programmer) ==
250255
R"({"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"})"_json);
256+
CHECK(json(large_id) == json(static_cast<std::uint64_t>(1) << 63));
257+
CHECK(json(large_id) > 0u);
258+
CHECK(to_string(json(large_id)) == "9223372036854775808");
259+
CHECK(json(large_id).is_number_unsigned());
251260

252261
CHECK(
253262
json(book) ==
254-
R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
263+
R"({"name":"C++", "id":42, "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json);
255264

256265
}
257266

258267
SECTION("conversion from json via free-functions")
259268
{
260269
const auto big_json =
261-
R"({"name":"C++", "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json;
270+
R"({"name":"C++", "id":42, "contacts" : [{"person" : {"age":23, "name":"theo", "country":"France"}, "address":"Paris"}, {"person" : {"age":42, "country":"中华人民共和国", "name":"王芳"}, "address":"Paris"}]})"_json;
262271
SECTION("via explicit calls to get")
263272
{
264273
const auto parsed_book = big_json.get<udt::contact_book>();
265274
const auto book_name = big_json["name"].get<udt::name>();
275+
const auto book_id = big_json["id"].get<udt::book_id>();
266276
const auto contacts =
267277
big_json["contacts"].get<std::vector<udt::contact>>();
268278
const auto contact_json = big_json["contacts"].at(0);
@@ -282,6 +292,7 @@ TEST_CASE("basic usage" * doctest::test_suite("udt"))
282292
CHECK(contact == cpp_programmer);
283293
CHECK(contacts == book.m_contacts);
284294
CHECK(book_name == udt::name{"C++"});
295+
CHECK(book_id == book.m_book_id);
285296
CHECK(book == parsed_book);
286297
}
287298

@@ -303,6 +314,7 @@ TEST_CASE("basic usage" * doctest::test_suite("udt"))
303314
{
304315
const udt::contact_book parsed_book = big_json;
305316
const udt::name book_name = big_json["name"];
317+
const udt::book_id book_id = big_json["id"];
306318
const std::vector<udt::contact> contacts = big_json["contacts"];
307319
const auto contact_json = big_json["contacts"].at(0);
308320
const udt::contact contact = contact_json;
@@ -320,6 +332,7 @@ TEST_CASE("basic usage" * doctest::test_suite("udt"))
320332
CHECK(contact == cpp_programmer);
321333
CHECK(contacts == book.m_contacts);
322334
CHECK(book_name == udt::name{"C++"});
335+
CHECK(book_id == static_cast<udt::book_id>(42u));
323336
CHECK(book == parsed_book);
324337
}
325338
#endif

0 commit comments

Comments
 (0)