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

questions regarding from_json #1226

Closed
tamarlev opened this issue Sep 5, 2018 · 19 comments
Closed

questions regarding from_json #1226

tamarlev opened this issue Sep 5, 2018 · 19 comments
Labels
kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation

Comments

@tamarlev
Copy link

tamarlev commented Sep 5, 2018

I have a problem where if I try to convert json to a class, and the class have std::optional members I need to always check if the keys exists in the json and only if they exists (in json) then I can capture their data, something like:
m_id = (j.find("ID") != j.end() && j.at("ID") != nullptr)? j.at("ID").getstd::string() : nullopt;
how can I avoid it?
is there a way to change 'get' j.at("ID").getstd::string() and return either nullopt and avoid asking if 'ID' exists and not nullptr?

@theodelrieu
Copy link
Contributor

There is an example with boost::optional at the end of this doc section.

@nlohmann nlohmann added kind: question solution: proposed fix a fix for the issue has been proposed and waits for confirmation labels Sep 5, 2018
@tamarlev
Copy link
Author

tamarlev commented Sep 5, 2018

Hi theodelrieu,

Thanks for the quick answer but the example does not work for me and Im trying to figure it out

I have a class Person that contains optional members something like:

class Pesron {
std::string id;
std::string name;
std::optional<std::string> eye_color; 
}
static void from_json(const json& j, Person& r) {
r.id = j.at("ID").get<std::string>();
r.name = j.at("Name").get<std::string>();
r.eye_color =  j.at("Eye_Color").get<std::optional<std::string>>(); // gives me error compilation
}
namespace nlohmann {
	template <typename T>
	struct adl_serializer<std::optional<T>> {
		static void to_json(json& j, const std::optional<T>& opt) {
			if (!opt) {
				j = nullptr;
			}
			else {
				j = *opt; // this will call adl_serializer<T>::to_json which will
						  // find the free function to_json in T's namespace!
			}
		}

		static void from_json(const json& j, std::optional<T>& opt) {
			if (j.is_null()) {
				opt.reset();
			}
			else {
				opt = j.get<T>(); // same as above, but with
								  // adl_serializer<T>::from_json
			}
		}
	};
}

what am I doing wrong here?
why its not compiling?

@nlohmann
Copy link
Owner

nlohmann commented Sep 5, 2018

What is your error message? Can you provide a minimal program that produces the error?

@nlohmann nlohmann added this to the Release 3.2.1 milestone Sep 6, 2018
@nlohmann nlohmann self-assigned this Sep 6, 2018
@theodelrieu
Copy link
Contributor

My guess would be that you did not put the adl_serializer partial specialization before the from_json function.

I would advise you to put every adl_serializer specialization in a specific header file (e.g json_custom.hpp), then include it instead of nlohmann/json.hpp.

That way your specializations will be available in every translation unit.

@tamarlev
Copy link
Author

tamarlev commented Sep 6, 2018

Hi Im sorry my mistake,
its not a compilation error its a run time error,
because if in my example "Eye_Color" is not a field in json how can I make sure that .at() wouldnt jump with Exception that it is not exists and I can return a value through 'get' that is not initialized (optional value)
The bottom line is there a way to avoid checking if a field exists in json like in my example
j.at("Eye_Color") and not to do :
j.find("Eye_Color") != j.end() && j.at("Eye_Color") != nullptr) ? std::optionalstd::string(j.at("Eye_Color").getstd::string()) : std::nullopt ?

@theodelrieu
Copy link
Contributor

If you copy-pasted the code from the doc, you will always have a field, which will be null if the optional is empty, so at will never throw in this case.

@nlohmann nlohmann removed their assignment Sep 6, 2018
@nlohmann
Copy link
Owner

nlohmann commented Sep 6, 2018

(Sorry for the confusion on milestones.)

@nlohmann nlohmann removed this from the Release 3.2.1 milestone Sep 6, 2018
@tamarlev
Copy link
Author

tamarlev commented Sep 6, 2018

I dont really understand your answer
my problem is if for example I have a json like that:
json j= json{ {"id", "1234"},
{"name", "aaa"}};
if I try to access j.("Eye_Color") I will get an exception, how can I avoid asking if "Eye_Color" exists in j?
is that possible?I have a class with std::optional members I want to initialize them with std::optional in case they do not exists in j but j.at() prevent me from doing that.

@theodelrieu
Copy link
Contributor

Here is a full example (using boost::optional):

#include <nlohmann/json.hpp>
#include <boost/optional.hpp>

using nlohmann::json;

namespace nlohmann {
template <typename T> struct adl_serializer<boost::optional<T>> {
  static void to_json(json &j, const boost::optional<T> &opt) {
    if (!opt) {
      j = nullptr;
    } else {
      j = *opt; // this will call adl_serializer<T>::to_json which will
                // find the free function to_json in T's namespace!
    }
  }

  static void from_json(const json &j, boost::optional<T> &opt) {
    if (j.is_null()) {
      opt.reset();
    } else {
      opt = j.get<T>(); // same as above, but with
                        // adl_serializer<T>::from_json
    }
  }
};
}

