nlohmann / json

JSON for Modern C++

Home Page:https://json.nlohmann.me

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Derived classes from json class

bradacsa opened this issue · comments

Description

Equal operator won't work with derived classes.

Reproduction steps

class Cookie : public nlohmann::json <- this can handle cookies, chops up cookie strings from headers, etc...
class JsonFile : public nlohmann::json <- this is nothing else but the json class joined up with a fstream to make easier to load and save json from file.

Cookie A1("A1=d=AQABBN3h6GUCECMYqmLClG1pK2uiuaCh0xoFEgABCAEq6mUTZutMb2UBAiAAAAcI3OHoZXSoivI&S=AQAAAqvfHaGFJkLfQ07HNDr2tH0;Expires=Thu, 6 Mar 2025 213629 GMT;"); <- this will create a json object

Pass it to JsonFile
FileHandlers::JsonFile cJsonFile(cookieJson);
cJsonFile["Cookies"]["A1"] = A1; <- this drops error: "terminate called after throwing an instance of 'nlohmann::json_abi_v3_11_2::detail::type_error' what(): [json.exception.type_error.302] type must be string, but is object"

nlohmann::json::parse(A1.dump()) <- this works, but it seems like a not ideal workaroud

Expected vs. actual results

I expect that as an nlohmann::json derived object it should be passed as
nlohmann::json x = { {"one", 1}, {"two", 2} };
nlohmann::json y;
y["thingy"] = x;

Minimal code example

In reproduction steps.

Error messages

"terminate called after throwing an instance of 'nlohmann::json_abi_v3_11_2::detail::type_error' what(): [json.exception.type_error.302] type must be string, but is object"

Compiler and operating system

GCC, Ubuntu

Library version

3.11.2

Validation

Hi can I work on this problem?

Hi can I work on this problem?

Sure.

I'm a beginner-like guy in C++ and I don't want to say something idiotic thing, but when I did some debugging, VSCode shows some differences.
The normal shows a nlohmann baseclass:
nl01
My derived is wrapped in something else:
nl02

Maybe the object id is a filter at inheritance? Sorry if I ask something stupid...

The difference is because you derive from json. The tooltip doesn't show the type of the class itself, just the base classes. The top of the lower one is the actual type of nlohmann::json, which is an alias.

I looked into it but I have a hard time figuring it out. I think its because of some typechecking during runtime.

How did you define the constructor of Class Cookie?

class Cookie : public nlohmann::json
{
      Cookie(const std::string& cookieName, const std::string cookieValue)
      {
          (*this)["name"] = cookieName;
          (*this)["value"] = cookieValue;
      }
      
      Cookie(const std::string& cookieString)
      {
          _chopCString(cookieString);
      }
      ~Cookie()
      {}
};

_chopCString() is private function to chop up HTML header format cookies and create json key-value pairs.
And now I immediately see that I just forgot : nlohmann::json() from the end of the constructors...

Update:
I tried with the Cookie(const std::string& cookieName, const std::string cookieValue) : nlohmann::json() version as well, but the error is the same...
image

I was able to write the following test that passes. I used constructor delegation from the json parent class to construct the object properly.
This is the test I wrote:

//
// Created by arthi on 20-3-2024.
//
#include "doctest_compatibility.h"
#include <nlohmann/json.hpp>
#include <string>
using nlohmann::json;

class Cookie : public json
{
  public:
    Cookie(const std::string& cookieName, const std::string cookieValue ,const std::string jsonValue ) :json(jsonValue)
    {
//        this->operator[]("name") = cookieName;
//        this->operator[]("value") = cookieValue;
    }





};

class JsonFile : public json
{
};

TEST_CASE("Derived classes handling") {
    SECTION("Assign derived object to json") {
        Cookie A1("testCookie", "A1=d=AQABBN3h6GUCECMYqmLClG1pK2uiuaCh0xoFEgABCAEq6mUTZutMb2UBAiAAAAcI3OHoZXSoivI&S=AQAAAqvfHaGFJkLfQ07HNDr2tH0;Expires=Thu, 6 Mar 2025 213629 GMT;",R"({ {"one", 1}, {"two", 2} })");
        JsonFile cJsonFile;
        cJsonFile["Cookies"]["A1"] = A1;
        CHECK(cJsonFile["Cookies"]["A1"].dump() == A1.dump());
    }
}

Still, I have problems with the following lines:

//        (*this)["name"] = cookieName;
//        (*this)["value"] = cookieValue;

Well, maybe the problem stems from my misunderstanding of object inheritance...
So, if I derive my own class from the json class and use the (*this) pointer, I essentially refer to the class itself, including the base class in which I want to create the json structure. This way I can supplement the json class with my own functions.

