text: /* 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);
    template <typename F>
    cmJSONObjectHelper& Bind(const cm::string_view& name, 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>
  template <typename F>
  cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
    const cm::string_view& name, F func, bool required)
  {
    return this->BindPrivate(name, MemberFunction(func), 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(std::move(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);
    };
  }
  