Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1074,7 +1074,7 @@ Just as in [Arbitrary Type Conversions](#arbitrary-types-conversions) above,

Other Important points:

- When using `get<ENUM_TYPE>()`, undefined JSON values will default to the first pair specified in your map. Select this default pair carefully.
- When using `get<ENUM_TYPE>()`, undefined JSON values will default to the first pair specified in your map. Select this default pair carefully. If you desire an exception in this circumstance use `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()` which behaves identically except for throwing an exception on unrecognized values.
- If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the map will be returned when converting to or from JSON.

### Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)
Expand Down
3 changes: 2 additions & 1 deletion docs/mkdocs/docs/features/enum_conversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
Other Important points:

- When using `get<ENUM_TYPE>()`, undefined JSON values will default to the first pair specified in your map. Select this
default pair carefully.
default pair carefully. If you desire an exception in this circumstance use `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()`
which behaves identically except for throwing an exception on unrecognised values.
- If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the
map will be returned when converting to or from JSON.
- To disable the default serialization of enumerators as integers and force a compiler error instead, see [`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
39 changes: 39 additions & 0 deletions include/nlohmann/detail/macro_scope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,45 @@
e = ((it != std::end(m)) ? it : std::begin(m))->first; \
}

/*!
@brief macro to briefly define a mapping between an enum and JSON with exception
on invalid input
@def NLOHMANN_JSON_SERIALIZE_ENUM_STRICT
@since version 3.12.0
*/
#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \
template<typename BasicJsonType> \
inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
{ \
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
auto it = std::find_if(std::begin(m), std::end(m), \
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
{ \
return ej_pair.first == e; \
}); \
if (it != std::end(m)) j = it->second; \
else throw nlohmann::detail::out_of_range::create(403, "enum value out of range", nullptr);\
} \
template<typename BasicJsonType> \
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
{ \
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
auto it = std::find_if(std::begin(m), std::end(m), \
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
{ \
return ej_pair.second == j; \
}); \
if (it != std::end(m)) e = it->first; \
else throw nlohmann::detail::out_of_range::create(403, "enum value out of range", nullptr);\
}


// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
// may be removed in the future once the class is split.

Expand Down
39 changes: 39 additions & 0 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2617,6 +2617,45 @@ 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 with exception
on invalid input
@def NLOHMANN_JSON_SERIALIZE_ENUM_STRICT
@since version 3.12.0
*/
#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \
template<typename BasicJsonType> \
inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
{ \
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
auto it = std::find_if(std::begin(m), std::end(m), \
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
{ \
return ej_pair.first == e; \
}); \
if (it != std::end(m)) j = it->second; \
else throw nlohmann::detail::out_of_range::create(403, "enum value out of range", nullptr);\
} \
template<typename BasicJsonType> \
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
{ \
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
auto it = std::find_if(std::begin(m), std::end(m), \
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
{ \
return ej_pair.second == j; \
}); \
if (it != std::end(m)) e = it->first; \
else throw nlohmann::detail::out_of_range::create(403, "enum value out of range", nullptr);\
}


// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
// may be removed in the future once the class is split.

Expand Down
71 changes: 71 additions & 0 deletions tests/src/unit-conversions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,77 @@ TEST_CASE("JSON to enum mapping")
}
}

enum class strict_cards {kreuz, pik, herz, karo};

// NOLINTNEXTLINE(misc-use-internal-linkage,misc-const-correctness,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(strict_cards,
{
{strict_cards::kreuz, "kreuz"},
{strict_cards::pik, "pik"},
{strict_cards::pik, "puk"}, // second entry for cards::puk; will not be used
{strict_cards::herz, "herz"},
{strict_cards::karo, "karo"}
})

enum StrictTaskState // NOLINT(cert-int09-c,readability-enum-initial-value,cppcoreguidelines-use-enum-class)
{
STRICT_TS_STOPPED,
STRICT_TS_RUNNING,
STRICT_TS_COMPLETED,
STRICT_TS_INVALID = -1,
};

// NOLINTNEXTLINE(misc-const-correctness,misc-use-internal-linkage,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(StrictTaskState,
{
{STRICT_TS_INVALID, nullptr},
{STRICT_TS_STOPPED, "stopped"},
{STRICT_TS_RUNNING, "running"},
{STRICT_TS_COMPLETED, "completed"},
})

TEST_CASE("Strict JSON to enum mapping")
{
SECTION("enum class")
{
// enum -> json
CHECK(json(strict_cards::kreuz) == "kreuz");
CHECK(json(strict_cards::pik) == "pik");
CHECK(json(strict_cards::herz) == "herz");
CHECK(json(strict_cards::karo) == "karo");

// json -> enum
CHECK(strict_cards::kreuz == json("kreuz"));
CHECK(strict_cards::pik == json("pik"));
CHECK(strict_cards::herz == json("herz"));
CHECK(strict_cards::karo == json("karo"));

// invalid json -> exception thrown
json _;
CHECK_THROWS_WITH_AS(_ = json("what?").get<strict_cards>(), "[json.exception.out_of_range.403] enum value out of range", json::out_of_range&);
}

SECTION("traditional enum")
{
// enum -> json
CHECK(json(STRICT_TS_STOPPED) == "stopped");
CHECK(json(STRICT_TS_RUNNING) == "running");
CHECK(json(STRICT_TS_COMPLETED) == "completed");
CHECK(json(STRICT_TS_INVALID) == json());

// json -> enum
CHECK(STRICT_TS_STOPPED == json("stopped"));
CHECK(STRICT_TS_RUNNING == json("running"));
CHECK(STRICT_TS_COMPLETED == json("completed"));
CHECK(STRICT_TS_INVALID == json());

// invalid json -> exception thrown
json _;
CHECK_THROWS_WITH_AS(_ = json("what?").get<StrictTaskState>(), "[json.exception.out_of_range.403] enum value out of range", json::out_of_range&);
}
}


#ifdef JSON_HAS_CPP_17
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
TEST_CASE("std::filesystem::path")
Expand Down
Loading