Skip to content
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

alternative macro for throwing on enum serialization errors #3996

Closed
wants to merge 0 commits into from
Closed

alternative macro for throwing on enum serialization errors #3996

wants to merge 0 commits into from

Conversation

pabloariasal
Copy link

@pabloariasal pabloariasal commented Mar 25, 2023

#3992


Pull request checklist

Read the Contribution Guidelines for detailed information.

  • Changes are described in the pull request, or an existing issue is referenced.
  • The test suite compiles and runs without error.
  • Code coverage is 100%. Test cases can be added by editing the test suite.
  • The source code is amalgamated; that is, after making changes to the sources in the include/nlohmann directory, run make amalgamate to create the single-header files single_include/nlohmann/json.hpp and single_include/nlohmann/json_fwd.hpp. The whole process is described here.

Please don't

  • The C++11 support varies between different compilers and versions. Please note the list of supported compilers. Some compilers like GCC 4.7 (and earlier), Clang 3.3 (and earlier), or Microsoft Visual Studio 13.0 and earlier are known not to work due to missing or incomplete C++11 support. Please refrain from proposing changes that work around these compiler's limitations with #ifdefs or other means.
  • Specifically, I am aware of compilation problems with Microsoft Visual Studio (there even is an issue label for this kind of bug). I understand that even in 2016, complete C++11 support isn't there yet. But please also understand that I do not want to drop features or uglify the code just to make Microsoft's sub-standard compiler happy. The past has shown that there are ways to express the functionality such that the code compiles with the most recent MSVC - unfortunately, this is not the main objective of the project.
  • Please refrain from proposing changes that would break JSON conformance. If you propose a conformant extension of JSON to be supported by the library, please motivate this extension.
  • Please do not open pull requests that address multiple issues.

@@ -26,7 +26,7 @@ enum class Color
red, green, blue, unknown
};

NLOHMANN_JSON_SERIALIZE_ENUM(Color,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a new example instead of editing this one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@pabloariasal pabloariasal changed the title alternative macro for strict enum serialization alternative macro for throwing on enum serialization errors Mar 25, 2023
@github-actions
Copy link

🔴 Amalgamation check failed! 🔴

The source code has not been amalgamated. @pabloariasal
Please read and follow the Contribution Guidelines.

@coveralls
Copy link

coveralls commented Mar 25, 2023

Coverage Status

Coverage: 100.0%. Remained the same when pulling 2c9eda7 on pabloariasal:issue3992 into 6af826d on nlohmann:develop.

@github-actions
Copy link

🔴 Amalgamation check failed! 🔴

The source code has not been amalgamated. @pabloariasal
Please read and follow the Contribution Guidelines.

??? example "Example 3: Strict error handling"

The example shows how to define strict error handling for enum serialization.
In the example conversion between `Color` and json throw an exception in case of erros (instead of defauting to the first value).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: erros instead of errors

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

@@ -27,6 +27,8 @@ 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.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: preffered instead of preferred

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -230,6 +230,46 @@
e = ((it != std::end(m)) ? it : std::begin(m))->first; \
}

#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this only works when JSON_NOEXCEPTION isn't defined, then that should be noted in the docs. However, I think you can just use JSON_THROW instead of throw and use the normal exception override behavior.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, will note it in the docs. I think there are some arguments for not using JSON_NOEXCEPTION:

  • nested macros don't work (the preprocessor doesn't expand a macro inside another one)
  • it can lead to silent and hard to debug erros if JSON_NOEXCEPTION is defined and the macro is used -> what should we do in that case?
  • the macro itself doens't make any sense if JSON_NOEXCEPTION is defined (hence better not define it in the first place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nested macros don't work (the preprocessor doesn't expand a macro inside another one)

It certainly does. There are many nested macros in the library. https://godbolt.org/z/bbWv8Ye7r

it can lead to silent and hard to debug erros if JSON_NOEXCEPTION is defined and the macro is used -> what should we do in that case?

JSON_THROW calls std::abort when JSON_NOEXCEPTION is defined, unless the user has overridden that by defining JSON_THROW_USER.

the macro itself doens't make any sense if JSON_NOEXCEPTION is defined (hence better not define it in the first place.

The library already has well-defined behavior for what happens to throw calls when JSON_NOEXCEPTION is defined.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, very interesting. Thanks for the godbolt link! Now I'm confused, my compiler (gcc 12, fedora 37) doesn't like that:

/home/pablo/workspace/json/tests/src/unit-conversions.cpp:1665:9:   required from here
/home/pablo/workspace/json/include/nlohmann/detail/macro_scope.hpp:249:23: error: ‘JSON_THROW’ was not declared in this scope; did you mean ‘JSON_TRY’?
  249 |             JSON_THROW(nlohmann::detail::type_error::create(302,                                                                   \
      |             ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  250 |                     nlohmann::detail::concat("can't serialize enum value ", std::to_string(static_cast<int>(e)),              \
      |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  251 |                                              " of enum '", #ENUM_TYPE, "' to json. ",                                          \
      |                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  252 |                                              "Did you list the value in NLOHMANN_JSON_SERIALIZE_ENUM_STRICT?"), &j));          \
      |                                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/pablo/workspace/json/tests/src/unit-conversions.cpp:1618:1: note: in expansion of macro ‘NLOHMANN_JSON_SERIALIZE_ENUM_STRICT’
 1618 | NLOHMANN_JSON_SERIALIZE_ENUM_STRICT(game,
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

I wonder what I'm doing wrong

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My first thought was that JSON_THROW wasn't defined yet, but that's not the case. It appears right before that macro, and it's not possible to get there without it being defined. I'm not sure why gcc would choke on that. The only other thing I can think of is that somehow you have something equivalent to #define JSON_THROW JSON_THROW. Maybe #define JSON_THROW_USER JSON_THROW. Can you reproduce this on compiler explorer?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really clueless, would it be possible for you to try the branch out with the proposed changes locally? Looks like my preprocessor doesn't see JSON_THROW and lets it through to the compiler...

if (it == std::end(m)) \
{ \
throw nlohmann::detail::type_error::create(302, \
nlohmann::detail::concat("can't serialize enum value ", std::to_string(static_cast<int>(e)), \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to say something about the value not being a enum value listed in NLOHMANN_JSON_SERIALIZE_ENUM_STRICT?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is a great suggestion.

if (it == std::end(m)) \
{ \
throw nlohmann::detail::type_error::create(302, \
nlohmann::detail::concat("can't deserialize json: '", j, "' to enum '", #ENUM_TYPE "'"), &j); \
Copy link
Contributor

@gregmarr gregmarr Mar 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, and add the string that couldn't be found.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added same hint as above. The string that could not be found is already being printed, it is j

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, missed that.

{
{ Color::green, "green" },
{ Color::blue, "blue" },
{ Color::red, "rot" } // note that serialization for yellow is missing

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this say "red" instead of "rot"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, changed

```

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 de-serialized JSON data may be
undefined or a different enum value than was originally intended.

The `NLOHMANN_JSON_SERIALIZE_ENUM` allows to define a user-defined serialization for every enumerator.
The `NLOHMANN_JSON_SERIALIZE_ENUM` and `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macros allow to define a user-defined serialization for every enumerator.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wording of this sentence reads really weird to me, a possible alternative:

Suggested change
The `NLOHMANN_JSON_SERIALIZE_ENUM` and `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macros allow to define a user-defined serialization for every enumerator.
The `NLOHMANN_JSON_SERIALIZE_ENUM` and `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macros allow specifying a user-defined serialization for every enumerator.

This then matches the language in the following sentence: "first value specified in the macro".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much better, thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants