Accept null values for strings with rapidjson inside cereal, make them "" - c++

For deserializing JSON into a c++ class, I'm using Cereal, which uses RapidJSON. As expected, c++ std::string can't have a null value. But other platforms do support null for strings (.NET SQL etc) and I get JSON from them with null values for strings. I need to tolerate this, and just make an empty string for nulls. What's the best way to do that?
I default to string substitute on the JSON changing nulls to "" like the following, but it is not a clean solution.
#include <cereal/archives/json.hpp>
#include <boost/algorithm/string.hpp>
// i.e. substitue all ":null with ":"" Like {"key":null} into {"key":""}
boost::replace_all(json, "\":null", "\":\"\"");
auto r = std::make_shared<R>();
std::stringstream ss(json);
{
cereal::JSONInputArchive archive(ss);
r->serialize(archive);
}
In case someone looks for this answer based on the exception generated by Cereal, it is: "rapidjson internal assertion failure: IsString()"

in cereal-1.1.2\include\cereal\external\rapidjson\document.h
change this
const Ch* GetString() const { RAPIDJSON_ASSERT(IsString()); return data_.s.str; }
to this
const Ch* GetString() const { if (IsNull_()) return ""; RAPIDJSON_ASSERT(IsString()); return data_.s.str; }
I'm not proposing this should be changed in the cereal source because some people may want strict type checking like the original

Related

How to replace #include <optional>

I come to you today with another question that my brain can't process by itself:
I got a cpp file that includes optional as a header file. Unfortunately, this works only on c++17 forwards, and I'm trying to compile it in c++14. This cpp file uses optional like this
std::optional<std::string> GetStringPropertyValueFromJson(const std::string& Property, const web::json::value& Json)
{
if (Json.has_field(utility::conversions::to_string_t(Property)))
{
auto& propertyValue = Json.at(utility::conversions::to_string_t(Property));
if (propertyValue.is_string())
{
return std::optional<std::string>{utility::conversions::to_utf8string(propertyValue.as_string())};
}
}
return std::nullopt;
}
and then the function is used to assign values like this:
std::string tokenType = GetStringPropertyValueFromJson("token_type", responseContent).value_or("");
std::string accessToken = GetStringPropertyValueFromJson("access_token", responseContent).value_or("");
Please help me with a proper substitution for OPTIONAL. Thanks and much love
PS: From what i've read, you can replace optional with pair somehow in order to get a similar result, but I don't really know how exactly.
PPS: I am new here so any tips on how to better write my questions or anything else are greatly appreciated :)
I guess in C++14 the optional header could be included by #include <experimental/optional>.
Change your method signature to
std::string GetStringPropertyValueFromJson(const std::string& Property, const web::json::value& Json)
and in the end just return the empty string
return "";
Then later in your code use it without std::optional::value_or:
std::string tokenType = GetStringPropertyValueFromJson("token_type", responseContent);
The logic is exactly the same and you don't use std::optional.
I see now your other question about possibility to use std::pair. Yes, you could also change your method to:
std::pair<std::string, bool> GetStringPropertyValueFromJson(const std::string& Property, const web::json::value& Json)
and return std::make_pair(valueFromJson, true) in case your json property has been found, or std::make_pair("", false) in case it was not. This also solves the problem with empty (but existing) json property.
A poor mans optional string that should be sufficient for your code is this:
struct my_nullopt {};
struct my_optional {
private:
std::string value;
bool has_value = false;
public:
my_optional(my_nullopt) {}
my_optional(const std::string& v) : value(v),has_value(true) {}
T value_or(const std::string& v) {
return has_value ? value : v;
}
};
Its a rather limited interface, for example it is not possible to set the value after construction. But it appears that you do not need that.
Alternatively you can use boost/optional.
Note that the tip you got about using a pair is just what I did above: The value and a bool. Just that std::pair is for cases where you cannot give better names than first and second (eg in generic code), but it is simple to provide a better interface than std::pair does here. With a pair the value_or would be something along the line of x.first ? x.second : "".
PS: Only in the end I realized that the code you present does not actually make use of what std::optional has to offer. As you are calling value_or(""), you cannot distinguish between a field with value "" or "" because the optional had no value. Because of that, the most simple solution is to use a plain std::string and return "" instead of std::nullopt.

Array or object: how to use nlohmann::json for simple use cases?

