Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to deserialize array with derived objects #716

Closed
MattiasEppler opened this issue Aug 28, 2017 · 24 comments
Closed

How to deserialize array with derived objects #716

MattiasEppler opened this issue Aug 28, 2017 · 24 comments
Labels
kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated

Comments

@MattiasEppler
Copy link

MattiasEppler commented Aug 28, 2017

Hi

I need to deserialize a array that have derived objects.

{
 "systems":
  [ 
     {"type": "system1", "name": "system_1"  special_param_1 : 1 },
     {"type": "system2", "name": "system_2", special_param_2 : "Value2" },
     {"type": "system3", "name": "system_3", special_param_3 : 3.0, special_param_3_1 : "Value_3"  }
  ]
}

so in my from_json I have to do what? Sorry but I do not realy know how to solve this.
I started with:

void from_json(cons json& j, settings& settings)
{
   settings.systems = j.at("systems").get<vector<systembase>>();
   ??
}

void from_json(cons json& j, systembase& base)
{
  base.type =j.at["type"].get<string>();
  base.name =j.at["name"].get<string>();

  switch (base.type)
  {
   case :
   ...? 
  }
}

void from_json(cons json& j, systemderived_1& derived)
{
 derived = j.at["special_param_1"].get<int>();
}
@nlohmann
Copy link
Owner

I am not sure what you want to do exactly, but this may be helpful:

#include <iostream>
#include <fstream>
#include <array>
#include "json.hpp"

using json = nlohmann::json;

class systembase
{
  public:
    std::string type;
    std::string name;
};

void from_json(const json& j, systembase& sb) {
    sb.type = j.at("type");
    sb.name = j.at("name");
}

class settings {
  public:
    std::vector<systembase> systems;
};

void from_json(const json& j, settings& s) {
    const json& sj = j.at("systems");
    s.systems.resize(sj.size());
    std::copy(sj.begin(), sj.end(), s.systems.begin());
}


int main() {
    std::string text = R"(

    {
        "systems": [
        {
            "type": "system1",
            "name": "system_1",
            "special_param_1": 1
        },
        {
            "type": "system2",
            "name": "system_2",
            "special_param_2": "Value2"
        },
        {
            "type": "system3",
            "name": "system_3",
            "special_param_3": 3.0,
            "special_param_3_1": "Value_3"
        }
        ]
    }
    )";

    json j = json::parse(text);
    settings s = j;
}

@MattiasEppler
Copy link
Author

Than I have only deserilized the systembaseclass with parameter type and name. But I have not deserialized the derived classes with the special parameter

I solved it now in this way

void from_json(const json& j, shared_ptr<systembase>& sb)
{
	// used Better_Enum 
       const auto type = SystemType::_from_string_nocase(j.at("type").get<string>().c_str());
		
	switch (type)
	{
		case SystemType::Derived1:
			sb= j.get<shared_ptr<systemderived_1>>();
			break;

		case SystemType::Derived2:
			sb= j.get<shared_ptr<systemderived_2>>();
			break;
		
		default: ;
	}
	sb->type = type;
	sb->name = j.at("name").get<string>();
}

void from_json(const json& j, shared_ptr<systemderived_1>& sd) 
{
	sd= make_shared<systemderived_1>();
	sd->special_param_1= j.at("special_param_1").get<int>();
}

@nlohmann
Copy link
Owner

Thanks for checking back!

@nlohmann nlohmann added the solution: proposed fix a fix for the issue has been proposed and waits for confirmation label Aug 29, 2017
@MattiasEppler
Copy link
Author

Hi... you closed... ok so you also the opinion thats the best/only solution?

@nlohmann nlohmann reopened this Aug 29, 2017
@nlohmann
Copy link
Owner

I have no real opinion on this. If it works for you, then it's OK. If not, are there issues left?

@theodelrieu
Copy link
Contributor

Looks good to me.

@MattiasEppler
Copy link
Author