If I want to create a json object, I could create it like

nlohmann::json js;
js["key"] = "value";

This means that I can create key-value pairs in the inherited class as well, right?

This way it will be like for example:

Cookie cookie("thisisthecookiename", "thisisthecookievalue");
std::cout << "CookieName:" << cookie["name"] << std::endl;
std::cout << "CookieValue:" << cookie["value"] << std::endl;

This will prints out:

CookieName: thisisthecookiename
CookieValue: thisisthecookievalue

I think you are right I have no clue why that is not working

I was thinking that when json takes over the parameters of the derived class, it filters based on some object id, so that either std::string or a json_base class can be passed. I still don't fully understand everything about C++, but it seems like the compiler doesn't like something around std::forward.

I also tried passing it as cJsonFile["Cookies"]["A1"] = static_cast<nlohmann::json>(A1); but that didn't work either, drops the same error.

I think we need some help from someone that has more experience with this codebase.

Can you post a full code example that shows the problem? You can use this as a starting point. https://www.godbolt.org/z/93osKEKEs

Here it goes, Sir:
https://www.godbolt.org/z/6Ezx973eW

I didn't post the full JsonFile class, because it has a few dependencies of my own classes (in the original you can pass trough a filepath and it tries to open it that will be the load/save file for the json object, but basically that's the only difference), but the error is the same here.
The Cookie class works, you can pass a std::cout << A1["value"] << std::endl; before cJsonFile["A1"] = A1; line, it will give back the "d=AQABBN3h6GUCECMYqmLClG1pK2uiuaCh0xoFEgABCAEq6mUTZutMb2UBAiAAAAcI3OHoZXSoivI&S=AQAAAqvfHaGFJkLfQ07HNDr2tH0" value.

There's something weird with the conversion from Cookie to nlohmann::json to store in the cJsonFile. I would recommend that Cookie have a nlohmann::json member rather than deriving from it.

This reproduces the error:

    #define JSON_USE_IMPLICIT_CONVERSIONS 0
    #include <nlohmann/json.hpp>

    Cookie A1("A1=d=AQABBN3h6GUCECMYqmLClG1pK2uiuaCh0xoFEgABCAEq6mUTZutMb2UBAiAAAAcI3OHoZXSoivI&S=AQAAAqvfHaGFJkLfQ07HNDr2tH0;Expires=Thu, 6 Mar 2025 213629 GMT;");
    nlohmann::json cookie = A1;

This change removes the error:

class Cookie 
{
    private:
        nlohmann::json j;
...
            void _chopCString(const std::string& cookieString)
            {
...
                                j["name"] = tempKey;
                                j["name"] = tempKey;
...
                if(tempKey!="") j[tempKey] = tempValue;
            }
...
            operator nlohmann::json const &() { return j; }

Minimal reproduction scenario:

#include <nlohmann/json.hpp>
class derived : public nlohmann::json{};
int main()
{
    nlohmann::json t = derived();
}

So there's apparently something wrong with the constructor selection logic.

There's something weird with the conversion from Cookie to nlohmann::json to store in the cJsonFile. I would recommend that Cookie have a nlohmann::json member rather than deriving from it.

This reproduces the error:

    #define JSON_USE_IMPLICIT_CONVERSIONS 0
    #include <nlohmann/json.hpp>

    Cookie A1("A1=d=AQABBN3h6GUCECMYqmLClG1pK2uiuaCh0xoFEgABCAEq6mUTZutMb2UBAiAAAAcI3OHoZXSoivI&S=AQAAAqvfHaGFJkLfQ07HNDr2tH0;Expires=Thu, 6 Mar 2025 213629 GMT;");
    nlohmann::json cookie = A1;

This change removes the error:

class Cookie 
{
    private:
        nlohmann::json j;
...
            void _chopCString(const std::string& cookieString)
            {
...
                                j["name"] = tempKey;
                                j["name"] = tempKey;
...
                if(tempKey!="") j[tempKey] = tempValue;
            }
...
            operator nlohmann::json const &() { return j; }

Thanks!
I think I will stick to the nlohmann::json::parse(A1.dump()) workaround until this problem will be fixed.

If you don't want to change like that, then you can also do this to clean up the selection.

    cJsonFile["A1"] = static_cast<nlohmann::json const &>(A1);

If you don't want to change like that, then you can also do this to clean up the selection.

    cJsonFile["A1"] = static_cast<nlohmann::json const &>(A1);

Oh, thanks a lot! This is what I'm looking for! So basically it's not a simple nlohmann::json cast, but a const reference... that's why it didn't work for me before.