Value conversions¶
Conversion to nlohmann::json
¶
The value of (the elements of) a nlohmann::json
object can be set as follows:
int myInt = 1;
nlohmann::json j = myInt; // j.is_numeric( ) -> true
double myDouble = 1.0;
nlohmann::json k; // k.is_null( ) -> true
k[ 0 ] = myDouble; // k.is_array( ) && k[ 0 ].is_numeric( ) && j == k[ 0 ] -> true
However, this does not work (yet):
MyClass myObject = ...
nlohmann::json j = myObject; // compile error
because the JSON representation of MyClass
objects is not known.
At first, one could think that the nlohmann::json
class has constructors taking as an argument a basic type such as double
, int
, bool
, std::string
, std::vector
or std::map
. However, this is not true. When writing:
std::string myString = "hi";
nlohmann::json j = myString;
the following is happening behind the scenes:
...
nlohmann::json j;
std::to_json( j, "hi" );
return j;
Indeed, to_json
functions are defined for many frequently used types in the file "json/src/json.hpp"
. These functions are always defined in the namespace of the type that is on the right-hand side of the assignment. We can create to_json
functions for our custom classes:
namespace animals
{
class Dog
{
public:
std::string name;
double weight;
Dog( const std::string& name, const double weight ) :
name( name ), weight( weight ) { }
};
}
#include "json/src/json.hpp"
#include "dog.h"
namespace animals
{
void to_json( nlohmann::json& j, const Dog& dog )
{
j[ "name" ] = dog.name; // std::to_json( nlohmann::json&, const string& ) will be called
j[ "weight" ] = dog.weight; // to_json( nlohmann::json&, const double& ) will be called
}
}
#include "jsonInterface.h"
void printDog( )
{
nlohmann::json jsonDog = animals::Dog( "Senda", 20 );
std::cout << jsonDog << std::endl; // {"name":"Senda","weight":20}
}
Conversion from nlohmann::json
¶
The value of the elements of a nlohmann::json
object can be accessed as follows:
nlohmann::json j = { "no", "yes" };
j[ 0 ]; // "no"
j.at( 1 ); // "yes"
nlohmann::json k = { { "half", 0.5 }, { "twice", 2.0 } };
k[ "half" ]; // 0.5
k.at( "twice" ); // 2.0
However, as discussed in Value types, the returned values are not numbers or strings, but nlohmann::json
objects. Thus:
std::string str = j[ 0 ] + ", thanks"; // compile error
std::string no = j[ 0 ]; // "no"
std::string str = no + ", thanks"; // "no, thanks"
The implicit conversion is done by overloading the =
operator. This means that the following won’t work:
std::string str = std::string( j[ 0 ] ) + ", thanks"; // compile error
However, an explicit conversion is always possible by calling the templated get
method:
std::string str = j[ 0 ].get< std::string >( ) + ", thanks"; // "no, thanks"
What is happening behind the scenes, either when an implicit conversion takes place or the get
method is called, is:
...
std::string str; // str -> ""
std::from_json( j, str ); // str -> "no"
return str;
The from_json
functions must be defined in the namespace of the type we are converting to. Many are pre-defined for frequently-used types. But, for our Dog
class, this is not possible yet:
nlohmann::json jsonDog = { { "name", "Senda" }, { "weight", 20 } };
Dog dog = jsonDog; // compile error
We have to define its from_json
function:
#include "json/src/json.hpp"
#include "dog.h"
namespace animals
{
...
void from_json( const nlohmann::json& j, Dog& dog )
{
dog = Dog( j.at( "name" ), j.at( "weight" ) );
}
}
However, we are still getting a compile error, because this is happening behind the scenes:
...
animals::Dog dog; // compile error: Dog is not default-constructible!
animals::from_json( j, dog );
return dog;
Thus, we need to define a default constructor for the class Dog
. We can do this by providing default values for all the arguments in the constructor:
namespace animals
{
class Dog
{
public:
std::string name;
double weight;
Dog( const std::string& name = "", const double weight = 0.0 ) :
name( name ), weight( weight ) { }
};
}
Now, we can do:
nlohmann::json jsonDog = { { "name", "Senda" }, { "weight", 20 } };
Dog dog = jsonDog; // fine