Demos
taoJSON (taocpp/json) and JSON for Modern C++ (nlohman/json) libraries are used in these demos.
Unpacking array into function parameters
taoJSON: godbolt.org/z/z1zGdfoG1
code (tap to show)
#include <iostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <tao/json.hpp>
using namespace std::literals::string_view_literals;
namespace json = tao::json;
template<typename... F>
struct Overloaded : F... {
using F::operator()...;
};
// deduction guide is needed until
// all compilers fully implement C++20
template<typename... F>
Overloaded(F...)->Overloaded<F...>;
template<typename T>
bool isConvertibleTo(const json::value &value) {
return std::visit(
Overloaded{
[](bool) { return std::is_same_v<T, bool>; },
// worksn't with GCC as of 11.2 and Clang 12 (fixed in 13)
[](std::floating_point auto) { return std::is_floating_point_v<T>; },
[](std::integral auto) { return std::is_arithmetic_v<T>; },
// worksn't at all with GCC as of 11.2
//[]<std::floating_point U>(U) { return std::is_floating_point_v<T>; },
//[]<std::integral U>(U) { return std::is_arithmetic_v<T>; },
[](auto &v) { return std::is_convertible_v<decltype(v), T>; }
},
value.variant());
}
template<typename T>
std::string_view getTypeName() {
if constexpr (std::is_same_v<T, bool>)
return "boolean";
else if constexpr (std::is_integral_v<T>)
return "integer";
else if constexpr (std::is_floating_point_v<T>)
return "floating point number";
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
return "string";
else if constexpr (std::is_same_v<T, json::value::array_t>)
return "array";
else if constexpr (std::is_same_v<T, json::value> || std::is_same_v<T, json::value::object_t>)
return "object";
else
return "unknown type";
}
template<typename Variant>
std::string_view getTypeName(const Variant &v) {
return std::visit([](auto &&v) {
return getTypeName<std::decay_t<decltype(v)>>();
}, v);
}
using Name = std::string_view;
template<typename ExpectedType>
void reportInvalidArg(size_t i, const json::value &value) {
std::cout << "* arg " << (i + 1) << " has unexpected type: "
<< getTypeName(value.variant())
<< " (expected: " << getTypeName<ExpectedType>() << ")\n";
}
template<typename T>
bool validateArg(size_t i, const std::vector<json::value> &args) {
if (isConvertibleTo<T>(args[i]))
return true;
reportInvalidArg<T>(i, args[i]);
return false;
}
template<typename... Args>
bool validateArgs(const std::vector<json::value> &args) {
constexpr size_t ArgCount = sizeof...(Args);
const bool argCountIsValid = args.size() == ArgCount;
if (!argCountIsValid)
std::cout << "* invalid arg count: " << args.size() << " (expected: " << ArgCount << ")\n";
bool isValid = argCountIsValid;
size_t index = 0;
((
isValid = index < args.size() && validateArg<Args>(index, args) && isValid, ++index
), ...);
return isValid;
}
template<typename T>
decltype(auto) getAs(const json::value &v) {
if constexpr (std::is_same_v<T, json::value>)
return v;
else
return v.as<T>();
}
template<typename... Args, size_t... I>
void applyImpl(void (*handler)(Args...), const std::vector<json::value> &args, std::index_sequence<I...>) {
handler(getAs<std::decay_t<Args>>(args[I])...);
}
template<typename... Args>
void apply(void (*handler)(Args...), const std::vector<json::value> &args) {
applyImpl(handler, args, std::make_index_sequence<sizeof...(Args)>{});
}
struct Handler {
template<typename... Args>
Handler(void(*handler)(Args...)) : erasedHandler{ reinterpret_cast<void(*)()>(handler) },
handlerImpl{ [](void(*erasedHandler)(), const json::value *args) {
auto handler = reinterpret_cast<void(*)(Args...)>(erasedHandler);
if constexpr (sizeof...(Args) == 0) {
if (args) {
if (!args->is_array())
throw std::invalid_argument{ "request 'args' is not an array" };
if (!args->get_array().empty())
throw std::invalid_argument{ "handler expects 0 arguments" };
}
handler();
}
else {
if (!args)
throw std::invalid_argument{ "request does not contain arguments" };
if (!args->is_array())
throw std::invalid_argument{ "request 'args' is not an array" };
auto &argArray = args->get_array();
if (!validateArgs<std::decay_t<Args>...>(argArray))
throw std::invalid_argument{ "request arguments are invalid" };
apply(handler, argArray);
}
} }
{}
void operator()(const json::value *args) const {
handlerImpl(erasedHandler, args);
}
private:
void(*erasedHandler)() = nullptr;
void(*handlerImpl)(void(*erasedHandler)(), const json::value*) = nullptr;
};
void foo() {
std::cout << "got 'foo' request\n";
}
void bar(std::string_view s) {
std::cout << "got 'bar' request with\n"
" '" << s << "'\n";
}
void baz(int i, std::string_view s, const json::value &p) {
std::cout << "got 'baz' request with\n"
" '" << i << "',\n"
" '" << s << "',\n"
" and\n"
<< json::to_string(p) << '\n';
}
const std::tuple<Name, Handler> handlers[] = {
{ "foo", &foo },
{ "bar", &bar },
{ "baz", &baz }
};
void processRequest(const std::string_view &message) {
const auto json = json::from_string(message);
if (!json.is_object())
throw std::invalid_argument{ "request is not a valid JSON" };
auto *request = json.find("request");
if (!request)
throw std::invalid_argument{ "request does not contain name" };
auto *args = json.find("args");
auto &name = request->get_string();
std::cout << "* trying to process " << name << '\n';
for (auto handler : handlers) {
if (std::get<Name>(handler) == name) {
std::get<Handler>(handler)(args);
return;
}
}
std::cout << "* could not handle request " << name << '\n';
}
int main() {
auto request = R"({
"request":"baz",
"args":[1, "two", { "three":3 }]
})"sv;
try {
processRequest(request);
}
catch (const std::exception &e) {
std::cout << "error: " << e.what() << '\n';
}
}
JSON for Modern C++: godbolt.org/z/zroor1E8f
code (tap to show)
#include <iostream>
#include <string>
#include <string_view>
#include <type_traits>
#include <nlohmann/json.hpp>
using namespace std::literals::string_view_literals;
using nlohmann::json;
template<typename T>
bool isConvertibleTo(const json &value) {
switch (value.type())
{
case json::value_t::boolean: return std::is_same_v<T, bool>;
case json::value_t::string: return std::is_convertible_v<json::string_t, T>;
case json::value_t::number_unsigned: return std::is_convertible_v<json::number_unsigned_t, T>;
case json::value_t::number_integer: return std::is_convertible_v<json::number_integer_t, T>;
case json::value_t::number_float: return std::is_convertible_v<json::number_float_t, T>;
case json::value_t::object: return std::is_same_v<T, json> || std::is_same_v<T, json::object_t>;
case json::value_t::array: return std::is_same_v<json::array_t, T>;
default: break;
}
return false;
}
template<typename T>
std::string_view getTypeName() {
if constexpr (std::is_same_v<T, bool>)
return "boolean";
else if constexpr (std::is_integral_v<T>)
return "integer";
else if constexpr (std::is_floating_point_v<T>)
return "floating point number";
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
return "string";
else if constexpr (std::is_same_v<T, json::array_t>)
return "array";
else if constexpr (std::is_same_v<T, json> || std::is_same_v<T, json::object_t>)
return "object";
else
return "unknown type";
}
std::string_view getTypeName(const json &value) {
switch (value.type())
{
case json::value_t::boolean: return getTypeName<bool>();
case json::value_t::string: return getTypeName<json::string_t>();
case json::value_t::number_unsigned: return getTypeName<json::number_unsigned_t>();
case json::value_t::number_integer: return getTypeName<json::number_integer_t>();
case json::value_t::number_float: return getTypeName<json::number_float_t>();
case json::value_t::object: return getTypeName<json>();
case json::value_t::array: return getTypeName<json::array_t>();
default: break;
}
return getTypeName<void>(); // for "unknown type"
}
using Name = std::string_view;
template<typename ExpectedType>
void reportInvalidArg(size_t i, const json &value) {
std::cout << "* arg " << i << " has unexpected type: "
<< getTypeName(value)
<< " (expected: " << getTypeName<ExpectedType>() << ")\n";
}
template<typename T>
bool validateArg(size_t i, const std::vector<json> &args) {
if (isConvertibleTo<T>(args[i]))
return true;
reportInvalidArg<T>(i, args[i]);
return false;
}
template<typename... Args>
bool validateArgs(const std::vector<json> &args) {
constexpr size_t ArgCount = sizeof...(Args);
const bool argCountIsValid = args.size() == ArgCount;
if (!argCountIsValid)
std::cout << "* invalid arg count: " << args.size() << " (expected: " << ArgCount << ")\n";
bool isValid = argCountIsValid;
size_t index = 0;
((
isValid = index < args.size() && validateArg<Args>(index, args) && isValid, ++index
), ...);
return isValid;
}
template<typename T>
decltype(auto) getAs(const json &v) {
if constexpr (std::is_same_v<T, json>)
return v;
else
return v.get<T>();
}
template<typename... Args, size_t... I>
void applyImpl(void (*handler)(Args...), const std::vector<json> &args, std::index_sequence<I...>) {
handler(getAs<std::decay_t<Args>>(args[I])...);
}
template<typename... Args>
void apply(void (*handler)(Args...), const std::vector<json> &args) {
applyImpl(handler, args, std::make_index_sequence<sizeof...(Args)>{});
}
struct Handler {
template<typename... Args>
Handler(void(*handler)(Args...)) : erasedHandler{ reinterpret_cast<void(*)()>(handler) },
handlerImpl{ [](void(*erasedHandler)(), const json *args) {
auto handler = reinterpret_cast<void(*)(Args...)>(erasedHandler);
if constexpr (sizeof...(Args) == 0) {
if (args) {
if (!args->is_array())
throw std::invalid_argument{ "request 'args' is not an array" };
if (!args->get_ref<const json::array_t&>().empty())
throw std::invalid_argument{ "handler expects 0 arguments" };
}
handler();
}
else {
if (!args)
throw std::invalid_argument{ "request does not contain arguments" };
if (!args->is_array())
throw std::invalid_argument{ "request 'args' is not an array" };
auto &argArray = args->get_ref<const json::array_t&>();
if (!validateArgs<std::decay_t<Args>...>(argArray))
throw std::invalid_argument{ "request arguments are invalid" };
apply(handler, argArray);
}
} }
{}
void operator()(const json *args) const {
handlerImpl(erasedHandler, args);
}
private:
void(*erasedHandler)() = nullptr;
void(*handlerImpl)(void(*erasedHandler)(), const json*) = nullptr;
};
void foo() {
std::cout << "got 'foo' request\n";
}
void bar(std::string_view s) {
std::cout << "got 'bar' request with\n"
" '" << s << "'\n";
}
void baz(int i, std::string_view s, const json &p) {
std::cout << "got 'baz' request with\n"
" '" << i << "',\n"
" '" << s << "',\n"
" and\n"
<< p.dump() << '\n';
}
const std::tuple<Name, Handler> handlers[] = {
{ "foo", &foo },
{ "bar", &bar },
{ "baz", &baz }
};
void processRequest(const std::string_view &message) {
const auto json = json::parse(message);
if (!json.is_object())
throw std::invalid_argument{ "request is not a valid JSON" };
auto request = json.find("request");
if (request == json.end())
throw std::invalid_argument{ "request does not contain name" };
auto args = json.find("args");
auto &name = request->get_ref<const json::string_t&>();
std::cout << "* trying to process " << name << '\n';
for (auto handler : handlers) {
if (std::get<Name>(handler) == name) {
std::get<Handler>(handler)(args == json.end() ? nullptr : &*args);
return;
}
}
std::cout << "* could not handle request " << name << '\n';
}
int main() {
auto request = R"({
"request":"baz",
"args":[1, "two", { "three":3 }]
})"sv;
try {
processRequest(request);
}
catch (const std::exception &e) {
std::cout << "error: " << e.what() << '\n';
}
}
Unpacking dictionary into named parameters
taoJSON: godbolt.org/z/b7sqdKv5e
code (tap to show)
#include <iostream>
#include <string>
#include <string_view>
#include <optional>
#include <type_traits>
#include <tao/json.hpp>
using namespace std::literals::string_view_literals;
namespace json = tao::json;
template<typename... F>
struct Overloaded : F... {
using F::operator()...;
};
// deduction guide is needed until
// all compilers fully implement C++20
template<typename... F>
Overloaded(F...)->Overloaded<F...>;
template<typename T>
bool isConvertibleTo(const json::value &value) {
return std::visit(
Overloaded{
[](bool) { return std::is_same_v<T, bool>; },
// worksn't with GCC as of 11.2 and Clang 12 (fixed in 13)
[](std::floating_point auto) { return std::is_floating_point_v<T>; },
[](std::integral auto) { return std::is_arithmetic_v<T>; },
// worksn't at all with GCC as of 11.2
//[]<std::floating_point U>(U) { return std::is_floating_point_v<T>; },
//[]<std::integral U>(U) { return std::is_arithmetic_v<T>; },
[](auto &v) { return std::is_convertible_v<decltype(v), T>; }
},
value.variant());
}
template<typename T>
std::string_view getTypeName() {
if constexpr (std::is_same_v<T, bool>)
return "boolean";
else if constexpr (std::is_integral_v<T>)
return "integer";
else if constexpr (std::is_floating_point_v<T>)
return "floating point number";
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
return "string";
else if constexpr (std::is_same_v<T, json::value::array_t>)
return "array";
else if constexpr (std::is_same_v<T, json::value> || std::is_same_v<T, json::value::object_t>)
return "object";
else
return "unknown type";
}
template<typename Variant>
std::string_view getTypeName(const Variant &v) {
return std::visit([](auto &&v) {
return getTypeName<std::decay_t<decltype(v)>>();
}, v);
}
using Name = std::string_view;
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char(&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template<StringLiteral Name, typename T>
struct Param {
using ValueType = T;
const T &value;
static std::string_view name() { return Name.value; }
};
template<StringLiteral Name, typename T>
struct Param<Name, std::optional<T>> {
using ValueType = std::optional<T>;
std::optional<T> value;
static std::string_view name() { return Name.value; }
};
template<typename T>
struct IsParameter : std::false_type {};
template<StringLiteral N, typename T>
struct IsParameter<Param<N, T>> : std::true_type {};
using Name = std::string_view;
template<typename T>
struct IsOptional : std::false_type {};
template<typename T>
struct IsOptional<std::optional<T>> : std::true_type {};
template<typename T>
inline constexpr bool isOptional = IsOptional<T>::value;
template<typename ExpectedType>
void reportInvalidArg(std::string_view name, const json::value &value) {
std::cout << "* parameter '" << name << "' has unexpected type: "
<< getTypeName(value.variant())
<< " (expected: " << getTypeName<ExpectedType>() << ")\n";
}
template<typename Param>
bool validateArg(const json::value &request) {
auto *value = request.find(Param::name());
if (!value || value->is_null()) {
if constexpr (isOptional<typename Param::ValueType>)
return true;
std::cout << "* missing parameter '" << Param::name() << "'\n";
return false;
}
if constexpr (isOptional<typename Param::ValueType>) {
using T = typename Param::ValueType::value_type;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
else {
using T = typename Param::ValueType;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
return true;
}
template<typename... Args>
bool validateArgs(const json::value &request) {
bool isValid = true;
((isValid = validateArg<Args>(request) && isValid), ...);
return isValid;
}
template<typename T>
decltype(auto) getAs(const json::value *v) {
if constexpr (isOptional<T>) {
return v && !v->is_null() ? T{ getAs<typename T::value_type>(v) } : T{};
}
else
{
if constexpr (std::is_same_v<T, json::value>)
return *v;
else
return v->as<T>();
}
}
template<typename... Args>
void apply(void (*handler)(Args...), const json::value &request) {
handler(
{ getAs<typename Args::ValueType>(request.find(Args::name())) }...
);
}
template<typename P>
void printParamDescription() {
using T = typename P::ValueType;
std::cout << " '" << P::name() << "': ";
if constexpr (isOptional<T>) {
std::cout << getTypeName<typename T::value_type>() << " (optional)\n";
}
else {
std::cout << getTypeName<T>() << '\n';
}
}
struct Handler {
template<typename... Args>
requires (IsParameter<Args>::value && ...)
Handler(void(*handler)(Args...)) : erasedHandler{ reinterpret_cast<void(*)()>(handler) },
handlerImpl{ [](void(*erasedHandler)(), const json::value &request) {
auto handler = reinterpret_cast<void(*)(Args...)>(erasedHandler);
if constexpr (sizeof...(Args) == 0) {
handler();
}
else {
if (!validateArgs<Args...>(request))
throw std::invalid_argument{ "request arguments are invalid" };
apply(handler, request);
}
} }
,
printParamDescriptionsImpl{ [] {
if constexpr (sizeof...(Args) == 0)
std::cout << " (no arguments)\n";
else
(printParamDescription<Args>(), ...);
} }
{}
void operator()(const json::value &request) const {
handlerImpl(erasedHandler, request);
}
void printParamDescriptions() const {
printParamDescriptionsImpl();
}
private:
void(*erasedHandler)() = nullptr;
void(*handlerImpl)(void(*erasedHandler)(), const json::value&) = nullptr;
void(*printParamDescriptionsImpl)() = nullptr;
};
void foo() {
std::cout << "got 'foo' request\n";
}
void bar(Param<"id", std::string_view> s) {
std::cout << "got 'bar' request with\n"
" " << s.name() << "='" << s.value << "'\n";
}
void baz(Param<"count", int> i, Param<"id", std::string_view> s, Param<"payload", json::value> p) {
std::cout << "got 'baz' request with\n"
" " << i.name() << "='" << i.value << "',\n"
" " << s.name() << "='" << s.value << "', and\n"
" " << p.name() << "=\n"
<< json::to_string(p.value) << "\n";
}
void qux(Param<"count", int> i, Param<"id", std::optional<std::string_view>> s, Param<"payload", json::value> p) {
std::cout << "got 'qux' request with\n"
" " << i.name() << "='" << i.value << "',\n";
if (s.value) {
std::cout << ' ' << s.name() << "='" << *s.value << "',\n";
}
else {
std::cout << ' ' << s.name() << " parameter is absent,\n";
}
std::cout << " and\n " << p.name() << "=\n"
<< json::to_string(p.value) << "\n";
}
const std::tuple<Name, Handler> handlers[] = {
{ "foo", &foo },
{ "bar", &bar },
{ "baz", &baz },
{ "qux", &qux }
};
void processRequest(const std::string_view &message) {
const auto json = json::from_string(message);
if (!json.is_object())
throw std::invalid_argument{ "request is not a valid JSON" };
auto *request = json.find("request");
if (!request)
throw std::invalid_argument{ "request does not contain name" };
auto &name = request->get_string();
std::cout << "* trying to process " << name << '\n';
for (auto handler : handlers) {
if (std::get<Name>(handler) == name) {
std::get<Handler>(handler)(json);
return;
}
}
std::cout << "* could not handle request " << name << '\n';
}
void printDescriptions() {
for (auto handler : handlers) {
std::cout << '\n' << std::get<Name>(handler) << '\n';
std::get<Handler>(handler).printParamDescriptions();
}
}
int main() {
auto request = R"({
"request":"qux",
"count":1,
"id":"two",
"payload":{ "three":3 }
})"sv;
try {
processRequest(request);
}
catch (const std::exception &e) {
std::cout << "error: " << e.what() << '\n';
}
std::cout << "\n\nAPI description:\n";
printDescriptions();
}
JSON for Modern C++: godbolt.org/z/Pe9446aEP
code (tap to show)
#include <iostream>
#include <string>
#include <string_view>
#include <optional>
#include <type_traits>
#include <nlohmann/json.hpp>
using namespace std::literals::string_view_literals;
using nlohmann::json;
template<typename T>
bool isConvertibleTo(const json &value) {
switch (value.type())
{
case json::value_t::boolean: return std::is_same_v<T, bool>;
case json::value_t::string: return std::is_convertible_v<json::string_t, T>;
case json::value_t::number_unsigned: return std::is_convertible_v<json::number_unsigned_t, T>;
case json::value_t::number_integer: return std::is_convertible_v<json::number_integer_t, T>;
case json::value_t::number_float: return std::is_convertible_v<json::number_float_t, T>;
case json::value_t::object: return std::is_same_v<T, json> || std::is_same_v<T, json::object_t>;
case json::value_t::array: return std::is_same_v<json::array_t, T>;
default: break;
}
return false;
}
template<typename T>
std::string_view getTypeName() {
if constexpr (std::is_same_v<T, bool>)
return "boolean";
else if constexpr (std::is_integral_v<T>)
return "integer";
else if constexpr (std::is_floating_point_v<T>)
return "floating point number";
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
return "string";
else if constexpr (std::is_same_v<T, json::array_t>)
return "array";
else if constexpr (std::is_same_v<T, json> || std::is_same_v<T, json::object_t>)
return "object";
else
return "unknown type";
}
std::string_view getTypeName(const json &value) {
switch (value.type())
{
case json::value_t::boolean: return getTypeName<bool>();
case json::value_t::string: return getTypeName<json::string_t>();
case json::value_t::number_unsigned: return getTypeName<json::number_unsigned_t>();
case json::value_t::number_integer: return getTypeName<json::number_integer_t>();
case json::value_t::number_float: return getTypeName<json::number_float_t>();
case json::value_t::object: return getTypeName<json>();
case json::value_t::array: return getTypeName<json::array_t>();
default: break;
}
return getTypeName<void>(); // for "unknown type"
}
using Name = std::string_view;
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char(&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template<StringLiteral Name, typename T>
struct Param {
using ValueType = T;
const T &value;
static std::string_view name() { return Name.value; }
};
template<StringLiteral Name, typename T>
struct Param<Name, std::optional<T>> {
using ValueType = std::optional<T>;
std::optional<T> value;
static std::string_view name() { return Name.value; }
};
template<typename T>
struct IsParameter : std::false_type {};
template<StringLiteral N, typename T>
struct IsParameter<Param<N, T>> : std::true_type {};
using Name = std::string_view;
template<typename T>
struct IsOptional : std::false_type {};
template<typename T>
struct IsOptional<std::optional<T>> : std::true_type {};
template<typename T>
inline constexpr bool isOptional = IsOptional<T>::value;
template<typename ExpectedType>
void reportInvalidArg(std::string_view name, const json &value) {
std::cout << "* parameter '" << name << "' has unexpected type: "
<< getTypeName(value)
<< " (expected: " << getTypeName<ExpectedType>() << ")\n";
}
template<typename Param>
bool validateArg(const json &request) {
auto value = request.find(Param::name());
if (value == request.end() || value->is_null()) {
if constexpr (isOptional<typename Param::ValueType>)
return true;
std::cout << "* missing parameter '" << Param::name() << "'\n";
return false;
}
if constexpr (isOptional<typename Param::ValueType>) {
using T = typename Param::ValueType::value_type;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
else {
using T = typename Param::ValueType;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
return true;
}
template<typename... Args>
bool validateArgs(const json &request) {
bool isValid = true;
((isValid = validateArg<Args>(request) && isValid), ...);
return isValid;
}
template<typename T>
decltype(auto) getAs(const json *v) {
if constexpr (isOptional<T>) {
return v && !v->is_null() ? T{ getAs<typename T::value_type>(v) } : T{};
}
else
{
if constexpr (std::is_same_v<T, json>)
return *v;
else
return v->get<T>();
}
}
template<typename... Args>
void apply(void (*handler)(Args...), const json &request) {
handler({ [&request]()->decltype(auto) {
auto node = request.find(Args::name());
return getAs<typename Args::ValueType>(node == request.end() ? nullptr : &*node);
}() }...);
}
template<typename P>
void printParamDescription() {
using T = typename P::ValueType;
std::cout << " '" << P::name() << "': ";
if constexpr (isOptional<T>) {
std::cout << getTypeName<typename T::value_type>() << " (optional)\n";
}
else {
std::cout << getTypeName<T>() << '\n';
}
}
struct Handler {
template<typename... Args>
requires (IsParameter<Args>::value && ...)
Handler(void(*handler)(Args...)) : erasedHandler{ reinterpret_cast<void(*)()>(handler) },
handlerImpl{ [](void(*erasedHandler)(), const json &request) {
auto handler = reinterpret_cast<void(*)(Args...)>(erasedHandler);
if constexpr (sizeof...(Args) == 0) {
handler();
}
else {
if (!validateArgs<Args...>(request))
throw std::invalid_argument{ "request arguments are invalid" };
apply(handler, request);
}
} }
,
printParamDescriptionsImpl{ [] {
if constexpr (sizeof...(Args) == 0)
std::cout << " (no arguments)\n";
else
(printParamDescription<Args>(), ...);
} }
{}
void operator()(const json &request) const {
handlerImpl(erasedHandler, request);
}
void printParamDescriptions() const {
printParamDescriptionsImpl();
}
private:
void(*erasedHandler)() = nullptr;
void(*handlerImpl)(void(*erasedHandler)(), const json&) = nullptr;
void(*printParamDescriptionsImpl)() = nullptr;
};
void foo() {
std::cout << "got 'foo' request\n";
}
void bar(Param<"id", std::string_view> s) {
std::cout << "got 'bar' request with\n"
" " << s.name() << "='" << s.value << "'\n";
}
void baz(Param<"count", int> i, Param<"id", std::string_view> s, Param<"payload", json> p) {
std::cout << "got 'baz' request with\n"
" " << i.name() << "='" << i.value << "',\n"
" " << s.name() << "='" << s.value << "', and\n"
" " << p.name() << "=\n"
<< p.value.dump() << "\n";
}
void qux(Param<"count", int> i, Param<"id", std::optional<std::string_view>> s, Param<"payload", json> p) {
std::cout << "got 'qux' request with\n"
" " << i.name() << "='" << i.value << "',\n";
if (s.value) {
std::cout << ' ' << s.name() << "='" << *s.value << "',\n";
}
else {
std::cout << ' ' << s.name() << " parameter is absent,\n";
}
std::cout << " and\n " << p.name() << "=\n"
<< p.value.dump() << "\n";
}
const std::tuple<Name, Handler> handlers[] = {
{ "foo", &foo },
{ "bar", &bar },
{ "baz", &baz },
{ "qux", &qux }
};
void processRequest(const std::string_view &message) {
const auto json = json::parse(message);
if (!json.is_object())
throw std::invalid_argument{ "request is not a valid JSON" };
auto request = json.find("request");
if (request == json.end())
throw std::invalid_argument{ "request does not contain name" };
auto &name = request->get_ref<const json::string_t&>();
std::cout << "* trying to process " << name << '\n';
for (auto handler : handlers) {
if (std::get<Name>(handler) == name) {
std::get<Handler>(handler)(json);
return;
}
}
std::cout << "* could not handle request " << name << '\n';
}
void printDescriptions() {
for (auto handler : handlers) {
std::cout << '\n' << std::get<Name>(handler) << '\n';
std::get<Handler>(handler).printParamDescriptions();
}
}
int main() {
auto request = R"({
"request":"qux",
"count":1,
"id":"two",
"payload":{ "three":3 }
})"sv;
try {
processRequest(request);
}
catch (const std::exception &e) {
std::cout << "error: " << e.what() << '\n';
}
std::cout << "\n\nAPI description:\n";
printDescriptions();
}
Member functions support
taoJSON: godbolt.org/z/x57GbM4Kr
code (tap to show)
#include <iostream>
#include <string>
#include <string_view>
#include <optional>
#include <type_traits>
#include <tao/json.hpp>
using namespace std::literals::string_view_literals;
namespace json = tao::json;
template<typename... F>
struct Overloaded : F... {
using F::operator()...;
};
// deduction guide is needed until
// all compilers fully implement C++20
template<typename... F>
Overloaded(F...)->Overloaded<F...>;
template<typename T>
bool isConvertibleTo(const json::value &value) {
return std::visit(
Overloaded{
[](bool) { return std::is_same_v<T, bool>; },
// worksn't with GCC as of 11.2 and Clang 12 (fixed in 13)
[](std::floating_point auto) { return std::is_floating_point_v<T>; },
[](std::integral auto) { return std::is_arithmetic_v<T>; },
// worksn't at all with GCC as of 11.2
//[]<std::floating_point U>(U) { return std::is_floating_point_v<T>; },
//[]<std::integral U>(U) { return std::is_arithmetic_v<T>; },
[](auto &v) { return std::is_convertible_v<decltype(v), T>; }
},
value.variant());
}
template<typename T>
std::string_view getTypeName() {
if constexpr (std::is_same_v<T, bool>)
return "boolean";
else if constexpr (std::is_integral_v<T>)
return "integer";
else if constexpr (std::is_floating_point_v<T>)
return "floating point number";
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
return "string";
else if constexpr (std::is_same_v<T, json::value::array_t>)
return "array";
else if constexpr (std::is_same_v<T, json::value> || std::is_same_v<T, json::value::object_t>)
return "object";
else
return "unknown type";
}
template<typename Variant>
std::string_view getTypeName(const Variant &v) {
return std::visit([](auto &&v) {
return getTypeName<std::decay_t<decltype(v)>>();
}, v);
}
using Name = std::string_view;
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char(&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template<StringLiteral Name, typename T>
struct Param {
using ValueType = T;
const T &value;
static std::string_view name() { return Name.value; }
};
template<StringLiteral Name, typename T>
struct Param<Name, std::optional<T>> {
using ValueType = std::optional<T>;
std::optional<T> value;
static std::string_view name() { return Name.value; }
};
template<typename T>
struct IsParameter : std::false_type {};
template<StringLiteral N, typename T>
struct IsParameter<Param<N, T>> : std::true_type {};
using Name = std::string_view;
template<typename T>
struct IsOptional : std::false_type {};
template<typename T>
struct IsOptional<std::optional<T>> : std::true_type {};
template<typename T>
inline constexpr bool isOptional = IsOptional<T>::value;
template<typename ExpectedType>
void reportInvalidArg(std::string_view name, const json::value &value) {
std::cout << "* parameter '" << name << "' has unexpected type: "
<< getTypeName(value.variant())
<< " (expected: " << getTypeName<ExpectedType>() << ")\n";
}
template<typename Param>
bool validateArg(const json::value &request) {
auto *value = request.find(Param::name());
if (!value || value->is_null()) {
if constexpr (isOptional<typename Param::ValueType>)
return true;
std::cout << "* missing parameter '" << Param::name() << "'\n";
return false;
}
if constexpr (isOptional<typename Param::ValueType>) {
using T = typename Param::ValueType::value_type;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
else {
using T = typename Param::ValueType;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
return true;
}
template<typename... Args>
bool validateArgs(const json::value &request) {
bool isValid = true;
((isValid = validateArg<Args>(request) && isValid), ...);
return isValid;
}
template<typename T>
decltype(auto) getAs(const json::value *v) {
if constexpr (isOptional<T>) {
return v && !v->is_null() ? T{ getAs<typename T::value_type>(v) } : T{};
}
else
{
if constexpr (std::is_same_v<T, json::value>)
return *v;
else
return v->as<T>();
}
}
template<typename Processor, typename... Args>
void apply(Processor &processor, void (Processor::*handler)(Args...), const json::value &request) {
(processor.*handler)({ getAs<typename Args::ValueType>(request.find(Args::name())) }...);
}
template<typename Processor>
struct Handler {
template<auto handler>
//requires (IsParameter<decltype(args)>::value && ...)
static constexpr Handler makeHandler() {
return makeHandlerImpl<handler>(handler);
}
constexpr Handler() = default;
void operator()(Processor &processor, const json::value &request) const {
handlerImpl(processor, request);
}
private:
constexpr Handler(void(*handlerImpl)(Processor&, const json::value&)) : handlerImpl{ handlerImpl }
{}
template<auto handler, typename... Args>
static constexpr auto makeHandlerImpl(void(Processor::*)(Args...)) {
return Handler{
[](Processor &processor, const json::value &request) {
if constexpr (sizeof...(Args) == 0) {
(processor.*handler)();
}
else {
if (!validateArgs<Args...>(request))
throw std::invalid_argument{ "request arguments are invalid" };
apply(processor, handler, request);
}
}
};
}
void(*handlerImpl)(Processor&, const json::value&) = nullptr;
};
struct Processor {
void foo() {
std::cout << "got 'foo' request\n";
}
void bar(Param<"id", std::string_view> s) {
std::cout << "got 'bar' request with\n"
" " << s.name() << "='" << s.value << "'\n";
}
void baz(Param<"count", int> i, Param<"id", std::string_view> s, Param<"payload", json::value> p) {
std::cout << "got 'baz' request with\n"
" " << i.name() << "='" << i.value << "',\n"
" " << s.name() << "='" << s.value << "', and\n"
" " << p.name() << "=\n"
<< json::to_string(p.value) << "\n";
}
void qux(Param<"count", int> i, Param<"id", std::optional<std::string_view>> s, Param<"payload", json::value> p) {
std::cout << "got 'qux' request with\n"
" " << i.name() << "='" << i.value << "',\n";
if (s.value) {
std::cout << ' ' << s.name() << "='" << *s.value << "',\n";
}
else {
std::cout << ' ' << s.name() << " parameter is absent,\n";
}
std::cout << " and\n " << p.name() << "=\n"
<< json::to_string(p.value) << "\n";
}
};
template<StringLiteral name, auto handler>
constexpr auto h() {
return std::tuple{ Name{ name.value }, Handler<Processor>::makeHandler<handler>() };
}
constexpr std::tuple<Name, Handler<Processor>> handlers[] = {
h<"foo", &Processor::foo>(),
h<"bar", &Processor::bar>(),
h<"baz", &Processor::baz>(),
h<"qux", &Processor::qux>()
};
void processRequest(Processor &processor, const std::string_view &message) {
const auto json = json::from_string(message);
if (!json.is_object())
throw std::invalid_argument{ "request is not a valid JSON" };
auto *request = json.find("request");
if (!request)
throw std::invalid_argument{ "request does not contain name" };
auto &name = request->get_string();
std::cout << "* trying to process " << name << '\n';
for (auto handler : handlers) {
if (std::get<Name>(handler) == name) {
std::get<Handler<Processor>>(handler)(processor, json);
return;
}
}
std::cout << "* could not handle request " << name << '\n';
}
int main() {
auto request = R"({
"request":"qux",
"count":1,
"id":"two",
"payload":{ "three":3 }
})"sv;
Processor processor{};
try {
processRequest(processor, request);
}
catch (const std::exception &e) {
std::cout << "error: " << e.what() << '\n';
}
}
JSON for Modern C++: godbolt.org/z/GT31x5T9a
code (tap to show)
#include <iostream>
#include <string>
#include <string_view>
#include <optional>
#include <type_traits>
#include <nlohmann/json.hpp>
using namespace std::literals::string_view_literals;
using nlohmann::json;
template<typename T>
bool isConvertibleTo(const json &value) {
switch (value.type())
{
case json::value_t::boolean: return std::is_same_v<T, bool>;
case json::value_t::string: return std::is_convertible_v<json::string_t, T>;
case json::value_t::number_unsigned: return std::is_convertible_v<json::number_unsigned_t, T>;
case json::value_t::number_integer: return std::is_convertible_v<json::number_integer_t, T>;
case json::value_t::number_float: return std::is_convertible_v<json::number_float_t, T>;
case json::value_t::object: return std::is_same_v<T, json> || std::is_same_v<T, json::object_t>;
case json::value_t::array: return std::is_same_v<json::array_t, T>;
default: break;
}
return false;
}
template<typename T>
std::string_view getTypeName() {
if constexpr (std::is_same_v<T, bool>)
return "boolean";
else if constexpr (std::is_integral_v<T>)
return "integer";
else if constexpr (std::is_floating_point_v<T>)
return "floating point number";
else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view>)
return "string";
else if constexpr (std::is_same_v<T, json::array_t>)
return "array";
else if constexpr (std::is_same_v<T, json> || std::is_same_v<T, json::object_t>)
return "object";
else
return "unknown type";
}
std::string_view getTypeName(const json &value) {
switch (value.type())
{
case json::value_t::boolean: return getTypeName<bool>();
case json::value_t::string: return getTypeName<json::string_t>();
case json::value_t::number_unsigned: return getTypeName<json::number_unsigned_t>();
case json::value_t::number_integer: return getTypeName<json::number_integer_t>();
case json::value_t::number_float: return getTypeName<json::number_float_t>();
case json::value_t::object: return getTypeName<json>();
case json::value_t::array: return getTypeName<json::array_t>();
default: break;
}
return getTypeName<void>(); // for "unknown type"
}
using Name = std::string_view;
template<size_t N>
struct StringLiteral {
constexpr StringLiteral(const char(&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template<StringLiteral Name, typename T>
struct Param {
using ValueType = T;
const T &value;
static std::string_view name() { return Name.value; }
};
template<StringLiteral Name, typename T>
struct Param<Name, std::optional<T>> {
using ValueType = std::optional<T>;
std::optional<T> value;
static std::string_view name() { return Name.value; }
};
template<typename T>
struct IsParameter : std::false_type {};
template<StringLiteral N, typename T>
struct IsParameter<Param<N, T>> : std::true_type {};
using Name = std::string_view;
template<typename T>
struct IsOptional : std::false_type {};
template<typename T>
struct IsOptional<std::optional<T>> : std::true_type {};
template<typename T>
inline constexpr bool isOptional = IsOptional<T>::value;
template<typename ExpectedType>
void reportInvalidArg(std::string_view name, const json &value) {
std::cout << "* parameter '" << name << "' has unexpected type: "
<< getTypeName(value)
<< " (expected: " << getTypeName<ExpectedType>() << ")\n";
}
template<typename Param>
bool validateArg(const json &request) {
auto value = request.find(Param::name());
if (value == request.end() || value->is_null()) {
if constexpr (isOptional<typename Param::ValueType>)
return true;
std::cout << "* missing parameter '" << Param::name() << "'\n";
return false;
}
if constexpr (isOptional<typename Param::ValueType>) {
using T = typename Param::ValueType::value_type;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
else {
using T = typename Param::ValueType;
if (!isConvertibleTo<T>(*value)) {
reportInvalidArg<T>(Param::name(), *value);
return false;
}
}
return true;
}
template<typename... Args>
bool validateArgs(const json &request) {
bool isValid = true;
((isValid = validateArg<Args>(request) && isValid), ...);
return isValid;
}
template<typename T>
decltype(auto) getAs(const json *v) {
if constexpr (isOptional<T>) {
return v && !v->is_null() ? T{ getAs<typename T::value_type>(v) } : T{};
}
else
{
if constexpr (std::is_same_v<T, json>)
return *v;
else
return v->get<T>();
}
}
template<typename Processor, typename... Args>
void apply(Processor &processor, void (Processor::*handler)(Args...), const json &request) {
(processor.*handler)({ [&request]()->decltype(auto) {
auto node = request.find(Args::name());
return getAs<typename Args::ValueType>(node == request.end() ? nullptr : &*node);
}() }...);
}
template<typename Processor>
struct Handler {
template<auto handler>
//requires (IsParameter<decltype(args)>::value && ...)
static constexpr Handler makeHandler() {
return makeHandlerImpl<handler>(handler);
}
constexpr Handler() = default;
void operator()(Processor &processor, const json &request) const {
handlerImpl(processor, request);
}
private:
constexpr Handler(void(*handlerImpl)(Processor&, const json&)) : handlerImpl{ handlerImpl }
{}
template<auto handler, typename... Args>
static constexpr auto makeHandlerImpl(void(Processor::*)(Args...)) {
return Handler{
[](Processor &processor, const json &request) {
if constexpr (sizeof...(Args) == 0) {
(processor.*handler)();
}
else {
if (!validateArgs<Args...>(request))
throw std::invalid_argument{ "request arguments are invalid" };
apply(processor, handler, request);
}
}
};
}
void(*handlerImpl)(Processor&, const json&) = nullptr;
};
struct Processor {
void foo() {
std::cout << "got 'foo' request\n";
}
void bar(Param<"id", std::string_view> s) {
std::cout << "got 'bar' request with\n"
" " << s.name() << "='" << s.value << "'\n";
}
void baz(Param<"count", int> i, Param<"id", std::string_view> s, Param<"payload", json> p) {
std::cout << "got 'baz' request with\n"
" " << i.name() << "='" << i.value << "',\n"
" " << s.name() << "='" << s.value << "', and\n"
" " << p.name() << "=\n"
<< p.value.dump() << "\n";
}
void qux(Param<"count", int> i, Param<"id", std::optional<std::string_view>> s, Param<"payload", json> p) {
std::cout << "got 'qux' request with\n"
" " << i.name() << "='" << i.value << "',\n";
if (s.value) {
std::cout << ' ' << s.name() << "='" << *s.value << "',\n";
}
else {
std::cout << ' ' << s.name() << " parameter is absent,\n";
}
std::cout << " and\n " << p.name() << "=\n"
<< p.value.dump() << "\n";
}
};
template<StringLiteral name, auto handler>
constexpr auto h() {
return std::tuple{ Name{ name.value }, Handler<Processor>::makeHandler<handler>() };
}
constexpr std::tuple<Name, Handler<Processor>> handlers[] = {
h<"foo", &Processor::foo>(),
h<"bar", &Processor::bar>(),
h<"baz", &Processor::baz>(),
h<"qux", &Processor::qux>()
};
void processRequest(Processor &processor, const std::string_view &message) {
const auto json = json::parse(message);
if (!json.is_object())
throw std::invalid_argument{ "request is not a valid JSON" };
auto request = json.find("request");
if (request == json.end())
throw std::invalid_argument{ "request does not contain name" };
auto &name = request->get_ref<const json::string_t&>();
std::cout << "* trying to process " << name << '\n';
for (auto handler : handlers) {
if (std::get<Name>(handler) == name) {
std::get<Handler<Processor>>(handler)(processor, json);
return;
}
}
std::cout << "* could not handle request " << name << '\n';
}
int main() {
auto request = R"({
"request":"qux",
"count":1,
"id":"two",
"payload":{ "three":3 }
})"sv;
Processor processor{};
try {
processRequest(processor, request);
}
catch (const std::exception &e) {
std::cout << "error: " << e.what() << '\n';
}
}