Skip to content

Commit 06b667c

Browse files
committed
add strict enum de/serialization macro
Signed-off-by: Harinath Nampally <[email protected]>
1 parent f06604f commit 06b667c

File tree

8 files changed

+395
-1
lines changed

8 files changed

+395
-1
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# NLOHMANN_JSON_SERIALIZE_ENUM_STRICT
2+
3+
```cpp
4+
#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(type, conversion...)
5+
```
6+
7+
The `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` allows to define a user-defined serialization for every enumerator.
8+
9+
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.
10+
11+
12+
## Parameters
13+
14+
`type` (in)
15+
: name of the enum to serialize/deserialize
16+
17+
`conversion` (in)
18+
: a pair of an enumerator and a JSON serialization; arbitrary pairs can be given as a comma-separated list
19+
20+
## Default definition
21+
22+
The macro adds two functions to the namespace which take care of the serialization and deserialization:
23+
24+
```cpp
25+
template<typename BasicJsonType>
26+
inline void to_json(BasicJsonType& j, const type& e);
27+
template<typename BasicJsonType>
28+
inline void from_json(const BasicJsonType& j, type& e);
29+
```
30+
31+
## Notes
32+
33+
!!! info "Prerequisites"
34+
35+
The macro must be used inside the namespace of the enum.
36+
37+
!!! important "Important notes"
38+
39+
- 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.
40+
- 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.
41+
- Unlike `NLOHMANN_JSON_SERIALIZE_ENUM`, this macro enforces strict validation:
42+
- Attempting to serialize an unmapped enum value will throw a `type_error.302` exception
43+
- Attempting to deserialize an unmapped JSON value will throw a `type_error.302` exception
44+
- There is no default value behavior - all values must be explicitly mapped
45+
46+
## Examples
47+
48+
??? example "Example 1: Strict serialization"
49+
50+
The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` enforces strict validation when serializing an enum value that is not in the mapping:
51+
52+
```cpp
53+
--8<-- "examples/nlohmann_json_serialize_enum_strict.cpp"
54+
```
55+
56+
Expected output:
57+
58+
```
59+
[json.exception.type_error.302] can't serialize - enum value 3 out of range
60+
```
61+
62+
??? example "Example 2: Strict deserialization"
63+
64+
The example shows how `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` enforces strict validation when deserializing a JSON value that is not in the mapping:
65+
66+
```cpp
67+
--8<-- "examples/nlohmann_json_deserialize_enum_strict.cpp"
68+
```
69+
70+
Expected output:
71+
72+
```
73+
[json.exception.type_error.302] can't deserialize - invalid json value : "yellow"
74+
```
75+
76+
Both examples demonstrate:
77+
78+
- Proper error handling using try-catch blocks
79+
- Clear error messages indicating the cause of failure
80+
- No default value behavior - all values must be explicitly mapped
81+
- Exception throwing for unmapped values
82+
83+
## See also
84+
85+
- [Specializing enum conversion](../../features/enum_conversion.md)
86+
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
87+
88+
## Version history
89+
90+
- Added in version 3.11.3.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <iostream>
2+
#include <nlohmann/json.hpp>
3+
4+
#ifdef __cpp_exceptions
5+
#undef __cpp_exceptions
6+
#define __cpp_exceptions 1
7+
#endif
8+
9+
#ifdef JSON_NOEXCEPTION
10+
#define JSON_NOEXCEPTION 0
11+
#endif
12+
13+
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
14+
#define JSON_THROW(exception) throw exception
15+
#define JSON_TRY try
16+
#define JSON_CATCH(exception) catch(exception)
17+
#define JSON_INTERNAL_CATCH(exception) catch(exception)
18+
#else
19+
#include <cstdlib>
20+
#define JSON_THROW(exception) std::abort()
21+
#define JSON_TRY if(true)
22+
#define JSON_CATCH(exception) if(false)
23+
#define JSON_INTERNAL_CATCH(exception) if(false)
24+
#endif
25+
26+
using json = nlohmann::json;
27+
28+
namespace ns
29+
{
30+
enum class Color
31+
{
32+
red, green, blue
33+
};
34+
35+
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color,
36+
{
37+
{ Color::red, "red" },
38+
{ Color::green, "green" },
39+
{ Color::blue, "blue" },
40+
})
41+
}
42+
43+
int main()
44+
{
45+
46+
// deserialization
47+
json j_yellow = "yellow";
48+
try
49+
{
50+
auto yellow = j_yellow.template get<ns::Color>();
51+
std::cout << j_yellow << " -> " << static_cast<int>(yellow) << std::endl;
52+
}
53+
catch (const nlohmann::json::exception& e)
54+
{
55+
std::cout << e.what() << std::endl;
56+
}
57+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <iostream>
2+
#include <nlohmann/json.hpp>
3+
4+
#ifdef __cpp_exceptions
5+
#undef __cpp_exceptions
6+
#define __cpp_exceptions 1
7+
#endif
8+
9+
#ifdef JSON_NOEXCEPTION
10+
#define JSON_NOEXCEPTION 0
11+
#endif
12+
13+
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
14+
#define JSON_THROW(exception) throw exception
15+
#define JSON_TRY try
16+
#define JSON_CATCH(exception) catch(exception)
17+
#define JSON_INTERNAL_CATCH(exception) catch(exception)
18+
#else
19+
#include <cstdlib>
20+
#define JSON_THROW(exception) std::abort()
21+
#define JSON_TRY if(true)
22+
#define JSON_CATCH(exception) if(false)
23+
#define JSON_INTERNAL_CATCH(exception) if(false)
24+
#endif
25+
26+
using json = nlohmann::json;
27+
28+
namespace ns
29+
{
30+
enum class Color
31+
{
32+
red, green, blue, pink
33+
};
34+
35+
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(Color,
36+
{
37+
{ Color::red, "red" },
38+
{ Color::green, "green" },
39+
{ Color::blue, "blue" },
40+
})
41+
}
42+
43+
int main()
44+
{
45+
// serialization
46+
try
47+
{
48+
json j_red = ns::Color::pink;
49+
auto color = j_red.get<ns::Color>();
50+
std::cout << static_cast<int>(color) << " -> " << j_red << std::endl;
51+
}
52+
catch (const nlohmann::json::exception& e)
53+
{
54+
std::cout << e.what() << std::endl;
55+
}
56+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[json.exception.type_error.302] can't serialize - enum value 3 out of range

docs/mkdocs/docs/features/enum_conversion.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM( TaskState, {
2727
The [`NLOHMANN_JSON_SERIALIZE_ENUM()` macro](../api/macros/nlohmann_json_serialize_enum.md) declares a set of
2828
`to_json()` / `from_json()` functions for type `TaskState` while avoiding repetition and boilerplate serialization code.
2929
30+
3031
## Usage
3132
3233
```cpp
@@ -59,3 +60,22 @@ Other Important points:
5960
- If an enum or JSON value is specified more than once in your map, the first matching occurrence from the top of the
6061
map will be returned when converting to or from JSON.
6162
- 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).
63+
64+
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.
65+
66+
## Usage
67+
```cpp
68+
// example enum type declaration
69+
enum TaskState {
70+
TS_STOPPED,
71+
TS_RUNNING,
72+
TS_COMPLETED,
73+
};
74+
75+
// map TaskState values to JSON as strings
76+
NLOHMANN_JSON_SERIALIZE_ENUM_STRICT( TaskState, {
77+
{TS_STOPPED, "stopped"},
78+
{TS_RUNNING, "running"},
79+
{TS_COMPLETED, "completed"},
80+
})
81+
```