I think its not possible to determine the type of derived class automaticaly. So the way with the switch was the only solution for me.

@MattiasEppler
Copy link
Author

MattiasEppler commented Aug 29, 2017

The to_json method for the sake of completeness

void to_json(json& j, const shared_ptr<systembase>& sb)
{		
	switch (sb->type)
	{
		case SystemType::Derived1:
			j = json(dynamic_pointer_cast<systemderived_1>(sb));
			break;
		case SystemType::Derived2:
			j = json(dynamic_pointer_cast<systemderived_2>(sb));
			break;			
		default: ;
	}
	j["type"] = metaData->type._to_string();
	j["name"] = metaData->name;
}

void to_json(json& j, const shared_ptr<systemderived_1>& sd)
{
	j = json
	{
		{ "special_param_1", sd->special_param_1}
	};
}

void to_json(json& j, const shared_ptr<systemderived_2>& sd)
{
	j = json
	{
		{ "special_param_2", sd->special_param_2}
	};
}

@cwreynolds
Copy link

Sorry if I am reopening a can of worms…

I have a similar issue, a base class, and two derived classes. The base class handles the bulk of the serialization, and the derived classes have some unique properties which also need to be serialized. The difference in perspective is that there will be more derived classes down the road, which might include “user defined” classes. Generally it would be messy for the base class to have to know about all the potential derived classes (e.g. in a big switch statement as in MattiasEppler’s to_json example above).

I already have a solution to the basic functionality I need. I define a virtual function on the base class (virtual void serializeToJson(nlohmann::json& j);) and override that in the derived classes. (The overrides calls the base class method then do their specialize stuff.) This is fine, but does not provide the nice syntax that I could get from using the standard to_json() interface.

So I am not stuck, but it would be more convenient if I could use to_json() and treat it as a virtual function I could override. In the meantime I will continue to use my ad hoc interface.

@nlohmann
Copy link
Owner

nlohmann commented Mar 5, 2018

@theodelrieu to the rescue...

@cwreynolds
Copy link

cwreynolds commented Mar 6, 2018

Maybe my desired use case is supported by the existing implementation. Say I have a base class with an inheritable serialization method, lets call it to_json_helper() as well as the standard to_json():

class Fruit
{
    virtual void to_json_helper(json& j)
    {
        j = {{"edible", true}, {“contains_seeds”, true}};
    }
    static void to_json(json& j, const Fruit& f) { f.to_json_helper(j); };
};

Then a derived class would look like:

class Kumquat : public Fruit
{
    void to_json_helper(json& j) override
    {
        Fruit::to_json_helper(j);
        j["vitamin_c"] = true;
    }
    static void to_json(json& j, const Kumquat& k) { k.to_json_helper(j); };
};

However, I guess if I have a generic Fruit reference to an instance of Kumquat, then try to invoke the to_json interface, I will not get the derived version:

j.push_back(fruit); // uses Fruit::to_json() not Kumquat::to_json().

@theodelrieu
Copy link
Contributor

I did think a bit about it, and there's a way to achieve this with CRTP.

Here is a full working example:

#include <iostream>

#include <nlohmann/json.hpp>

using nlohmann::json;

template <typename T>
struct Base
{
  int a{1};
};

template <typename T>
struct Derived : Base<Derived<T>>
{
  int b{2};
};

struct Final : Derived<Final>
{
  int c{3};
};

template <typename T>
void to_json(json& j, Base<T> const& b)
{
  j["value"] = b.a;
  ::nlohmann::to_json(j["derived"], static_cast<T const&>(b));
}

template <typename T>
void to_json(json& j, Derived<T> const& d)
{
  j["value"] = d.b;
  ::nlohmann::to_json(j["final"], static_cast<T const&>(d));
}

void to_json(json& j, Final const& f)
{
  j["value"] = f.c;
}

template <typename T>
void print_json(Base<T> const& val)
{
  json j = val;
  std::cout << j.dump(2) << std::endl;
}

