diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 34270e2368..4c0aa25fa4 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -29,6 +29,16 @@ #include #include +/* helper for strict enum error reporting */ +template +inline void throw_enum_error(const BasicJsonType& j, const char* enum_type) +{ + JSON_THROW(::nlohmann::detail::type_error::create( + 302, + std::string("invalid value for ") + enum_type + ": " + j.dump(), + &j)); +} + // include after macro_scope.hpp #ifdef JSON_HAS_CPP_17 #include // optional diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 833b7ddf04..bd4bdc6f3a 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -8,6 +8,7 @@ #pragma once +#include #include // declval, pair #include #include @@ -253,6 +254,47 @@ e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } +/*! +@brief macro to briefly define a mapping between an enum and JSON (strict version) +@def NLOHMANN_JSON_SERIALIZE_ENUM_STRICT +@since version 3.12.0 +*/ + +#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + if (it != std::end(m)) \ + { \ + e = it->first; \ + return; \ + } \ + throw_enum_error(j, #ENUM_TYPE); \ + } + // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index ceb7a9f11d..cafb246a00 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -227,6 +227,7 @@ +#include #include // declval, pair // #include // __ _____ _____ _____ @@ -2617,6 +2618,47 @@ JSON_HEDLEY_DIAGNOSTIC_POP e = ((it != std::end(m)) ? it : std::begin(m))->first; \ } +/*! +@brief macro to briefly define a mapping between an enum and JSON (strict version) +@def NLOHMANN_JSON_SERIALIZE_ENUM_STRICT +@since version 3.12.0 +*/ + +#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + if (it != std::end(m)) \ + { \ + e = it->first; \ + return; \ + } \ + throw_enum_error(j, #ENUM_TYPE); \ + } + // Ugly macros to avoid uglier copy-paste when specializing basic_json. They // may be removed in the future once the class is split. @@ -4862,6 +4904,16 @@ NLOHMANN_JSON_NAMESPACE_END // #include +/* helper for strict enum error reporting */ +template +inline void throw_enum_error(const BasicJsonType& j, const char* enum_type) +{ + JSON_THROW(::nlohmann::detail::type_error::create( + 302, + std::string("invalid value for ") + enum_type + ": " + j.dump(), + &j)); +} + // include after macro_scope.hpp #ifdef JSON_HAS_CPP_17 #include // optional diff --git a/tests/src/unit-serialize_enum_strict.cpp b/tests/src/unit-serialize_enum_strict.cpp new file mode 100644 index 0000000000..58fa32e2a7 --- /dev/null +++ b/tests/src/unit-serialize_enum_strict.cpp @@ -0,0 +1,40 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2025 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" +#include + +using json = nlohmann::json; + +namespace ns +{ +enum class Color { red, green, blue, unknown }; + +NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color, +{ + { Color::unknown, "unknown" }, + { Color::red, "red" }, + { Color::green, "green" }, + { Color::blue, "blue" } +}) +} // namespace ns + +TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT throws on unknown input string") +{ + json j = "purple"; // not mapped + ns::Color c; + + CHECK_THROWS_AS((j.get_to(c)), nlohmann::detail::type_error); +} + +TEST_CASE("NLOHMANN_JSON_SERIALIZE_ENUM_STRICT still deserializes valid values") +{ + json j = "green"; + auto c = j.get(); + CHECK(c == ns::Color::green); +}