This helper generates functions that access inner data of Boost.JSON value with predefined path and predefined type.
This helper is single-header library.
- Boost 1.82 or later
- C++ 17 or later
Include json_access_helper.hpp
.
You may want to avoid writing JSON data path in every source code because it makes the code more dependent on the applications's JSON data structure.
using boost::json::value;
void func() {
value jv = read_json_from_file("app_config.json");
int age = jv.at_pointer("/user/age").as_int64();
}
void another_func(const value& jv) {
int age = jv.at_pointer("/user/age").as_int64();
}
In the case of above, you need to change every code that accesses /user/age
if you changed the JSON structure of your application.
Writing JSON access functions like below may reduce the dependency on JSON structure.
using boost::json::value;
int read_age_from_json(const value& jv) {
return jv.at_pointer("/user/age").as_int64();
}
int func() {
// caller do not need to know the path to the data.
int age = read_age_from_json(jv);
}
However, writing functions like above for every JSON data may be repetitive.
int read_age_from_json(const value& jv) {
return jv.at_pointer("/user/age").as_int64();
}
void write_age_to_json(value& jv, int age) {
jv.at_pointer("/user/age") = value(age);
}
std::string read_name_from_json(const value& jv) {
return jv.at_pointer("/user/name").as_string();
}
void write_name_to_json(value& jv, const std::string& name) {
jv.at_pointer("/user/name") = value(name);
}
// for every data you use in your application!
Using this helper utility, you can define JSON access function in simple way.
#include <boost/json.hpp>
#include <json_access_helper.hpp>
#include <string>
#include <vector>
using std::string;
using std::vector;
namespace json_access_helper {
// define (tag, type, path) to make helper functions
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserAge, int, "/user/age")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserName, string, "/user/name")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserSkills, vector<string>, "/user/skills")
} // namespace json_access_helper
namespace my_app {
boost::json::value read_json_from_file(const std::string& file_path);
int main() {
namespace jac = json_access_helper;
namespace json = boost::json;
json::value jv = read_json_from_file("app_config.json");
// reads value from predefined path and converts it to predefined type.
int age = jac::read(jv, jac::UserAge);
// tries to read value from the predefined path and tries to convert it
// to the predefined type.
json::result<string> name_result = jac::try_read(jv, jac::UserName)
if (name) {
std::string name = *name_result;
} else {
std::cout << name_result.error();
}
// writes value to the predefined path
jac::write(jv, jac::UserSkills, std::vector<std::string>{"C++", "C", "Python"});
// writes value to the predefined path with creating intermediate elements.
json::value new_jv = json::value();
jac::emplace(jv, jac::UserSkills, std::vector<std::string>{"C++", "C", "Python"});
return 0;
}
} // namespace my_app
Use DECLARE_JSON_ACCESSOR
macro in header file and use DEFINE_JSON_ACCESSOR
macro in cpp file.
The recommended way it to make a file to be included from declaration file and definition file as follows.
common.hpp
// Do not include any file and do not define any macro in this file!
MAKE_JSON_ACCESSOR(UserAge, int, "/user/age")
MAKE_JSON_ACCESSOR(UserName, string, "/user/name")
MAKE_JSON_ACCESSOR(UserSkills, vector<string>, "/user/skills")
my_json_accessor.hpp
#include <boost/json.hpp>
#include <json_access_helper.hpp>
// replace MAKE_JSON_ACCESSOR in common.hpp with DECLARE_JSON_ACCESSOR
#define MAKE_JSON_ACCESSOR DECLARE_JSON_ACCESSOR
// wrap common.hpp with the namespace where you want to define functions.
namespace json_accessor {
#include "common.hpp"
}
#undef MAKE_JSON_ACCESSOR
#undef DECLARE_JSON_ACCESSOR
#undef DEFINE_JSON_ACCESSOR
my_json_accessor.cpp
#include <boost/json.hpp>
#include <json_access_helper.hpp>
// replace MAKE_JSON_ACCESSOR in common.hpp with DEFINE_JSON_ACCESSOR
#define MAKE_JSON_ACCESSOR DEFINE_JSON_ACCESSOR
// wrap common.hpp with the namespace where you want to define functions.
namespace json_accessor {
#include "common.hpp"
}
#undef MAKE_JSON_ACCESSOR
#undef DECLARE_JSON_ACCESSOR
#undef DEFINE_JSON_ACCESSOR
Use DECLARE_AND_DEFINE_JSON_ACCESSOR
macro.
my_json_accessor.hpp
#include <boost/json.hpp>
#include <json_access_helper.hpp>
// wrap the macro with the namespace where you want to define functions.
namespace json_accessor {
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserAge, int, "/user/age")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserName, string, "/user/name")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserSkills, vector<string>, "/user/skills")
}
#undef MAKE_JSON_ACCESSOR
#undef DECLARE_JSON_ACCESSOR
#undef DEFINE_JSON_ACCESSOR
This helper makes function for each path.
For example,
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserAge, int, "/user/age")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserName, string, "/user/name")
the code above defines two read
functions like below.
// tag types
struct UserAgeT {};
struct UserNameT {};
// constants for each tag type
inline constexpr UserAgeT UserAge = {};
inline constexpr UserNameT UserName = {};
// generated functions
int read(const boost::json::value& jv, UserAgeT); // #1
string read(const boost::json::value& jv, UserNameT); // #2
The function overload is resolved by the second argument.
json::value js = read_json_from_file("app_config.json");
auto age = read(jv, UserAge); // #1 is called
auto name = read(jv, UserName); // #2 is called
This helper generates the following functions.
Type read(const boost::json::value& jv, const Tag&);
boost::json::result<Type> try_read(const boost::json::value& jv, const Tag&);
bool write(boost::json::value& jv, const Tag&, const Type& value);
bool write(boost::json::value& jv, const Tag&, Type&& value);
bool write(boost::json::value& jv, const Tag&, nullptr_t value);
boost::json::value* emplace(boost::json::value& jv, const Tag&, const Type& value);
boost::json::value* emplace(boost::json::value& jv, const Tag&, Type&& value);
boost::json::value* emplace(boost::json::value& jv, const Tag&, nullptr_t value);
boost::json::value* reference(boost::json::value& jv, const Tag&);
const boost::json::value* reference(const boost::json::value& jv, const Tag&);
std::string_view path(const Tag&);
In the examples in this section, the followings are assumed to be predefined.
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserAge, int, "/user/age")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserName, string, "/user/name")
DECLARE_AND_DEFINE_JSON_ACCESSOR(UserSkills, vector<string>, "/user/skills")
using boost::json::value;
using std::string;
using std::vector;
value read_json_from_file(const std::string& path);
Reads data from the specified path and converts it to the specified type. If failed, it throws exception defined in Boost.JSON.
Example:
value jv = read_json_from_file("app_config.json");
int age = read(json, UserAge);
string name = read(json, UserName);
Note:
This function uses boost::json::value_to
for type conversion.
Tries to read data from the specified path and tries to convert it to the specified type.
The result type of this function is boost::json::result
.
Example:
value jv = read_json_from_file("app_config.json");
auto result = try_read(jv, UserAge);
if (result) {
int age = *result;
} else {
std::cout << result.error();
}
Note:
This function uses boost::json::try_value_to
for type conversion.
Writes value to the predefined path. This function fails if failed to access the existing boost::json::value
in the path.
This function returns true
if succeeded to write the value and returns false
if failed.
You can pass the value of the predefined type or nullptr
.
Example:
value jv = read_json_from_file("app_config.json");
std::string new_user_name = "Alice";
bool succeeded = write(jv, UserName, new_user_name);
// pass nullptr to make the value to null
succeeded = write(jv, UserName, nullptr);
Note:
This function uses boost::json::value_from
for type conversion.
Emplaces value to the path. This function may create the intermediate element to the path.
The return value is the reference of the created boost::json::value&
.
You can pass the value of the predefined type or nullptr
.
Example:
value jv = boost::json::value(); // make empty json
emplace(jv, UserName, std::string("Alice"));
emplace(jv, UserAge, 23);
emplace(jv, UserAge, std::vector<std::string>{"C++", "C", "Python"});
// pass nullptr to make the value to null
emplace(jv, UserAge, nullptr);
Note:
This function uses boost::json::value_from
for type conversion.
Gets the pointer to the value in the predefined path. This function returns nullptr
if filed to access.
value jv = read_json_from_file("app_config.json");
value* name_ref = reference(jv, UserName);
Gets the predefined path.
assert(path(UserSkills) == "/user/skills");
gcc 11.4.0