I want to use the nlohmann JSON library in order to read options from a JSON file. Specifying options is optional, as reflected in the constructor in my code example. I'm assuming the JSON structure is an object in its root.
Unfortunately, I'm unable to use these options, because it is unclear to me how I can force the JSON structure to be an object. What is worse, merely initializing a member variable with a JSON object {} (magically?) turns it into an array [{}].
#include <cstdlib>
#include <iostream>
#include <nlohmann/json.hpp>
class Example {
public:
explicit Example(const nlohmann::json& options = nlohmann::json::object())
: m_options{options}
{
std::clog << options << '\n' << m_options << '\n';
}
private:
nlohmann::json m_options;
};
auto main() -> int
{
Example x;
Example y{nlohmann::json::object()};
return EXIT_SUCCESS;
}
This results in the following output. Notice that we have to perform some ceremony in order to use an empty object as the default value (= empty settings), with = nlohmann::json::object(). Also notice that the settings object changes its type as soon as we initialize the member value (!):
{}
[{}]
My use use case is quite straightforward, but I'm unable to extract settings, unless I explicitly check whether the settings are an array or an object.
Another thing that worries me is that incorrect code compiles without warning, e.g., code in which I use x.value("y") on a JSON array x containing an object with key "y". Only at run time do I discover that I should have done x.at(0).value("y") instead.
In brief, the whole situation is quite surprising to me. I must be missing something / I must be using this library in an unintended way?
nlohman is a very "modern" library, it uses a lot of features in C++. And that might make it harder to read and understand the code. But it is very flexible.
This short introduction might help
Introduction to nlohmann json
Parse text to json object is done like
constexpr std::string_view stringJson = R"({"k1": "v1"})";
nlohmann::json j = nlohmann::json::parse( stringJson.begin(), stringJson.end() );

Cannot use std::string variable in RapidJSON function call

I am all new to C++ and am running into an issue. I am using rapidJSON to create JSON documents.
void setKeyValue() {
Value obj(kObjectType);
Value key("key");
Value val(42);;
obj.AddMember(key, val, d.GetAllocator());
}
Works as expected. But when I try to replace the call to key to make it use a passed in param, like so:
void setKeyValue(string myKey) {
Value obj(kObjectType);
Value key(myKey);
Value val(42);;
obj.AddMember(key, val, d.GetAllocator());
}
The myKey in Value key(myKey) get a red curly underling in Visual Studio saying the following:
What is causing this and how can I solve it?
You don't get support for std::string by default. rapidJSON requires you to specify you want std::string support.
#define RAPIDJSON_HAS_STDSTRING 1
Only then is this constructor you're using valid:
GenericValue (const std::basic_string< Ch > &s, Allocator &allocator)
JSON library you are using seems that doesn't work with string objects from standard library, but it works with const char*.
So you must convert string object to char* with the method c_str():
void setKeyValue(string myKey) {
Value obj(kObjectType);
Value key((char*)myKey.c_str());
Value val(42);;
obj.AddMember(key, val, d.GetAllocator());
}

C++ nlohmann/json how to use runtime provided json_pointers to read json values

I am using the json parser Json for Modern C++ (https://github.com/nlohmann/json). I know that I can get the value of a JSON value with a JSON_Pointer:
auto v1 = j["/a/b/c"_json_pointer];
But how would I go about getting the value if the JSON Pointer is defined at runtime (passed into my function)?
std:string s1 = "/a/b/c";
auto v1 = j[s1]; // doesn't work
You can't append "json_pointer" to either the std::string assignment or to the s1 variable. Is there a function that will convert a std::string to a json_pointer? The caller knows nothing about json and can't have access to the "json.hpp" header. I've also tried
std::string s1 = "/a/b/c";
json_pointer p1(s1);
but "json_pointer" class is undefined. Other than this issue this is a great library that does everything else I need. TIA.
Look at the source code:
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
{
return nlohmann::json::json_pointer(s);
}
If json_pointer is undefined, then you aren't using the right namespaces. Try
using nlohmann::json::json_pointer;
std::string s1 = "/a/b/c";
json_pointer p1(s1);

Error when passing a 'system::string' to a function

I have the following function, which will hopefully tell me whether or not a folder exists, but when I call it, I get this error -
cannot convert parameter 1 from 'System::String ^' to 'std::string'
The function -
#include <sys/stat.h>
#include <string>
bool directory_exists(std::string path){
struct stat fileinfo;
return !stat(path.c_str(), &fileinfo);
}
The call (from the form.h file that holds the form where the user selects the folder) -
private:
System::Void radioListFiles_CheckedChanged(System::Object^ sender, System::EventArgs^ e) {
if(directory_exists(txtActionFolder->Text)){
this->btnFinish->Enabled = true;
}
}
Is anyone able to tell me how to filx this? Thanks.
You're trying to convert from a managed, C++/CLI string (System::String^) into a std::string. There is no implicit conversion provided for this.
In order for this to work, you'll have to handle the string conversion yourself.
This will likely look something like:
std::string path = context->marshal_as<std::string>(txtActionFolder->Text));
if(directory_exists(path)) {
this->btnFinish->Enabled = true;
}
That being said, in this case, it might be easier to stick to managed APIs entirely:
if(System::IO::Directory::Exists(txtActionFolder->Text)) {
this->btnFinish->Enabled = true;
}
You are trying to convert a CLR string to a STL string to convert it to a C-string to use it with a POSIX-emulation function. Why such a complication? Since you are using C++/CLI anyway, just use System::IO::Directory::Exists.
To make this work you need to convert from the managed type System::String to the native type std::string. This involves a bit of marshaling and will result in 2 separate string instances. MSDN has a handy table for all of the different types of marshaling for strings
http://msdn.microsoft.com/en-us/library/bb384865.aspx
In this particular case you can do the following
std::string nativeStr = msclr::interop::marshal_as<std::string>(managedStr);