nlohmann / json

JSON for Modern C++
https://json.nlohmann.me
MIT License
41.3k stars 6.58k forks source link

Derived classes from json class #4316

Open bradacsa opened 3 months ago

bradacsa commented 3 months ago

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

arthikek commented 3 months ago

Hi can I work on this problem?

nlohmann commented 3 months ago

Hi can I work on this problem?

Sure.

bradacsa commented 3 months ago

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...

gregmarr commented 3 months ago

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.

arthikek commented 3 months ago

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

arthikek commented 3 months ago

How did you define the constructor of Class Cookie?

bradacsa commented 3 months ago
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

arthikek commented 3 months ago

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;
bradacsa commented 3 months ago

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
arthikek commented 3 months ago

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

bradacsa commented 3 months ago

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.

arthikek commented 3 months ago

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

gregmarr commented 3 months ago

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

bradacsa commented 3 months ago

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.

gregmarr commented 3 months ago

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; }
gregmarr commented 3 months ago

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.

bradacsa commented 3 months ago

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.

gregmarr commented 3 months ago

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);
bradacsa commented 3 months ago

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 cast, but a const reference... that's why it didn't work for me before.