template <typename T>
void print_json(Derived<T> const& val)
{
  print_json(static_cast<Base<Derived<T>> const&>(val));
}

template <typename T>
void print_json(T const& val)
{
  print_json(static_cast<Derived<T> const&>(val));
}


int main(int argc, char const *argv[])
{
  Final f;
  print_json(f);
}

Note that:

Final f;
json j = f; // will only call to_json(json, Final);
j = static_cast<Derived<Final> const&>(f); // to_json(json, Derived<Final>);
j = static_cast<Base<Derived<Final>> const&>(f); //to_json(json, Base<Derived<Final> const&>);

I've hidden those static_cast in the print_json helper functions, I don't think you want your users to perform the casts themselves. Here the print_json will always call the full chain from Base to the lowest level.

The good thing here is that you don't have to rely on virtual functions, users still write to_json methods as usual.

@cwreynolds
Copy link

Thank you @theodelrieu for the suggestion, and for introducing me to the “curiously recurring template pattern” of which I had never heard!

@theodelrieu
Copy link
Contributor

No problem :)

@MattiasEppler
Copy link
Author

I take a look at your example. Whats the difference. You also have to know about the derived class in the base class

You have to know that something is derived from base.

template <typename T>
void to_json(json& j, Base<T> const& b)
{
  j["value"] = b.a;
  ::nlohmann::to_json(j["derived"], static_cast<T const&>(b));
}

This is also working. Without knowing something about the derived class in base to_json method.

struct Base
{
  int a{1};
};

struct Derived : Base
{
  int b{2};
};

inline void to_json(json& j, const Base& data)
{
        j = json
        {
                { "a", data.a }
        };
}

inline void to_json(json& j, const Derived& data)
{
        j = json
        {
                static_cast<Base>(data),
                { "b", data.b }
        };
}

But what about the from_json method:
I don't know any solution without a switch case in the from_json base class method.

@stale
Copy link

stale bot commented Sep 12, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Sep 12, 2019
@stale stale bot closed this as completed Sep 19, 2019
@nhule11
Copy link

nhule11 commented Dec 2, 2019

@nlohmann the example you provided on Aug 28, 2017, Can you tell me how can I access the member of the class systembase?

@nlohmann
Copy link
Owner

nlohmann commented Dec 2, 2019

With code

void from_json(const json& j, systembase& sb) {
    sb.type = j.at("type");
    sb.name = j.at("name");
}

you store a systembase value as an object. So with

systembase s;
s.name = "foo";
s.type = "bar";
json j = s;

j now holds this JSON value:

{"name": "foo", "type": "bar"}

@nhule11
Copy link

nhule11 commented Dec 2, 2019

What if I just have to read the value and not write it? As I'm receiving Json from other source and I just have to fill it in class.

@nlohmann
Copy link
Owner

nlohmann commented Dec 3, 2019

Then you need to implement a to_json function, see https://github.com/nlohmann/json#basic-usage.

@nhule11
Copy link

nhule11 commented Dec 3, 2019

It is a little confusing. But I'll figure it out.
I have one question does it make any difference if I have a nested JSon? Do I have to add adl_serializer or just the to and from json?

@nlohmann
Copy link
Owner

nlohmann commented Dec 3, 2019

Just to_json.

@nhule11
Copy link

nhule11 commented Dec 4, 2019

Can you tell me how can I write to_json if I have a nested json?

{
"MT": "C",
"C": [
{
"ID": 1,
"MD": 0,
}
{
"ID": 2,
"MD": 0,
}
]
}

How can I get the value of Id with value 2?

@nhule11
Copy link

nhule11 commented Dec 4, 2019

It was easy, this is how I did it
Json j = I had my entire json string here
json C = j["C"];
cout << C[1]["ID"]<< endl;
Amazing library, very easy to use and quick assistance than I every got on internet.
@ Nlohmann Thank you so much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated
Projects
None yet
Development

No branches or pull requests

5 participants