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

Allow default values for NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE #2819

Closed
wants to merge 60 commits into from

Conversation

harry75369
Copy link
Contributor

These two macros use json::at for deserializing from json to obj, but it will throw exceptions and is not good for backward-compatibility if the struct adds new fields but the json is still unchanged.

This simple patch uses the default value from struct type (the type should be default constructible), and use json::value to set default value if some fileds is missing in json value. Hope it helps.


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 file single_include/nlohmann/json.hpp. The whole process is described here.

@harry75369
Copy link
Contributor Author

It failed unit test "unit-udt_macro.cpp" as line 256 expects an exception is thrown. No idea if this behavior should be changed.

@nlohmann
Copy link
Owner

This would be a breaking change, hence the tests fail. Best would be to introduce a new macro, though I'm not sure what a good name could be.

@gregmarr
Copy link
Contributor

This change also requires that the destination type be default constructible, which I believe is explicitly not a requirement currently.

…NE_TYPE_NON_INTRUSIVE; added new macros NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
@harry75369
Copy link
Contributor Author

Changes to old macros are reverted and new macros are added witih passed unit tests, which are

  • NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT
  • NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT

Copy link
Owner

@nlohmann nlohmann left a comment

Choose a reason for hiding this comment

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

@coveralls
Copy link

coveralls commented Jun 22, 2021

Coverage Status

Coverage increased (+0.02%) to 100.0% when pulling 321cc51 on harry75369:develop into 18a5f4c on nlohmann:develop.

…MANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT accordingly
@harry75369
Copy link
Contributor Author

Can you please also edit

* https://github.com/nlohmann/json/blob/develop/doc/mkdocs/docs/features/macros.md

* https://github.com/nlohmann/json/blob/develop/doc/mkdocs/docs/features/arbitrary_types.md#simplify-your-life-with-macros

Done.

@@ -291,6 +296,10 @@
inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }

#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking that having default_obj be const static would save constructing and destructing this object on every pass through the function, but there could be reasons that users wouldn't want that. I suppose that this is just something that users will need to be aware of.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making it const static sounds a good idea. Any specific reason for not wanting that?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nothing in general, just could be specific use cases where having a long-lived one of these would be a problem for people.

Copy link
Owner

Choose a reason for hiding this comment

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

Thinking about this, I also think having a long-lived object to be surprising.

Copy link

Choose a reason for hiding this comment

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

nlohmann_json_t is already default constructed. The WITH_DEFAULT macros can just not set it if the key is missing, and then no Type default_obj; is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's not necessarily true. We ran into situations recently where to_json was called on a json object that already had a value, and the functions didn't clear it properly, resulting in a memory leak.

@@ -79,6 +79,10 @@ The first parameter is the name of the class/struct, and all remaining parameter

