View on GitHub

talks

home|C++ Russia 2021

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>;
  defaultbreak;
  }
  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>();
  defaultbreak;
  }
  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>;
  defaultbreak;
  }
  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>();
  defaultbreak;
  }
  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>;
  defaultbreak;
  }
  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>();
  defaultbreak;
  }
  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';
  }
}