beached / daw_json_link

Fast, convenient JSON serialization and parsing in C++
https://beached.github.io/daw_json_link/
Boost Software License 1.0
478 stars 31 forks source link

Nullable construction of std::unorded_map #336

Closed beached closed 2 years ago

beached commented 2 years ago

@wxprospero While you're here, is "json_key_value_null" broken as well? Why does changing "json_key_value" to "json_key_value_null" break this, whereas with other types (like json_number or json_string), changing it to nullable requires no extra or change to template parameters? An unordered_map is default constructable, and value initializable, isn't it?

#include <daw/json/daw_json_link.h>
#include <daw/json/daw_json_iterator.h>

#include <iostream>
#include <unordered_map>

struct PersonObject {
    int id{};
    std::string name;
    std::unordered_map<std::string, std::string> fields;
};

namespace daw::json {
    template<>
    struct json_data_contract<PersonObject> {
        static constexpr char const id[] = "id";
        static constexpr char const name[] = "name";
        static constexpr char const fields[] = "fields";
        using type = json_member_list<
            json_number<id, int>,
            json_string<name>,
            json_key_value_null<fields, std::unordered_map<std::string, std::string>, std::string>
        >;
    };
}

int main() {
    std::string raw_json{R"_(
    {
        "id": 10,
        "name": "Jason",
        "fields": {"city": "Chicago", "Title": "Manager"},
        }
    )_"};
    PersonObject person = daw::json::from_json<PersonObject>(raw_json);

    return 0;
}

There is also no documentation for how to use json_key_value_null, that I can find

_Originally posted by @wxprospero in https://github.com/beached/daw_json_link/issues/334#issuecomment-1299461940_

beached commented 2 years ago

@wxprospero This is a design issue, but a quick work around would be https://jsonlink.godbolt.org/z/Wz6dednWb where the nullable type concept is specialized for the type. I have a fix in mind as I think the design is wrong here but I need to investigate more.

beached commented 2 years ago
#include <daw/json/daw_json_iterator.h>
#include <daw/json/daw_json_link.h>

#include <iostream>
#include <unordered_map>

struct PersonObject {
    int id{};
    std::string name;
    std::unordered_map<std::string, std::string> fields;
};

namespace daw::json {
namespace concepts {
template <typename K, typename V, typename... Ts>
struct nullable_value_traits<std::unordered_map<K, V, Ts...>> {
    using value_type = std::unordered_map<K, V, Ts...>;
    using nullable_type = std::unordered_map<K, V, Ts...>;
    static constexpr bool is_nullable = true;

    static constexpr value_type const &read(nullable_type const &val) {
        return *val;
    }

    constexpr nullable_type operator()(construct_nullable_with_value_t,
                                       nullable_type const &opt) const
        noexcept(std::is_nothrow_copy_constructible_v<nullable_type>) {
        return opt;
    }
    constexpr nullable_type operator()(construct_nullable_with_value_t,
                                       nullable_type &&opt) const
        noexcept(std::is_nothrow_move_constructible_v<nullable_type>) {
        return opt;
    }

    template <
        typename... Args,
        std::enable_if_t<nullable_impl::is_nullable_value_type_constructible_v<
                             value_type, Args...>,
                         std::nullptr_t> = nullptr>
    constexpr nullable_type operator()(construct_nullable_with_value_t,
                                       Args &&...args) const
        noexcept(std::is_nothrow_constructible_v<value_type, Args...>) {
        return nullable_type(DAW_FWD(args)...);
    }

    constexpr nullable_type operator()(
        construct_nullable_with_empty_t) const noexcept {
        return nullable_type();
    }

    static constexpr bool has_value(nullable_type const &val) {
        return true;
    }
};
}  // namespace concepts
template <>
struct json_data_contract<PersonObject> {
    static constexpr char const id[] = "id";
    static constexpr char const name[] = "name";
    static constexpr char const fields[] = "fields";
    using type = json_member_list<
        json_number<id, int>, json_string<name>,
        json_key_value_null<
            fields, std::unordered_map<std::string, std::string>, std::string>>;
};
}  // namespace daw::json

int main() {
    std::string raw_json{R"_(
    {
        "id": 10,
        "name": "Jason",
        "fields": {"city": "Chicago", "Title": "Manager"},
        }
    )_"};
    PersonObject person = daw::json::from_json<PersonObject>(raw_json);

    return 0;
}
beached commented 2 years ago

The docs briefly talk to it here. https://github.com/beached/daw_json_link/blob/release/docs/cookbook/nullable_value_concept.md

I want to take this so default constructible things work here but not for the type deduction when using json_link<"name", type_t> like mappings or eleswhere(arrays)

wxprospero commented 2 years ago

Thanks

beached commented 2 years ago

Thanks for reporting this, I believe the patch fixes it. Was able to find the issue and remediate.