struct test
{
  boost::optional<int> i;
};

void to_json(json& j, test const& t)
{
  j["opt"] = t.i;
}

void from_json(json const& j, test& t)
{
  t.i = j.at("opt").get<boost::optional<int>>();
}

int main(int argc, char const *argv[])
{
  nlohmann::json j(test{});

  auto t = j.get<test>();
}

at will not throw here, since a null optional is serialized as a null JSON value.

@tamarlev
Copy link
Author

tamarlev commented Sep 6, 2018

Thanks for showing a detail solution, when I tried to use std::optional instead of boost::optional, it did throw an exception, does json provide solution only to boost:optional and not std::optional?

@theodelrieu
Copy link
Contributor

It should work the same, I just could not test with std::optional on my machine.

Could you paste a complete reproducible example?

@tamarlev
Copy link
Author

tamarlev commented Sep 6, 2018

This code is giving me exception:
#include <json.hpp>
#include

using nlohmann::json;

namespace nlohmann {
template struct adl_serializer<std::optional> {
static void to_json(json &j, const std::optional &opt) {
if (!opt) {
j = nullptr;
}
else {
j = *opt; // this will call adl_serializer::to_json which will
// find the free function to_json in T's namespace!
}
}

	static void from_json(const json &j, std::optional<T> &opt) {
		if (j.is_null()) {
			opt.reset();
		}
		else {
			opt = j.get<T>(); // same as above, but with
							  // adl_serializer<T>::from_json
		}
	}
};

}

struct test
{
std::optional i;
std::optionalstd::string name;
};

void to_json(json& j, test const& t)
{
j["opt"] = t.i;
}

void from_json(json const& j, test& t)
{
t.i = j.at("opt").get<std::optional>();
t.name = j.at("name").get<std::optionalstd::string>();
}

int main(int argc, char const *argv[])
{
//nlohmann::json j(test{});
json j = json{{"opt", 1}};
test t = j;
}

so is that only works when you initialize json with
nlohmann::json j(test{})
and then call j.get()?

Thanks.

@theodelrieu
Copy link
Contributor

Could you edit your answer to put the code between triple backticks?

@tamarlev
Copy link
Author

tamarlev commented Sep 6, 2018

#include <json.hpp>
#include <optional>

using nlohmann::json;

namespace nlohmann {
template struct adl_serializer<std::optional> {
static void to_json(json &j, const std::optional &opt) {
if (!opt) {
j = nullptr;
}
else {
j = *opt; // this will call adl_serializer::to_json which will
// find the free function to_json in T's namespace!
}
}

static void from_json(const json &j, std::optional<T> &opt) {
   if (j.is_null()) {
	  opt.reset();
    }
   else {
	   opt = j.get<T>(); // same as above, but with
							  // adl_serializer<T>::from_json
    }
 }
};
}

struct test
{
std::optional i;
std::optionalstd::string name;
};

void to_json(json& j, test const& t)
{
j["opt"] = t.i;
}

void from_json(json const& j, test& t)
{
t.i = j.at("opt").get<std::optional>();
t.name = j.at("name").get<std::optionalstd::string>();
}

int main(int argc, char const *argv[])
{
//nlohmann::json j(test{});
json j = json{{"opt", 1}};
test t = j;
}

is that only works when you initialize json with
nlohmann::json j(test{}) j.get()?

@tamarlev tamarlev closed this as completed Sep 6, 2018
@tamarlev tamarlev reopened this Sep 6, 2018
@theodelrieu
Copy link
Contributor

Thanks, indeed it will throw because you did not put j["name"] = t.name in to_json.

I personally find it much easier to always have the field present in the json, and to have a null value when the optional is nullopt.

@tamarlev
Copy link
Author

tamarlev commented Sep 7, 2018

I tried to add j["name"] = t.name to to_json that didnt help I still got exception:

void to_json(json& j, test const& t)
{
	j["opt"] = t.i;
	j["name"] = t.name;
}

I think is because as you said you need to have it within json.

@theodelrieu
Copy link
Contributor

Oh, I finally get it. I did not see the end of your snippet...

Yes, the field must be present if you implement from json that way (i.e. with at, which is IMO way more convenient).

But it's up to you to use find in every from_json where there is optional fields. You also have to check optionals in every to_json, to not create a json value when they're nullopt.

If you do so, the adl_serializer specialization becomes irrelevant.

I strongly suggest the first approach though. If your JSON format cannot be changed, then you must use find.

I hope I was clear enough :)

@nlohmann
Copy link
Owner

@tamarlev Do you need further assistance?

@tamarlev
Copy link
Author

Thanks for the quick replies and no further assistance is required 👍

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
Projects
None yet
Development

No branches or pull requests

3 participants