Search code examples
c++jsonnlohmann-json

Error in converting from json using nlohmann json package


I'm trying to convert the json of the form

{
   "content": {
     "test_key": "test"
   },
   "sender": "alice",
   "type": "key_type"
}

to my object which is

template<class Content>
struct Event
{
        Content content;
        std::string type;
};

a template is being used as the structure of the Content is not fixed. When I try using the from_json which is like

template<class Content>
void
from_json(const nlohmann::json &obj, Event<Content> &event)
{
        event.content = obj.at("content").get<Content>();
        event.type    = obj.at("type").get<std::string>();
}

I'm getting the error

[json.exception.out_of_range.403] key 'content' not found

although there is content key in the json. Why is it so?

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;
using namespace std;

template<typename Content>
struct Event
{
    Content content;
    string type;
};

template<typename Content>
void from_json(const nlohmann::json &obj, Event<Content> &event)
{
    event.content = obj.at("content").get<Content>();
    event.type    = obj.at("type").get<string>();
}

struct Key{
    string test_key;
    string random_data;
};

int main(){
    json j={{"content",{{"test_key","test"}}},{"sender","alice"},{"type","key_type"}};

    Event<Key> event_instance;

    try{
        from_json(j,event_instance);
    }
    catch(json::exception& e){
        cout<<e.what()<<endl;
    }
}

The above code is a minimum reproducible example


Solution

  • What's missing is serializer support for your type Key. With that added, extraction works:

    void from_json(const nlohmann::json& obj, Key& k) {
        k.test_key = obj.at("test_key").get<std::string>();
        // k.random_data missing in json
    }
    
    template<typename Content>
    void from_json(const nlohmann::json& obj, Event<Content>& event) {
        event.content = obj.at("content").get<Content>();
        event.type = obj.at("type").get<std::string>();
    }
    

    Demo


    To handle optional fields like random_data in your Key, you could create a helper function, here called get_optional which returns a C++17 std::optional<T>. For earlier C++ versions, you could use boost::optional.

    #include <nlohmann/json.hpp>
    
    #include <iostream>
    #include <optional>
    #include <string>
    
    using json = nlohmann::json;
    
    template<typename Content>
    struct Event {
        Content content{};
        std::string type{};
    };
    
    struct Key {
        std::string test_key{};
        std::optional<std::string> random_data{}; // optional field made optional
    };
    
    template<typename T>
    std::optional<T> get_optional(const json& obj, const std::string& key) try {
        return obj.at(key).get<T>();
    } catch(const json::exception&) {
        return std::nullopt;
    }
    
    void from_json(const json& obj, Key& k) {
        k.test_key = obj.at("test_key").get<std::string>();
        k.random_data = get_optional<std::string>(obj, "random_data");
    }
    
    template<typename Content>
    void from_json(const json& obj, Event<Content>& event) {
        event.content = obj.at("content").get<Content>();
        event.type = obj.at("type").get<std::string>();
    }
    
    int main() {
        json j = {{"content", {{"test_key", "test"}}},
                  {"sender", "alice"},
                  {"type", "key_type"}};
    
        try {
            auto event_instance = j.get<Event<Key>>();
            std::cout << event_instance.content.test_key << '\n';
    
            if(event_instance.content.random_data) {
                std::cout << event_instance.content.random_data.value() << '\n';
            } else {
                std::cout << "no random_data\n";
            }
    
            std::cout << event_instance.type << '\n';
        } catch(const json::exception& e) {
            std::cerr << e.what() << std::endl;
        }
    }
    

    Demo