Search code examples
c++jsoncereal

Why am I getting "rapidjson internal assertion failure: IsArray()" error?


So I wanted to write the code for my mini game and need to read a json file for configuration of player. For this I used cereal library.

My code gave me an error, so I reduced it to simpler example but still got an error.

My code:

#include <cereal/types/map.hpp>
#include <cereal/types/string.hpp>
#include <cereal/archives/json.hpp>
#include <string>
#include <map>
#include <iostream>
#include <fstream>


struct Item {
    std::string name;
    int number;

    template <class Archive>
    void serialize(Archive &ar) {
        ar(cereal::make_nvp("name", name), cereal::make_nvp("number", number));
    }
};

int main() {
    std::map<std::string, Item> items;


    std::ifstream file("dataForTesting.json");
    if (!file.is_open()) {
        std::cerr << "Could not open the file!" << std::endl;
        return 1;
    }

    try {
        cereal::JSONInputArchive archive(file);

        archive(items);

    } catch (const cereal::Exception& e) {
        std::cerr << "Error deserializing JSON: " << e.what() << std::endl;
        return 1;
    } catch (const std::exception& e) {
        std::cerr << "An error occurred: " << e.what() << std::endl;
        return 1;
    }


    for (const auto& item : items) {
        std::cout << "Item ID: " << item.first 
                  << "\nName: " << item.second.name 
                  << "\nNumber: " << item.second.number << "\n" << std::endl;
    }
    return 0;
}

My json file:

{
    "item1": {
        "name": "Item One",
        "number": 1
    },
    "item2": {
        "name": "Item Two",
        "number": 2
    },
    "item3": {
        "name": "Item Three",
        "number": 3
    }
}

I compiled via $ c++ testingTesting.c++ -o test -std=c++11 -I. and executed via $ ./test and this is an error i got Error deserializing JSON: rapidjson internal assertion failure: IsArray()


Solution

  • Basically you should check documentation of cereal! It explains how std::map is archived:

    cereal Docs - Archive Specialization

    Output using cereal built in support:

    {
      "filter": [
        { "key": "type", "value": "sensor" },
        { "key": "status", "value": "critical" }
      ]
    }
    

    Note it archives data by explicitly stating "key" and "value". Rationale is that key for std::map do not have to be a std::string it can be anything and it should be serialized too. Note that in JSon key should be a string.

    So by using default built in support for std::map you have to adjust your JSon:

    {
        "values": [
            {
                "key": "item1",
                "value": {
                    "name": "Item One",
                    "number": 1
                }
            },
            {
                "key": "item2",
                "value": {
                    "name": "Item Two",
                    "number": 2
                }
            },
            {
                "key": "item3",
                "value": {
                    "name": "Item Three",
                    "number": 3
                }
            }
        ]
    }
    

    Then it works.

    Alternatively documentation provides an example how to alter this behavior. See section "Specializing the type".

    Here is my attempt to use this appraoch:

    #include <cereal/archives/json.hpp>
    #include <cereal/types/map.hpp>
    #include <cereal/types/string.hpp>
    #include <fstream>
    #include <iostream>
    #include <map>
    #include <string>
    
    struct Item {
        std::string name;
        int number;
    
        template <class Archive>
        void serialize(Archive& ar)
        {
            ar(cereal::make_nvp("name", name), cereal::make_nvp("number", number));
        }
    };
    
    //! Loading for std::map<std::string, std::string> for text based archives
    template <class Archive, class C, class A,
        cereal::traits::EnableIf<cereal::traits::is_text_archive<Archive>::value> = cereal::traits::sfinae>
    inline void cereal::load(Archive& ar, std::map<std::string, Item, C, A>& map)
    {
        map.clear();
    
        auto hint = map.begin();
        while (true) {
            const auto namePtr = ar.getNodeName();
            if (!namePtr)
                break;
            std::string key = namePtr;
            Item value;
            ar(value);
            hint = map.emplace_hint(hint, std::move(key), std::move(value));
        }
    }
    
    int main()
    try {
        std::map<std::string, Item> items;
    
        cereal::JSONInputArchive archive(std::cin);
        archive(cereal::make_nvp("values", items));
    
        for (const auto& item : items) {
            std::cout << "Item ID: " << item.first
                      << "\nName: " << item.second.name
                      << "\nNumber: " << item.second.number << "\n"
                      << std::endl;
        }
        return 0;
    } catch (const cereal::Exception& e) {
        std::cerr << "Error deserializing JSON: " << e.what() << std::endl;
        return 1;
    } catch (const std::exception& e) {
        std::cerr << "An error occurred: " << e.what() << std::endl;
        return 1;
    }
    

    There is still extra level in JSon, but tweaking that I'm leaving for you.