Explorar o código

JSON: Add helpers

Kyle Edwards %!s(int64=5) %!d(string=hai) anos
pai
achega
3f3a30e1e0

+ 1 - 0
Source/CMakeLists.txt

@@ -336,6 +336,7 @@ set(SRCS
   cmInstallTargetGenerator.cxx
   cmInstallDirectoryGenerator.h
   cmInstallDirectoryGenerator.cxx
+  cmJSONHelpers.h
   cmLDConfigLDConfigTool.cxx
   cmLDConfigLDConfigTool.h
   cmLDConfigTool.cxx

+ 304 - 0
Source/cmJSONHelpers.h

@@ -0,0 +1,304 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#pragma once
+
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+#include <cm/string_view>
+
+#include <cm3p/json/value.h>
+
+template <typename T, typename E>
+using cmJSONHelper = std::function<E(T& out, const Json::Value* value)>;
+
+template <typename T, typename E>
+class cmJSONObjectHelper
+{
+public:
+  cmJSONObjectHelper(E&& success, E&& fail, bool allowExtra = true);
+
+  template <typename U, typename M, typename F>
+  cmJSONObjectHelper& Bind(const cm::string_view& name, M U::*member, F func,
+                           bool required = true);
+  template <typename M, typename F>
+  cmJSONObjectHelper& Bind(const cm::string_view& name, std::nullptr_t, F func,
+                           bool required = true);
+
+  E operator()(T& out, const Json::Value* value) const;
+
+private:
+  // Not a true cmJSONHelper, it just happens to match the signature
+  using MemberFunction = std::function<E(T& out, const Json::Value* value)>;
+  struct Member
+  {
+    cm::string_view Name;
+    MemberFunction Function;
+    bool Required;
+  };
+  std::vector<Member> Members;
+  bool AnyRequired = false;
+  E Success;
+  E Fail;
+  bool AllowExtra;
+
+  cmJSONObjectHelper& BindPrivate(const cm::string_view& name,
+                                  MemberFunction&& func, bool required);
+};
+
+template <typename T, typename E>
+cmJSONObjectHelper<T, E>::cmJSONObjectHelper(E&& success, E&& fail,
+                                             bool allowExtra)
+  : Success(std::move(success))
+  , Fail(std::move(fail))
+  , AllowExtra(allowExtra)
+{
+}
+
+template <typename T, typename E>
+template <typename U, typename M, typename F>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
+  const cm::string_view& name, M U::*member, F func, bool required)
+{
+  return this->BindPrivate(
+    name,
+    [func, member](T& out, const Json::Value* value) -> E {
+      return func(out.*member, value);
+    },
+    required);
+}
+
+template <typename T, typename E>
+template <typename M, typename F>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
+  const cm::string_view& name, std::nullptr_t, F func, bool required)
+{
+  return this->BindPrivate(name,
+                           [func](T& /*out*/, const Json::Value* value) -> E {
+                             M dummy;
+                             return func(dummy, value);
+                           },
+                           required);
+}
+
+template <typename T, typename E>
+cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::BindPrivate(
+  const cm::string_view& name, MemberFunction&& func, bool required)
+{
+  Member m;
+  m.Name = name;
+  m.Function = std::move(func);
+  m.Required = required;
+  this->Members.push_back(std::move(m));
+  if (required) {
+    this->AnyRequired = true;
+  }
+  return *this;
+}
+
+template <typename T, typename E>
+E cmJSONObjectHelper<T, E>::operator()(T& out, const Json::Value* value) const
+{
+  if (!value && this->AnyRequired) {
+    return this->Fail;
+  }
+  if (value && !value->isObject()) {
+    return this->Fail;
+  }
+  Json::Value::Members extraFields;
+  if (value) {
+    extraFields = value->getMemberNames();
+  }
+
+  for (auto const& m : this->Members) {
+    std::string name(m.Name.data(), m.Name.size());
+    if (value && value->isMember(name)) {
+      E result = m.Function(out, &(*value)[name]);
+      if (result != this->Success) {
+        return result;
+      }
+      extraFields.erase(
+        std::find(extraFields.begin(), extraFields.end(), name));
+    } else if (!m.Required) {
+      E result = m.Function(out, nullptr);
+      if (result != this->Success) {
+        return result;
+      }
+    } else {
+      return this->Fail;
+    }
+  }
+
+  return this->AllowExtra || extraFields.empty() ? this->Success : this->Fail;
+}
+
+template <typename E>
+cmJSONHelper<std::string, E> cmJSONStringHelper(E success, E fail,
+                                                const std::string& defval = "")
+{
+  return
+    [success, fail, defval](std::string& out, const Json::Value* value) -> E {
+      if (!value) {
+        out = defval;
+        return success;
+      }
+      if (!value->isString()) {
+        return fail;
+      }
+      out = value->asString();
+      return success;
+    };
+}
+
+template <typename E>
+cmJSONHelper<int, E> cmJSONIntHelper(E success, E fail, int defval = 0)
+{
+  return [success, fail, defval](int& out, const Json::Value* value) -> E {
+    if (!value) {
+      out = defval;
+      return success;
+    }
+    if (!value->isInt()) {
+      return fail;
+    }
+    out = value->asInt();
+    return success;
+  };
+}
+
+template <typename E>
+cmJSONHelper<unsigned int, E> cmJSONUIntHelper(E success, E fail,
+                                               unsigned int defval = 0)
+{
+  return
+    [success, fail, defval](unsigned int& out, const Json::Value* value) -> E {
+      if (!value) {
+        out = defval;
+        return success;
+      }
+      if (!value->isUInt()) {
+        return fail;
+      }
+      out = value->asUInt();
+      return success;
+    };
+}
+
+template <typename E>
+cmJSONHelper<bool, E> cmJSONBoolHelper(E success, E fail, bool defval = false)
+{
+  return [success, fail, defval](bool& out, const Json::Value* value) -> E {
+    if (!value) {
+      out = defval;
+      return success;
+    }
+    if (!value->isBool()) {
+      return fail;
+    }
+    out = value->asBool();
+    return success;
+  };
+}
+
+template <typename T, typename E, typename F, typename Filter>
+cmJSONHelper<std::vector<T>, E> cmJSONVectorFilterHelper(E success, E fail,
+                                                         F func, Filter filter)
+{
+  return [success, fail, func, filter](std::vector<T>& out,
+                                       const Json::Value* value) -> E {
+    if (!value) {
+      out.clear();
+      return success;
+    }
+    if (!value->isArray()) {
+      return fail;
+    }
+    out.clear();
+    for (auto const& item : *value) {
+      T t;
+      E result = func(t, &item);
+      if (result != success) {
+        return result;
+      }
+      if (!filter(t)) {
+        continue;
+      }
+      out.push_back(t);
+    }
+    return success;
+  };
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<std::vector<T>, E> cmJSONVectorHelper(E success, E fail, F func)
+{
+  return cmJSONVectorFilterHelper<T, E, F>(success, fail, func,
+                                           [](const T&) { return true; });
+}
+
+template <typename T, typename E, typename F, typename Filter>
+cmJSONHelper<std::map<std::string, T>, E> cmJSONMapFilterHelper(E success,
+                                                                E fail, F func,
+                                                                Filter filter)
+{
+  return [success, fail, func, filter](std::map<std::string, T>& out,
+                                       const Json::Value* value) -> E {
+    if (!value) {
+      out.clear();
+      return success;
+    }
+    if (!value->isObject()) {
+      return fail;
+    }
+    out.clear();
+    for (auto const& key : value->getMemberNames()) {
+      if (!filter(key)) {
+        continue;
+      }
+      T t;
+      E result = func(t, &(*value)[key]);
+      if (result != success) {
+        return result;
+      }
+      out[key] = std::move(t);
+    }
+    return success;
+  };
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<std::map<std::string, T>, E> cmJSONMapHelper(E success, E fail,
+                                                          F func)
+{
+  return cmJSONMapFilterHelper<T, E, F>(
+    success, fail, func, [](const std::string&) { return true; });
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<cm::optional<T>, E> cmJSONOptionalHelper(E success, F func)
+{
+  return [success, func](cm::optional<T>& out, const Json::Value* value) -> E {
+    if (!value) {
+      out.reset();
+      return success;
+    }
+    out.emplace();
+    return func(*out, value);
+  };
+}
+
+template <typename T, typename E, typename F>
+cmJSONHelper<T, E> cmJSONRequiredHelper(E fail, F func)
+{
+  return [fail, func](T& out, const Json::Value* value) -> E {
+    if (!value) {
+      return fail;
+    }
+    return func(out, value);
+  };
+}

+ 1 - 0
Tests/CMakeLib/CMakeLists.txt

@@ -13,6 +13,7 @@ set(CMakeLib_TESTS
   testCTestResourceGroups.cxx
   testGccDepfileReader.cxx
   testGeneratedFileStream.cxx
+  testJSONHelpers.cxx
   testRST.cxx
   testRange.cxx
   testOptional.cxx

+ 493 - 0
Tests/CMakeLib/testJSONHelpers.cxx

@@ -0,0 +1,493 @@
+#include <functional>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <cm/optional>
+#include <cmext/string_view>
+
+#include <cm3p/json/value.h>
+
+#include "cmJSONHelpers.h"
+
+#define ASSERT_TRUE(x)                                                        \
+  do {                                                                        \
+    if (!(x)) {                                                               \
+      std::cout << "ASSERT_TRUE(" #x ") failed on line " << __LINE__ << "\n"; \
+      return false;                                                           \
+    }                                                                         \
+  } while (false)
+
+namespace {
+struct ObjectStruct
+{
+  std::string Field1;
+  int Field2;
+};
+
+struct InheritedStruct : public ObjectStruct
+{
+  std::string Field3;
+};
+
+enum class ErrorCode
+{
+  Success,
+  InvalidInt,
+  InvalidBool,
+  InvalidString,
+  InvalidObject,
+  InvalidArray,
+  MissingRequired,
+};
+
+auto const IntHelper =
+  cmJSONIntHelper<ErrorCode>(ErrorCode::Success, ErrorCode::InvalidInt, 1);
+auto const RequiredIntHelper =
+  cmJSONRequiredHelper<int, ErrorCode>(ErrorCode::MissingRequired, IntHelper);
+auto const UIntHelper =
+  cmJSONUIntHelper<ErrorCode>(ErrorCode::Success, ErrorCode::InvalidInt, 1);
+auto const BoolHelper = cmJSONBoolHelper<ErrorCode>(
+  ErrorCode::Success, ErrorCode::InvalidBool, false);
+auto const StringHelper = cmJSONStringHelper<ErrorCode>(
+  ErrorCode::Success, ErrorCode::InvalidString, "default");
+auto const RequiredStringHelper = cmJSONRequiredHelper<std::string, ErrorCode>(
+  ErrorCode::MissingRequired, StringHelper);
+auto const StringVectorHelper = cmJSONVectorHelper<std::string, ErrorCode>(
+  ErrorCode::Success, ErrorCode::InvalidArray, StringHelper);
+auto const StringVectorFilterHelper =
+  cmJSONVectorFilterHelper<std::string, ErrorCode>(
+    ErrorCode::Success, ErrorCode::InvalidArray, StringHelper,
+    [](const std::string& value) { return value != "ignore"; });
+auto const StringMapHelper = cmJSONMapHelper<std::string, ErrorCode>(
+  ErrorCode::Success, ErrorCode::InvalidObject, StringHelper);
+auto const StringMapFilterHelper =
+  cmJSONMapFilterHelper<std::string, ErrorCode>(
+    ErrorCode::Success, ErrorCode::InvalidObject, StringHelper,
+    [](const std::string& key) { return key != "ignore"; });
+auto const OptionalStringHelper =
+  cmJSONOptionalHelper<std::string>(ErrorCode::Success, StringHelper);
+
+bool testInt()
+{
+  Json::Value v(2);
+  int i = 0;
+  ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(i == 2);
+
+  i = 0;
+  v = Json::nullValue;
+  ASSERT_TRUE(IntHelper(i, &v) == ErrorCode::InvalidInt);
+
+  i = 0;
+  ASSERT_TRUE(IntHelper(i, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(i == 1);
+
+  return true;
+}
+
+bool testUInt()
+{
+  Json::Value v(2);
+  unsigned int i = 0;
+  ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(i == 2);
+
+  i = 0;
+  v = Json::nullValue;
+  ASSERT_TRUE(UIntHelper(i, &v) == ErrorCode::InvalidInt);
+
+  i = 0;
+  ASSERT_TRUE(UIntHelper(i, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(i == 1);
+
+  return true;
+}
+
+bool testBool()
+{
+  Json::Value v(true);
+  bool b = false;
+  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success);
+  ASSERT_TRUE(b);
+
+  b = false;
+  v = false;
+  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::Success);
+  ASSERT_TRUE(!b);
+
+  b = false;
+  v = 4;
+  ASSERT_TRUE(BoolHelper(b, &v) == ErrorCode::InvalidBool);
+
+  b = true;
+  ASSERT_TRUE(BoolHelper(b, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(!b);
+
+  return true;
+}
+
+bool testString()
+{
+  Json::Value v("str");
+  std::string str = "";
+  ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(str == "str");
+
+  str = "";
+  v = Json::nullValue;
+  ASSERT_TRUE(StringHelper(str, &v) == ErrorCode::InvalidString);
+
+  str = "";
+  ASSERT_TRUE(StringHelper(str, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(str == "default");
+
+  return true;
+}
+
+bool testObject()
+{
+  auto const helper = cmJSONObjectHelper<ObjectStruct, ErrorCode>(
+                        ErrorCode::Success, ErrorCode::InvalidObject)
+                        .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
+                        .Bind("field2"_s, &ObjectStruct::Field2, IntHelper)
+                        .Bind<std::string>("field3"_s, nullptr, StringHelper);
+
+  Json::Value v(Json::objectValue);
+  v["field1"] = "Hello";
+  v["field2"] = 2;
+  v["field3"] = "world!";
+  v["extra"] = "extra";
+
+  ObjectStruct s1;
+  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(s1.Field1 == "Hello");
+  ASSERT_TRUE(s1.Field2 == 2);
+
+  v["field2"] = "wrong";
+  ObjectStruct s2;
+  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
+
+  v.removeMember("field2");
+  ObjectStruct s3;
+  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject);
+
+  v["field2"] = 2;
+  v["field3"] = 3;
+  ObjectStruct s4;
+  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString);
+
+  v.removeMember("field3");
+  ObjectStruct s5;
+  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject);
+
+  v = "Hello";
+  ObjectStruct s6;
+  ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
+
+  ObjectStruct s7;
+  ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject);
+
+  return true;
+}
+
+bool testObjectInherited()
+{
+  auto const helper =
+    cmJSONObjectHelper<InheritedStruct, ErrorCode>(ErrorCode::Success,
+                                                   ErrorCode::InvalidObject)
+      .Bind("field1"_s, &InheritedStruct::Field1, StringHelper)
+      .Bind("field2"_s, &InheritedStruct::Field2, IntHelper)
+      .Bind("field3"_s, &InheritedStruct::Field3, StringHelper);
+
+  Json::Value v(Json::objectValue);
+  v["field1"] = "Hello";
+  v["field2"] = 2;
+  v["field3"] = "world!";
+  v["extra"] = "extra";
+
+  InheritedStruct s1;
+  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(s1.Field1 == "Hello");
+  ASSERT_TRUE(s1.Field2 == 2);
+  ASSERT_TRUE(s1.Field3 == "world!");
+
+  v["field2"] = "wrong";
+  InheritedStruct s2;
+  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidInt);
+
+  v.removeMember("field2");
+  InheritedStruct s3;
+  ASSERT_TRUE(helper(s3, &v) == ErrorCode::InvalidObject);
+
+  v["field2"] = 2;
+  v["field3"] = 3;
+  InheritedStruct s4;
+  ASSERT_TRUE(helper(s4, &v) == ErrorCode::InvalidString);
+
+  v.removeMember("field3");
+  InheritedStruct s5;
+  ASSERT_TRUE(helper(s5, &v) == ErrorCode::InvalidObject);
+
+  v = "Hello";
+  InheritedStruct s6;
+  ASSERT_TRUE(helper(s6, &v) == ErrorCode::InvalidObject);
+
+  InheritedStruct s7;
+  ASSERT_TRUE(helper(s7, nullptr) == ErrorCode::InvalidObject);
+
+  return true;
+}
+
+bool testObjectNoExtra()
+{
+  auto const helper = cmJSONObjectHelper<ObjectStruct, ErrorCode>(
+                        ErrorCode::Success, ErrorCode::InvalidObject, false)
+                        .Bind("field1"_s, &ObjectStruct::Field1, StringHelper)
+                        .Bind("field2"_s, &ObjectStruct::Field2, IntHelper);
+
+  Json::Value v(Json::objectValue);
+  v["field1"] = "Hello";
+  v["field2"] = 2;
+
+  ObjectStruct s1;
+  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(s1.Field1 == "Hello");
+  ASSERT_TRUE(s1.Field2 == 2);
+
+  v["extra"] = "world!";
+  ObjectStruct s2;
+  ASSERT_TRUE(helper(s2, &v) == ErrorCode::InvalidObject);
+
+  return true;
+}
+
+bool testObjectOptional()
+{
+  auto const helper =
+    cmJSONObjectHelper<ObjectStruct, ErrorCode>(ErrorCode::Success,
+                                                ErrorCode::InvalidObject)
+      .Bind("field1"_s, &ObjectStruct::Field1, StringHelper, false)
+      .Bind("field2"_s, &ObjectStruct::Field2, IntHelper, false)
+      .Bind<std::string>("field3_s", nullptr, StringHelper, false);
+
+  Json::Value v(Json::objectValue);
+  v["field1"] = "Hello";
+  v["field2"] = 2;
+  v["field3"] = "world!";
+  v["extra"] = "extra";
+
+  ObjectStruct s1;
+  ASSERT_TRUE(helper(s1, &v) == ErrorCode::Success);
+  ASSERT_TRUE(s1.Field1 == "Hello");
+  ASSERT_TRUE(s1.Field2 == 2);
+
+  v = Json::objectValue;
+  ObjectStruct s2;
+  ASSERT_TRUE(helper(s2, &v) == ErrorCode::Success);
+  ASSERT_TRUE(s2.Field1 == "default");
+  ASSERT_TRUE(s2.Field2 == 1);
+
+  ObjectStruct s3;
+  ASSERT_TRUE(helper(s3, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(s3.Field1 == "default");
+  ASSERT_TRUE(s3.Field2 == 1);
+
+  return true;
+}
+
+bool testVector()
+{
+  Json::Value v(Json::arrayValue);
+  v.append("Hello");
+  v.append("world!");
+  v.append("ignore");
+
+  std::vector<std::string> l{ "default" };
+  std::vector<std::string> expected{ "Hello", "world!", "ignore" };
+  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::Success);
+  ASSERT_TRUE(l == expected);
+
+  v[1] = 2;
+  l = { "default" };
+  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidString);
+
+  v = "Hello";
+  l = { "default" };
+  ASSERT_TRUE(StringVectorHelper(l, &v) == ErrorCode::InvalidArray);
+
+  l = { "default" };
+  ASSERT_TRUE(StringVectorHelper(l, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(l.empty());
+
+  return true;
+}
+
+bool testVectorFilter()
+{
+  Json::Value v(Json::arrayValue);
+  v.append("Hello");
+  v.append("world!");
+  v.append("ignore");
+
+  std::vector<std::string> l{ "default" };
+  std::vector<std::string> expected{
+    "Hello",
+    "world!",
+  };
+  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::Success);
+  ASSERT_TRUE(l == expected);
+
+  v[1] = 2;
+  l = { "default" };
+  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidString);
+
+  v = "Hello";
+  l = { "default" };
+  ASSERT_TRUE(StringVectorFilterHelper(l, &v) == ErrorCode::InvalidArray);
+
+  l = { "default" };
+  ASSERT_TRUE(StringVectorFilterHelper(l, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(l.empty());
+
+  return true;
+}
+
+bool testMap()
+{
+  Json::Value v(Json::objectValue);
+  v["field1"] = "Hello";
+  v["field2"] = "world!";
+  v["ignore"] = "ignore";
+
+  std::map<std::string, std::string> m{ { "key", "default" } };
+  std::map<std::string, std::string> expected{ { "field1", "Hello" },
+                                               { "field2", "world!" },
+                                               { "ignore", "ignore" } };
+  ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::Success);
+  ASSERT_TRUE(m == expected);
+
+  v = Json::arrayValue;
+  m = { { "key", "default" } };
+  ASSERT_TRUE(StringMapHelper(m, &v) == ErrorCode::InvalidObject);
+
+  m = { { "key", "default" } };
+  ASSERT_TRUE(StringMapHelper(m, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(m.empty());
+
+  return true;
+}
+
+bool testMapFilter()
+{
+  Json::Value v(Json::objectValue);
+  v["field1"] = "Hello";
+  v["field2"] = "world!";
+  v["ignore"] = "ignore";
+
+  std::map<std::string, std::string> m{ { "key", "default" } };
+  std::map<std::string, std::string> expected{ { "field1", "Hello" },
+                                               { "field2", "world!" } };
+  ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::Success);
+  ASSERT_TRUE(m == expected);
+
+  v = Json::arrayValue;
+  m = { { "key", "default" } };
+  ASSERT_TRUE(StringMapFilterHelper(m, &v) == ErrorCode::InvalidObject);
+
+  m = { { "key", "default" } };
+  ASSERT_TRUE(StringMapFilterHelper(m, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(m.empty());
+
+  return true;
+}
+
+bool testOptional()
+{
+  Json::Value v = "Hello";
+
+  cm::optional<std::string> str{ "default" };
+  ASSERT_TRUE(OptionalStringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(str == "Hello");
+
+  str.emplace("default");
+  ASSERT_TRUE(OptionalStringHelper(str, nullptr) == ErrorCode::Success);
+  ASSERT_TRUE(str == cm::nullopt);
+
+  return true;
+}
+
+bool testRequired()
+{
+  Json::Value v = "Hello";
+
+  std::string str = "default";
+  int i = 1;
+  ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::Success);
+  ASSERT_TRUE(str == "Hello");
+  ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::InvalidInt);
+
+  v = 2;
+  str = "default";
+  i = 1;
+  ASSERT_TRUE(RequiredStringHelper(str, &v) == ErrorCode::InvalidString);
+  ASSERT_TRUE(RequiredIntHelper(i, &v) == ErrorCode::Success);
+  ASSERT_TRUE(i == 2);
+
+  str = "default";
+  i = 1;
+  ASSERT_TRUE(RequiredStringHelper(str, nullptr) ==
+              ErrorCode::MissingRequired);
+  ASSERT_TRUE(RequiredIntHelper(i, nullptr) == ErrorCode::MissingRequired);
+
+  return true;
+}
+}
+
+int testJSONHelpers(int /*unused*/, char* /*unused*/ [])
+{
+  if (!testInt()) {
+    return 1;
+  }
+  if (!testUInt()) {
+    return 1;
+  }
+  if (!testBool()) {
+    return 1;
+  }
+  if (!testString()) {
+    return 1;
+  }
+  if (!testObject()) {
+    return 1;
+  }
+  if (!testObjectInherited()) {
+    return 1;
+  }
+  if (!testObjectNoExtra()) {
+    return 1;
+  }
+  if (!testObjectOptional()) {
+    return 1;
+  }
+  if (!testVector()) {
+    return 1;
+  }
+  if (!testVectorFilter()) {
+    return 1;
+  }
+  if (!testMap()) {
+    return 1;
+  }
+  if (!testMapFilter()) {
+    return 1;
+  }
+  if (!testOptional()) {
+    return 1;
+  }
+  if (!testRequired()) {
+    return 1;
+  }
+  return 0;
+}