include/nlohmann/detail/macro_scope.hpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,47 @@
242242
e = ((it != std::end(m)) ? it : std::begin(m))->first; \
243243
}
244244

245+
/*!
246+
@brief macro to briefly define a mapping between an enum and JSON
247+
@def NLOHMANN_JSON_SERIALIZE_ENUM
248+
@since version 3.4.0
249+
*/
250+
#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \
251+
template<typename BasicJsonType> \
252+
inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
253+
{ \
254+
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
255+
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
256+
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
257+
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
258+
auto it = std::find_if(std::begin(m), std::end(m), \
259+
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
260+
{ \
261+
return ej_pair.first == e; \
262+
}); \
263+
if (it == std::end(m)) { \
264+
auto value = static_cast<typename std::underlying_type<ENUM_TYPE>::type>(e); \
265+
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't serialize - enum value ", std::to_string(value), " out of range"), &j)); \
266+
} \
267+
j = it->second; \
268+
} \
269+
template<typename BasicJsonType> \
270+
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
271+
{ \
272+
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
273+
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
274+
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
275+
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
276+
auto it = std::find_if(std::begin(m), std::end(m), \
277+
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
278+
{ \
279+
return ej_pair.second == j; \
280+
}); \
281+
if (it == std::end(m)) \
282+
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't deserialize - invalid json value : ", j.dump()), &j)); \
283+
e = it->first; \
284+
}
285+
245286
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
246287
// may be removed in the future once the class is split.
247288

single_include/nlohmann/json.hpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,47 @@ JSON_HEDLEY_DIAGNOSTIC_POP
26082608
e = ((it != std::end(m)) ? it : std::begin(m))->first; \
26092609
}
26102610

2611+
/*!
2612+
@brief macro to briefly define a mapping between an enum and JSON
2613+
@def NLOHMANN_JSON_SERIALIZE_ENUM
2614+
@since version 3.4.0
2615+
*/
2616+
#define NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(ENUM_TYPE, ...) \
2617+
template<typename BasicJsonType> \
2618+
inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
2619+
{ \
2620+
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
2621+
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
2622+
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
2623+
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
2624+
auto it = std::find_if(std::begin(m), std::end(m), \
2625+
[e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
2626+
{ \
2627+
return ej_pair.first == e; \
2628+
}); \
2629+
if (it == std::end(m)) { \
2630+
auto value = static_cast<typename std::underlying_type<ENUM_TYPE>::type>(e); \
2631+
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't serialize - enum value ", std::to_string(value), " out of range"), &j)); \
2632+
} \
2633+
j = it->second; \
2634+
} \
2635+
template<typename BasicJsonType> \
2636+
inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
2637+
{ \
2638+
/* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \
2639+
static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
2640+
/* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */ \
2641+
static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
2642+
auto it = std::find_if(std::begin(m), std::end(m), \
2643+
[&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
2644+
{ \
2645+
return ej_pair.second == j; \
2646+
}); \
2647+
if (it == std::end(m)) \
2648+
JSON_THROW(nlohmann::detail::type_error::create(302, nlohmann::detail::concat("can't deserialize - invalid json value : ", j.dump()), &j)); \
2649+
e = it->first; \
2650+
}
2651+
26112652
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
26122653
// may be removed in the future once the class is split.
26132654

0 commit comments

Comments
 (0)