-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
Serialize enum #5151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Serialize enum #5151
Changes from all commits
be589be
16e2669
ff66dad
768eb66
6453674
1cc9a04
6d9e0a8
95c29df
7217e98
d221941
21fe58f
8daaab0
8f6701b
3915dd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| # NLOHMANN_JSON_SERIALIZE_ENUM_STRICT | ||
|
|
||
| ```cpp | ||
| #define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(type, conversion...) | ||
| ``` | ||
|
|
||
| By default, enum values are serialized to JSON as integers. In some cases, this could result in undesired behavior. If | ||
| an enum is modified or re-ordered after data has been serialized to JSON, the later deserialized JSON data may be | ||
| undefined or a different enum value than was originally intended. | ||
|
|
||
| `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` allows to define a user-defined serialization for every enumerator that | ||
| throws an exception on undefined input. | ||
|
|
||
| ## Parameters | ||
|
|
||
| `type` (in) | ||
| : name of the enum to serialize/deserialize | ||
|
|
||
| `conversion` (in) | ||
| : a pair of an enumerator and a JSON serialization; arbitrary pairs can be given as a comma-separated list | ||
|
|
||
| ## Default definition | ||
|
|
||
| The macro adds two functions to the namespace which take care of the serialization and deserialization: | ||
|
|
||
| ```cpp | ||
| template<typename BasicJsonType> | ||
| inline void to_json(BasicJsonType& j, const type& e); | ||
| template<typename BasicJsonType> | ||
| inline void from_json(const BasicJsonType& j, type& e); | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| !!! info "Prerequisites" | ||
|
|
||
| The macro must be used inside the namespace of the enum. | ||
|
|
||
| !!! important "Important notes" | ||
|
|
||
| - When using [`get<ENUM_TYPE>()`](../basic_json/get.md), undefined JSON values will throw an exception. | ||
| - If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the | ||
| list will be returned when converting to or from JSON. See example 2 below. | ||
|
|
||
| ## Examples | ||
|
|
||
| ??? example "Example 1: Basic usage" | ||
|
|
||
| The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` can be used to serialize/deserialize both classical enums and | ||
| C++11 enum classes: | ||
|
|
||
| ```cpp hl_lines="16 17 18 19 20 21 22 29 30 31 32 33" | ||
| --8<-- "examples/nlohmann_json_serialize_enum_strict.cpp" | ||
| ``` | ||
|
|
||
| Output: | ||
|
|
||
| ```json | ||
| --8<-- "examples/nlohmann_json_serialize_enum_strict.output" | ||
| ``` | ||
|
|
||
| ??? example "Example 2: Multiple conversions for one enumerator" | ||
|
|
||
| The example shows how to use multiple conversions for a single enumerator. In the example, `Color::red` will always | ||
| be *serialized* to `"red"`, because the first occurring conversion. The second conversion, however, offers an | ||
| alternative *deserialization* from `"rot"` to `Color::red`. | ||
|
|
||
| ```cpp hl_lines="17" | ||
| --8<-- "examples/nlohmann_json_serialize_enum_strict_2.cpp" | ||
| ``` | ||
|
|
||
| Output: | ||
|
|
||
| ```json | ||
| --8<-- "examples/nlohmann_json_serialize_enum_strict_2.output" | ||
| ``` | ||
|
|
||
| ??? example "Example 3: exceptions on invalid serialization" | ||
|
|
||
| The example shows how an invalid serialization causes an exception to be thrown. In the example, | ||
| Color::unknown is not defined in the mapping used to call `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` | ||
| so causes an exception when used to serialize. Similarly, "what" does not refer to an enum | ||
| value so also causes an exception when deserialization is attempted. | ||
|
|
||
| ```cpp hl_lines="14 32 33 43 44 45" | ||
| --8<-- "examples/nlohmann_json_serialize_enum_strict_err.cpp" | ||
| ``` | ||
|
|
||
| Output: | ||
| ```json | ||
| --8<-- "examples/nlohmann_json_serialize_enum_strict_err.output" | ||
| ``` | ||
|
|
||
| ## See also | ||
|
|
||
| - [Specializing enum conversion](../../features/enum_conversion.md) | ||
| - [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md) | ||
| - [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md) | ||
|
|
||
| ## Version history | ||
|
|
||
| Added in version 3.12.0. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #include <iostream> | ||
| #include <nlohmann/json.hpp> | ||
|
|
||
| using json = nlohmann::json; | ||
|
|
||
| namespace ns | ||
| { | ||
| enum TaskState | ||
| { | ||
| TS_STOPPED, | ||
| TS_RUNNING, | ||
| TS_COMPLETED, | ||
| TS_INVALID = -1 | ||
| }; | ||
|
|
||
| NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(TaskState, | ||
| { | ||
| { TS_INVALID, nullptr }, | ||
| { TS_STOPPED, "stopped" }, | ||
| { TS_RUNNING, "running" }, | ||
| { TS_COMPLETED, "completed" } | ||
| }) | ||
|
|
||
| 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 | ||
|
|
||
| int main() | ||
| { | ||
| // serialization | ||
| json j_stopped = ns::TS_STOPPED; | ||
| json j_red = ns::Color::red; | ||
| std::cout << "ns::TS_STOPPED -> " << j_stopped | ||
| << ", ns::Color::red -> " << j_red << std::endl; | ||
|
|
||
| // deserialization | ||
| json j_running = "running"; | ||
| json j_blue = "blue"; | ||
| auto running = j_running.get<ns::TaskState>(); | ||
| auto blue = j_blue.get<ns::Color>(); | ||
| std::cout << j_running << " -> " << running | ||
| << ", " << j_blue << " -> " << static_cast<int>(blue) << std::endl; | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| ns::TS_STOPPED -> "stopped", ns::Color::red -> "red" | ||
| "running" -> 1, "blue" -> 2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| #include <iostream> | ||
| #include <nlohmann/json.hpp> | ||
|
|
||
| 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" }, | ||
| { Color::red, "rot" } // a second conversion for Color::red | ||
| }) | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| // serialization | ||
| json j_red = ns::Color::red; | ||
| std::cout << static_cast<int>(ns::Color::red) << " -> " << j_red << std::endl; | ||
|
|
||
| // deserialization | ||
| json j_rot = "rot"; | ||
| auto rot = j_rot.get<ns::Color>(); | ||
| auto red = j_red.get<ns::Color>(); | ||
| std::cout << j_rot << " -> " << static_cast<int>(rot) << std::endl; | ||
| std::cout << j_red << " -> " << static_cast<int>(red) << std::endl; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| 0 -> "red" | ||
| "rot" -> 0 | ||
| "red" -> 0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| #include <iostream> | ||
| #include <nlohmann/json.hpp> | ||
|
|
||
| using json = nlohmann::json; | ||
|
|
||
| namespace ns | ||
| { | ||
|
|
||
| enum class Color | ||
| { | ||
| red, | ||
| green, | ||
| blue, | ||
| unknown // not mapped in JSON_SERIALIZE_ENUM_STRICT | ||
| }; | ||
|
|
||
| NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color, | ||
| { | ||
| {Color::red, "red"}, | ||
| {Color::green, "green"}, | ||
| {Color::blue, "blue"} | ||
| }) | ||
|
|
||
| } // namespace ns | ||
|
|
||
|
|
||
| int main() | ||
| { | ||
| // invalid serialization | ||
| try | ||
| { | ||
| // ns::color::unknown was not mapped in macro | ||
| json invalid_serialization = ns::Color::unknown; | ||
| } | ||
| catch (const json::exception e) | ||
| { | ||
| std::cout << "deserialization failed: " << e.what() << std::endl; | ||
| } | ||
|
|
||
| // invalid deserialization | ||
| try | ||
| { | ||
| // what does not map to an enum | ||
| json invalid_deserialization("what"); | ||
| ns::Color color = invalid_deserialization.get<ns::Color>(); | ||
| } | ||
| catch (const json::exception e) | ||
| { | ||
| std::cout << "deserialization failed: " << e.what() << std::endl; | ||
| } | ||
|
|
||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| deserialization failed: [json.exception.out_of_range.410] enum value out of range for Color | ||
| deserialization failed: [json.exception.out_of_range.410] enum value out of range for Color: "what" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,6 +253,61 @@ | |
| e = ((it != std::end(m)) ? it : std::begin(m))->first; \ | ||
| } | ||
|
|
||
|
|
||
|
|
||
| /*! | ||
| @brief function to wrap JSON_THROW_MACRO - there can be compilation errors about | ||
| there being no arguments to JSON_THROW that depend on template arguments | ||
| if this is not used to call JSON_THROW | ||
| */ | ||
| template<typename ExceptionType> | ||
| void templated_json_throw(ExceptionType exception) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, two alternative versions of this function from previous "strict enum" PRs: https://github.com/nlohmann/json/pull/4612/changes#diff-ed4d9ac7996b56500f709b366672a844db294ffa884bbbcda8c690e6eb8a7711R245-R260 The first one basically reimplements I'm not judging if any of them are better, just pointing out other versions of this same fix for not being able to use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for bringing these up! I don't think the first would be a good fit as re-implementing https://github.com/nugentcaillin/json/blob/21fe58fa43745a846c6987f772094a6a0a4736c0/include/nlohmann/detail/macro_scope.hpp#L188-L192 // override exception macros
#if defined(JSON_THROW_USER)
#undef JSON_THROW
#define JSON_THROW JSON_THROW_USER
#endifAs for the second, looking at them side by side they seem roughly equivalent with the exception of the previous one being inlined and templated on // current
/*!
@brief function to wrap JSON_THROW_MACRO - NLOHMANN_SERIALIZE_ENUM_STRICT has a
compilation warning about there being no arguments to JSON_THROW that depend on
template arguments otherwise
*/
template<typename ExceptionType>
void templated_json_throw(ExceptionType exception)
{
JSON_THROW(exception);
/* JSON_THROW(exception) discards exception and aborts - void cast needed to supress
compilation error if compiled with -Werror and Wunused-parameter */
(void)exception;
}
// from previous pull
/* helper for strict enum error reporting */
template<typename BasicJsonType>
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));
}I do worry that emulating this would make the change less extensible as hard-coding in the exception would mean that if another strict macro was implemented in the future it would need to implement its own throw function instead of using the current one. I also don't know if it would make much sense to pass json object when throwing error in If that behavior is more desirable I could make a change like this and remove throwing in /*!
@brief function to wrap JSON_THROW_MACRO - NLOHMANN_SERIALIZE_ENUM_STRICT has a
compilation warning about there being no arguments to JSON_THROW that depend on
template arguments otherwise
*/
template<typename BasicJsonType>
void nlohmann_serialize_enum_throw(const BasicJsonType& j, const char *enum_type)
{
JSON_THROW(nlohmann::detail::out_of_range::create(
410,
std::string("invalid value for") + enum_type + ": " + j.dump(),
&j));
}I could also leave the templated function for throwing unchanged and change the call site to have a more descriptive error message in template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, const ENUM_TYPE& e)
{
...
// add stringification of enum type
else templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " + #ENUM_TYPE, nullptr));
}
template<typename BasicJsonType>
inline void from_json(BasicJsonType& j, const ENUM_TYPE& e)
{
...
// add stringification of enum type, json dump and pointer
else templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " + #ENUM_TYPE + ": " + j.dump(), &j));
}
This would take the more descriptive error message from that PR whilst keeping the function available for use for any future strict macros. I've also left the inline out here since it's a templated function and I don't think inline will change how the compiler handles it at all - happy to add it in though. Please advise which of these solutions, if any would be most appropriate. I'll start on the last one for now as that seems more appropriate but happy to switch. |
||
| { | ||
| JSON_THROW(exception); | ||
|
|
||
| /* JSON_THROW(exception) discards exception and aborts - void cast needed to supress | ||
| compilation error if compiled with -Werror and Wunused-parameter */ | ||
| (void)exception; | ||
| } | ||
|
|
||
| /*! | ||
| @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 templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE, 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 templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(410,"enum value out of range for " #ENUM_TYPE ": " + j.dump(), &j)); \ | ||
| } | ||
|
|
||
|
nugentcaillin marked this conversation as resolved.
|
||
| // Ugly macros to avoid uglier copy-paste when specializing basic_json. They | ||
| // may be removed in the future once the class is split. | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.