See [Simplify your life with macros](arbitrary_types.md#simplify-your-life-with-macros) for an example.

## `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)`

This macro is similar to `NLOHMANN_DEFINE_TYPE_INTRUSIVE` but will throw no exceptions. When converting JSON object to type object, if any field is missing in JSON object, it will use default value constructed by the type itself.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking this should call out that it requires that the type be default constructible and that it constructs an object inside the from_json() function. I think it may also still throw exceptions if the types don't match.

Perhaps something like this:

This macro is similar to NLOHMANN_DEFINE_TYPE_INTRUSIVE. It will not throw an exception in from_json() due to a missing value in the JSON, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The from_json() function default constructs an object and uses its values as the defaults when calling the value() function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds better, thanks. Already updated.

…OHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT

In both macros, the first parameter is the name of the class/struct, and all remaining parameters name the members.
In all macros, the first parameter is the name of the class/struct, and all remaining parameters name the members. You can read more docs about them starting from [here](https://github.com/nlohmann/json/blob/develop/doc/mkdocs/docs/features/macros.md#nlohmann_define_type_intrusivetype-member).
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
In all macros, the first parameter is the name of the class/struct, and all remaining parameters name the members. You can read more docs about them starting from [here](https://github.com/nlohmann/json/blob/develop/doc/mkdocs/docs/features/macros.md#nlohmann_define_type_intrusivetype-member).
In all macros, the first parameter is the name of the class/struct, and all remaining parameters name the members. You can read more docs about them starting from [here](macros.md#nlohmann_define_type_intrusivetype-member).

@@ -79,6 +79,10 @@ The first parameter is the name of the class/struct, and all remaining parameter

See [Simplify your life with macros](arbitrary_types.md#simplify-your-life-with-macros) for an example.

## `NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)`

This macro is similar to `NLOHMANN_DEFINE_TYPE_INTRUSIVE`. It will not throw an exception in `from_json()` due to a missing value in the JSON, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The `from_json()` function default constructs an object and uses its values as the defaults when calling the `value()` function.
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
This macro is similar to `NLOHMANN_DEFINE_TYPE_INTRUSIVE`. It will not throw an exception in `from_json()` due to a missing value in the JSON, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The `from_json()` function default constructs an object and uses its values as the defaults when calling the `value()` function.
This macro is similar to `NLOHMANN_DEFINE_TYPE_INTRUSIVE`. It will not throw an exception in `from_json()` due to a missing value in the JSON object, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The `from_json()` function default constructs an object and uses its values as the defaults when calling the `value()` function.

@@ -88,6 +92,10 @@ The first parameter is the name of the class/struct, and all remaining parameter

See [Simplify your life with macros](arbitrary_types.md#simplify-your-life-with-macros) for an example.

## `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(type, member...)`

This macro is similar to `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`. It will not throw an exception in `from_json()` due to a missing value in the JSON, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The `from_json()` function default constructs an object and uses its values as the defaults when calling the `value()` function.
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
This macro is similar to `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`. It will not throw an exception in `from_json()` due to a missing value in the JSON, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The `from_json()` function default constructs an object and uses its values as the defaults when calling the `value()` function.
This macro is similar to `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE`. It will not throw an exception in `from_json()` due to a missing value in the JSON object, but can throw due to a mismatched type. In order to support that it requires that the type be default constructible. The `from_json()` function default constructs an object and uses its values as the defaults when calling the `value()` function.


- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(name, member1, member2, ...)` is to be defined inside of the namespace of the class/struct to create code for.
- `NLOHMANN_DEFINE_TYPE_INTRUSIVE(name, member1, member2, ...)` is to be defined inside of the class/struct to create code for. This macro can also access private members.
- `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(name, member1, member2, ...)` is to be defined inside of the namespace of the class/struct to create code for.
Copy link
Owner

Choose a reason for hiding this comment

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

It would be good to add a comment about the difference to the previous macros.

Furthermore, the order should be.

  • NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
  • NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT
  • NLOHMANN_DEFINE_TYPE_INTRUSIVE
  • NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT

@@ -291,6 +296,10 @@
inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }

#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { Type default_obj; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }
Copy link
Owner

Choose a reason for hiding this comment

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

It might be safer to add nlohmann_json_ as prefix to default_obj to avoid name clashes.

@@ -59,6 +59,42 @@ class person_with_private_data
NLOHMANN_DEFINE_TYPE_INTRUSIVE(person_with_private_data, age, name, metadata)
};

class person_with_private_data_2
Copy link
Owner

Choose a reason for hiding this comment

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

Do you actually test how JSON objects with missing values are serialized?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Test added.

@harry75369
Copy link
Contributor Author

Why is this PR still not merged yet after so many days? All including docs are already editted as sugguested.

@nlohmann
Copy link
Owner

nlohmann commented Jul 9, 2021

Because this is a side project of mine and i did not find the time to check your last changes.

@nlohmann
Copy link
Owner

I messed up the CI lately, I'm sorry. Can you please update to the latest develop branch?

@harry75369
Copy link
Contributor Author

I messed up the CI lately, I'm sorry. Can you please update to the latest develop branch?

Do you mean I make an empty commit to trigger the CI?

@nlohmann
Copy link
Owner

Just update your branch and commit the changes.

@nlohmann nlohmann added the please rebase Please rebase your branch to origin/develop label Jul 22, 2021
@Martinii89
Copy link

I was surprised to find this just as I made my own macro for pretty much the same thing. I'd love for this to get merged in.

Here was my attempt on it

#define NLOHMANN_JSON_FROM_OPTIONAL(v1) if (const auto it = nlohmann_json_j.find(#v1); it != nlohmann_json_j.end()) {it->get_to(nlohmann_json_t.v1);}

#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_OPTIONAL(Type, ...)  \
    inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
    inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_OPTIONAL, __VA_ARGS__)) }

@nlohmann nlohmann removed the please rebase Please rebase your branch to origin/develop label Aug 8, 2021
@nlohmann
Copy link
Owner

nlohmann commented Aug 8, 2021

I'm confused - did you update to the latest develop branch?

@harry75369
Copy link
Contributor Author

I'm confused - did you update to the latest develop branch?

Yes, I pulled latest develop branch, and rebased.

@nlohmann
Copy link
Owner

nlohmann commented Aug 9, 2021

I'm confused - did you update to the latest develop branch?

Yes, I pulled latest develop branch, and rebased.

I am confused, because the diff now shows me changes in 51 files. 🤔

@NoumanNawaz51
Copy link

I still have no idea that if this works or not. But from the README.md that macro does'nt work at all. Or is it in progress ?

@nlohmann
Copy link
Owner

nlohmann commented Sep 2, 2021

I still have no idea that if this works or not. But from the README.md that macro does'nt work at all. Or is it in progress ?

No, this PR is not yet merged.

@nlohmann
Copy link
Owner

nlohmann commented Sep 2, 2021

I'm confused - did you update to the latest develop branch?

Yes, I pulled latest develop branch, and rebased.

I am confused, because the diff now shows me changes in 51 files. 🤔

Hey @harry75369, can you please fix the branch by updating it to the latest develop branch?

@lljbash
Copy link

lljbash commented Nov 9, 2021

Any update on this? I really like the macros for backward compatibility. Please let me know if there's anything I can help with to get it merged.

@stale
Copy link

stale bot commented Jan 9, 2022

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Jan 9, 2022
@AndTessitore
Copy link

Still not merged? Those macros are really usefull!

@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Jan 25, 2022
@nlohmann
Copy link
Owner

Still not merged? Those macros are really usefull!

The PR needs to be rebased. Can you do this, @harry75369 ?

@pketelsen
Copy link
Contributor

Still not merged? Those macros are really usefull!

The PR needs to be rebased. Can you do this, @harry75369 ?

Hi Nils!
I cherry-picked @harry75369 's commits in PR #3143. I don't know the protocol for stalled PRs, I hope that this is ok. I would really like to have this feature as well.

@nlohmann
Copy link
Owner

Can be closed as #3143 is merged. Thanks a lot everybody!

@nlohmann nlohmann closed this Jan 30, 2022
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.