Skip to content

[C++17] Allow std::optional to convert to nlohmann::json #1749

Closed

Description

@Treeston

This is what I assume to be a fairly minor change to the library's logic, but would represent a large improvement in usability.

Feature request

C++17 adds std::optional<T>. Let std::optional<T> convert to nlohmann::json.

If std::optional<T> is empty, I would expect this to result in null - if std::optional<T>, I would expect it to result in the contained value converted.

Use case example

The change would simplify (especially nested) uses of std::optional.

For example, imagine a simple key-value map, where each integer key can optionally have a localized string representation for each of 8 locales. The client dynamically selects the "best" available locale depending on user preferences.

In server code, one could express this as a std::vector<std::array<std::optional<std::string>, 8>>.
Currently, this type does not convert to nlohmann::json because std::optional<std::string> does not - resulting in needing to allocate a bunch of temporaries so the inner conversions can be done "manually".

Allowing std::optional to convert to nlohmann::json would simplify this logic greatly.

Activity

  1. jaredgrubb commented on Sep 13, 2019

    @jaredgrubb
    Contributor

    It's a neat idea, though this library is still intended as a C++11 library. I wonder at what point that could change to allow things like this?

  2. nlohmann commented on Sep 16, 2019

    @nlohmann
    Owner

    The library defines a macro JSON_HAS_CPP_17 to detect C++17. With that, the feature could be realized. The required code should look similar to #760.

  3. stale commented on Oct 16, 2019

    @stale

    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.

  4. added
    state: stalethe issue has not been updated in a while and will be closed automatically soon unless it is updated
    on Oct 16, 2019
  5. jnhyatt commented on Oct 20, 2019

    @jnhyatt

    In addition, I believe the library would benefit from the ability to return a std::optional from a getter method -- this way, client programs could avoid having to catch exceptions to detect a missing value, etc.:

    std::string name = j["name"].get_optional<std::string>().value_or("default-name");

    Qt uses something similar in their config file interface. This makes it very easy to save a default json file on a load failure -- you deserialize a class from json with default values using std::optional and then serialize the resulting class back to json; if there were no changes to the class, the json should be identical, otherwise missing data will be added to the output json.

  6. removed
    state: stalethe issue has not been updated in a while and will be closed automatically soon unless it is updated
    on Oct 20, 2019
  7. nlohmann commented on Oct 23, 2019

    @nlohmann
    Owner

    Wouldn't this be similar to j.value("name", "default-name")?

  8. jnhyatt commented on Oct 23, 2019

    @jnhyatt

    Yes, it would be very similar in most cases. I admit I hadn't searched the library exhaustively for such a feature before having made this suggestion.

  9. djcb commented on Nov 20, 2019

    @djcb

    It's often useful to differentiate about not getting the value, and getting some default value. Also, perhaps the item is there but it's not of the expected type.

    I'm dealing with such cases all the time, and using something like:

    template<typename T>
    std::optional<T> get_at_optional(const nlohmann::json& obj, const std::string& key) try {
            return obj.at(key).get<T>();
    } catch (...) {
            return std::nullopt;
    }
    

    Without it, I need to add type-checks and/or try/catch blocks everywhere, which isn't very nice.

  10. self-assigned this
    on Nov 22, 2019
  11. nlohmann commented on Nov 23, 2019

    @nlohmann
    Owner

    I started working on this in a feature branch https://github.com/nlohmann/json/tree/feature/optional. Unfortunately, I currently cannot make the code compile with MSVC: https://ci.appveyor.com/project/nlohmann/json/builds/29069989

    Any ideas?

  12. remedi commented on Dec 17, 2019

    @remedi
    Contributor

    I looked at AppVeyor and noticed that the builds fail only when the latest language standard is being used in Visual Studio. Essentially the builds fail when std::optional should be available.

    I checked out your branch, tested on local Visual Studio 2017 and found out the issue. The preprocessor definition JSON_HAS_CPP_17 is being used too early before the language standard detection is done. The "error C2039: 'optional': is not a member of 'std'" is caused by the later code in compilation using std::optional while the #include statement is skipped because JSON_HAS_CPP_17 has not been defined yet.

    However, now there is another error in unit-conversions.cpp lines 1624 and 1633 where there is no suitable conversion from nlohmann::json to std::vector containing std::optional (1624) or std::map containing std::optional (1633). I am not familiar with the code base so I am unsure where exactly the conversion should happen.

  13. 27 remaining items

  14. vid512 commented on Aug 30, 2024

    @vid512

    Thank you! Will this change be incorporated into nlohmann-json? As a new user, this is the behavior I expected from NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE (after defining to/from_json for optional, as described in the readme).

  15. vid512 commented on Sep 7, 2024

    @vid512

    I also would change to_json, to not write optional values if they are nullopt.

    template<typename T>
    void nlohmann_dump_val(nlohmann::json& j, std::string_view name, const std::optional<T>& val) {
        if (val.has_value()) {
            j[name] = val.value();
        }
    }
    
    template<typename T>
    void nlohmann_dump_val(nlohmann::json& j, std::string_view name, T& val) {
        j[name] = val;
    }
    
    #define NLOHMANN_JSON_TO_WITH_CONTAINS(v1) \
        nlohmann_dump_val(nlohmann_json_j, #v1, nlohmann_json_t.v1);
    

    (My template-fu is very weak, so I had to use overloading to implement the different logic for std::optionals. Not sure if this is always correct. Worked for me so far.)

    Example of concrete use case where this behavior is useful: Many optional values in https://microsoft.github.io/debug-adapter-protocol//specification.html

  16. Xtreme-G commented on Oct 22, 2024

    @Xtreme-G
    template<typename T>
    void nlohmann_dump_val(nlohmann::json& j, std::string_view name, const std::optional<T>& val) {
        if (val.has_value()) {
            j[name] = val.value();
        }
    }
    
    template<typename T>
    void nlohmann_dump_val(nlohmann::json& j, std::string_view name, T& val) {
        j[name] = val;
    }
    
    #define NLOHMANN_JSON_TO_WITH_CONTAINS(v1) \
        nlohmann_dump_val(nlohmann_json_j, #v1, nlohmann_json_t.v1);
    

    I would like to enable this behavior as well, could you please clarify how and where you added this?

  17. added
    solution: proposed fixa fix for the issue has been proposed and waits for confirmation
    and removed
    state: stalethe issue has not been updated in a while and will be closed automatically soon unless it is updated
    on Nov 16, 2024
  18. added this to the Release 3.11.4 milestone on Nov 16, 2024
  19. Ericson2314 commented on Nov 25, 2024

    @Ericson2314

    In https://github.com/NixOS/nix/blob/3180c09723c3b61d682732c308c74e2087ac628f/src/libutil/json-utils.hh#L43-L124 I made a little trait class to avoid issues where null being used in the underlying serialization would break round-trip parsing. Do we have anything like that in the merged PR? Should we have anything like that?

  20. nlohmann commented on Nov 25, 2024

    @nlohmann
    Owner

    In NixOS/nix@3180c09/src/libutil/json-utils.hh#L43-L124 I made a little trait class to avoid issues where null being used in the underlying serialization would break round-trip parsing. Do we have anything like that in the merged PR? Should we have anything like that?

    I think so - especially before the current develop version is actually released and a later change would be a breaking change.

  21. lingxd commented on Oct 27, 2025

    @lingxd

    to_json -> null
    from_json -> std::nullopt

    I want to_json not to serialize null.

    example:

    NLOHMANN_JSON_NAMESPACE_BEGIN
    
    template<typename T>
    struct adl_serializer<std::optional<T> > {
        static void to_json(json &j, const std::optional<T> &opt) {
            if (not opt.has_value()) {
                // j = nullptr;
            }
            else {
                j = *opt;// this will call adl_serializer<T>::to_json which will
                         // find the free function to_json in T's namespace!
            }
        }
    
        static void from_json(const json &j, std::optional<T> &opt) {
            if (j.is_null()) {
                opt = std::nullopt;
            }
            else {
                opt = j.get<T>();// same as above, but with
                                 // adl_serializer<T>::from_json
            }
        }
    
    };
    
    NLOHMANN_JSON_NAMESPACE_END
    
    struct Properties {
                    std::string controlType;
                    std::optional<std::string> executionTime;
                    uint32_t timestamp;
                }
    NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Properties, controlType, executionTime,
                                                            timestamp)

    i want

     {
            "controlType": "NormalLoad",
            "timestamp": 1761566190
     }

    In fact

     {
            "controlType": "NormalLoad",
            "executionTime" : null,
            "timestamp": 1761566190
     }
  22. gregmarr commented on Oct 27, 2025

    @gregmarr
    Contributor

    This is not really a function of to_json for std::optional<T>. It's either for json.dump, meaning don't write out null values, or to_json for Properties, meaning don't put an entry in the json object for the optional if it's not engaged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      ⌂ 换网址