add strict enum de/serialization macro
Signed-off-by: Harinath Nampally <harinath922@gmail.com>
This commit is contained in:
parent
f06604fce0
commit
06b667cbd5
@ -0,0 +1,90 @@
|
||||
# NLOHMANN_JSON_SERIALIZE_ENUM_STRICT
|
||||
|
||||
```cpp
|
||||
#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(type, conversion...)
|
||||
```
|
||||
|
||||
The `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` allows to define a user-defined serialization for every enumerator.
|
||||
|
||||
This macro declares strict serialization and deserialization functions (`to_json` and `from_json`) for an enum type. Unlike [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md), this macro enforces strict validation and throws errors for unmapped values instead of defaulting to the first enum value.
|
||||
|
||||
|
||||
## 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"
|
||||
|
||||
- If an enum value appears more than once in the mapping, only the first occurrence will be used for serialization, subsequent mappings for the same enum value will be ignored.
|
||||
- If a JSON value appears more than once in the mapping, only the first occurrence will be used for deserialization, subsequent mappings for the same JSON value will be ignored.
|
||||
- Unlike `NLOHMANN_JSON_SERIALIZE_ENUM`, this macro enforces strict validation:
|
||||
- Attempting to serialize an unmapped enum value will throw a `type_error.302` exception
|
||||
- Attempting to deserialize an unmapped JSON value will throw a `type_error.302` exception
|
||||
- There is no default value behavior - all values must be explicitly mapped
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example 1: Strict serialization"
|
||||
|
||||
The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` enforces strict validation when serializing an enum value that is not in the mapping:
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/nlohmann_json_serialize_enum_strict.cpp"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
[json.exception.type_error.302] can't serialize - enum value 3 out of range
|
||||
```
|
||||
|
||||
??? example "Example 2: Strict deserialization"
|
||||
|
||||
The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` enforces strict validation when deserializing a JSON value that is not in the mapping:
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/nlohmann_json_deserialize_enum_strict.cpp"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
[json.exception.type_error.302] can't deserialize - invalid json value : "yellow"
|
||||
```
|
||||
|
||||
Both examples demonstrate:
|
||||
|
||||
- Proper error handling using try-catch blocks
|
||||
- Clear error messages indicating the cause of failure
|
||||
- No default value behavior - all values must be explicitly mapped
|
||||
- Exception throwing for unmapped values
|
||||
|
||||
## See also
|
||||
|
||||
- [Specializing enum conversion](../../features/enum_conversion.md)
|
||||
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.11.3.
|
||||
@ -0,0 +1,57 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
#undef __cpp_exceptions
|
||||
#define __cpp_exceptions 1
|
||||
#endif
|
||||
|
||||
#ifdef JSON_NOEXCEPTION
|
||||
#define JSON_NOEXCEPTION 0
|
||||
#endif
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
#define JSON_THROW(exception) throw exception
|
||||
#define JSON_TRY try
|
||||
#define JSON_CATCH(exception) catch(exception)
|
||||
#define JSON_INTERNAL_CATCH(exception) catch(exception)
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#define JSON_THROW(exception) std::abort()
|
||||
#define JSON_TRY if(true)
|
||||
#define JSON_CATCH(exception) if(false)
|
||||
#define JSON_INTERNAL_CATCH(exception) if(false)
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ns
|
||||
{
|
||||
enum class Color
|
||||
{
|
||||
red, green, blue
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color,
|
||||
{
|
||||
{ Color::red, "red" },
|
||||
{ Color::green, "green" },
|
||||
{ Color::blue, "blue" },
|
||||
})
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
// deserialization
|
||||
json j_yellow = "yellow";
|
||||
try
|
||||
{
|
||||
auto yellow = j_yellow.template get<ns::Color>();
|
||||
std::cout << j_yellow << " -> " << static_cast<int>(yellow) << std::endl;
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
#undef __cpp_exceptions
|
||||
#define __cpp_exceptions 1
|
||||
#endif
|
||||
|
||||
#ifdef JSON_NOEXCEPTION
|
||||
#define JSON_NOEXCEPTION 0
|
||||
#endif
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
#define JSON_THROW(exception) throw exception
|
||||
#define JSON_TRY try
|
||||
#define JSON_CATCH(exception) catch(exception)
|
||||
#define JSON_INTERNAL_CATCH(exception) catch(exception)
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#define JSON_THROW(exception) std::abort()
|
||||
#define JSON_TRY if(true)
|
||||
#define JSON_CATCH(exception) if(false)
|
||||
#define JSON_INTERNAL_CATCH(exception) if(false)
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace ns
|
||||
{
|
||||
enum class Color
|
||||
{
|
||||
red, green, blue, pink
|
||||
};
|
||||
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color,
|
||||
{
|
||||
{ Color::red, "red" },
|
||||
{ Color::green, "green" },
|
||||
{ Color::blue, "blue" },
|
||||
})
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// serialization
|
||||
try
|
||||
{
|
||||
json j_red = ns::Color::pink;
|
||||
auto color = j_red.get<ns::Color>();
|
||||
std::cout << static_cast<int>(color) << " -> " << j_red << std::endl;
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
[json.exception.type_error.302] can't serialize - enum value 3 out of range
|
||||
@ -27,6 +27,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM( TaskState, {
|
||||
The [`NLOHMANN_JSON_SERIALIZE_ENUM()` macro](../api/macros/nlohmann_json_serialize_enum.md) declares a set of
|
||||
`to_json()` / `from_json()` functions for type `TaskState` while avoiding repetition and boilerplate serialization code.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```cpp
|
||||
@ -59,3 +60,22 @@ Other Important points:
|
||||
- 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).
|
||||
|
||||
An alternative macro [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT()` macro](../api/macros/nlohmann_json_serialize_enum.md) can be used when a more strict error handling is preffered, throwing in case of serialization errors instead of defaulting to the first enum value defined in the macro.
|
||||
|
||||
## Usage
|
||||
```cpp
|
||||
// example enum type declaration
|
||||
enum TaskState {
|
||||
TS_STOPPED,
|
||||
TS_RUNNING,
|
||||
TS_COMPLETED,
|
||||
};
|
||||
|
||||
// map TaskState values to JSON as strings
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT( TaskState, {
|
||||
{TS_STOPPED, "stopped"},
|
||||
{TS_RUNNING, "running"},
|
||||
{TS_COMPLETED, "completed"},
|
||||
})
|
||||
```
|
||||
@ -242,6 +242,47 @@
|
||||
e = ((it != std::end(m)) ? it : std::begin(m))->first; \
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief macro to briefly define a mapping between an enum and JSON
|
||||
@def NLOHMANN_JSON_SERIALIZE_ENUM
|
||||
@since version 3.4.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)) { \
|
||||
auto value = static_cast<typename std::underlying_type<ENUM_TYPE>::type>(e); \
|
||||
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't serialize - enum value ", std::to_string(value), " out of range"), &j)); \
|
||||
} \
|
||||
j = it->second; \
|
||||
} \
|
||||
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)) \
|
||||
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't deserialize - invalid json value : ", j.dump()), &j)); \
|
||||
e = it->first; \
|
||||
}
|
||||
|
||||
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
||||
// may be removed in the future once the class is split.
|
||||
|
||||
|
||||
@ -2608,6 +2608,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
|
||||
@def NLOHMANN_JSON_SERIALIZE_ENUM
|
||||
@since version 3.4.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)) { \
|
||||
auto value = static_cast<typename std::underlying_type<ENUM_TYPE>::type>(e); \
|
||||
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't serialize - enum value ", std::to_string(value), " out of range"), &j)); \
|
||||
} \
|
||||
j = it->second; \
|
||||
} \
|
||||
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)) \
|
||||
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't deserialize - invalid json value : ", j.dump()), &j)); \
|
||||
e = it->first; \
|
||||
}
|
||||
|
||||
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
||||
// may be removed in the future once the class is split.
|
||||
|
||||
|
||||
@ -1657,6 +1657,94 @@ TEST_CASE("JSON to enum mapping")
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cpp_exceptions
|
||||
#undef __cpp_exceptions
|
||||
#define __cpp_exceptions 1
|
||||
#endif
|
||||
|
||||
#ifdef JSON_NOEXCEPTION
|
||||
#define JSON_NOEXCEPTION 0
|
||||
#endif
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
#define JSON_THROW(exception) throw exception
|
||||
#define JSON_TRY try
|
||||
#define JSON_CATCH(exception) catch(exception)
|
||||
#define JSON_INTERNAL_CATCH(exception) catch(exception)
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#define JSON_THROW(exception) std::abort()
|
||||
#define JSON_TRY if(true)
|
||||
#define JSON_CATCH(exception) if(false)
|
||||
#define JSON_INTERNAL_CATCH(exception) if(false)
|
||||
#endif
|
||||
|
||||
enum class cards_strict {kreuz, pik, herz, karo};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(cards_strict,
|
||||
{
|
||||
{cards_strict::kreuz, "kreuz"},
|
||||
{cards_strict::pik, "pik"},
|
||||
{cards_strict::pik, "puk"}, // second entry for cards::puk; will not be used
|
||||
{cards_strict::herz, "herz"},
|
||||
{cards_strict::karo, "karo"}
|
||||
})
|
||||
|
||||
enum TaskStateStrict // NOLINT(cert-int09-c,readability-enum-initial-value)
|
||||
{
|
||||
TSS_STOPPED,
|
||||
TSS_RUNNING,
|
||||
TSS_COMPLETED,
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) - false positive
|
||||
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(TaskStateStrict,
|
||||
{
|
||||
{TSS_STOPPED, "stopped"},
|
||||
{TSS_RUNNING, "running"},
|
||||
{TSS_COMPLETED, "completed"},
|
||||
})
|
||||
|
||||
TEST_CASE("JSON to enum mapping")
|
||||
{
|
||||
SECTION("enum class")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(cards_strict::kreuz) == "kreuz");
|
||||
CHECK(json(cards_strict::pik) == "pik");
|
||||
CHECK(json(cards_strict::herz) == "herz");
|
||||
CHECK(json(cards_strict::karo) == "karo");
|
||||
|
||||
// json -> enum
|
||||
CHECK(cards_strict::kreuz == json("kreuz"));
|
||||
CHECK(cards_strict::pik == json("pik"));
|
||||
CHECK(cards_strict::herz == json("herz"));
|
||||
CHECK(cards_strict::karo == json("karo"));
|
||||
|
||||
// invalid json
|
||||
const json j = "foo";
|
||||
CHECK_THROWS_WITH_AS(j.template get<cards_strict>(), "[json.exception.type_error.302] can't deserialize - invalid json value : \"foo\"", json::type_error);
|
||||
}
|
||||
|
||||
SECTION("traditional enum")
|
||||
{
|
||||
// enum -> json
|
||||
CHECK(json(TSS_STOPPED) == "stopped");
|
||||
CHECK(json(TSS_RUNNING) == "running");
|
||||
CHECK(json(TSS_COMPLETED) == "completed");
|
||||
|
||||
// json -> enum
|
||||
CHECK(TSS_STOPPED == json("stopped"));
|
||||
CHECK(TSS_RUNNING == json("running"));
|
||||
CHECK(TSS_COMPLETED == json("completed"));
|
||||
|
||||
// invalid json
|
||||
const json j = "foo";
|
||||
CHECK_THROWS_WITH_AS(j.template get<TaskStateStrict>(), "[json.exception.type_error.302] can't deserialize - invalid json value : \"foo\"", json::type_error);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef JSON_HAS_CPP_17
|
||||
#ifndef JSON_USE_IMPLICIT_CONVERSIONS
|
||||
TEST_CASE("std::optional")
|
||||
@ -1725,4 +1813,4 @@ TEST_CASE("std::optional")
|
||||
#ifdef JSON_HAS_CPP_14
|
||||
#undef JSON_HAS_CPP_14
|
||||
#endif
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
Loading…
Reference in New Issue
Block a user