jsonrpcx/json-rpc-cxx

Invalid type error when calling a function where one of the types is Json.

Opened this issue · 3 comments

Have a call that is defined in the following manner:

using Json = nlohmann::json;
using JsonPointer = nlohmann::json_pointer;

Json SomeStruct::setProperty(const JsonPointer &pointer, const Json &value)
{
   Json j;
   to_json(j, *this);
   j[pointer] = value;
   from_json(j, *this);
}

It's used in a manner to allow independent values within a class/struct that can be converted to_json(json, SomeStruct) and allow the property to be changed. - So using the Json type as a variant of what is allowed and then converting and reconverting the values to allow the API to identify a single value to change.

The problem I have is when I do the following:

        const std::function<Json(const std::string &, const Json &)> setPropertyFn = [this](const std ::string &pointer,
                                                                                            const Json &value) {
            auto result = m_myStruct->setProperty(JsonPointer(pointer), value);
        };
        addRpc("struct.setProperty", GetHandle(setPropertyFn), {"pointer", "value"});

When I send the following for example:

{"id":8,"jsonrpc":"2.0","method":"struct.setProperty","params":["/type/subtype/subtype2/value",20]}

I get the return of:

{"error":{"code":-32602,"message":"invalid parameter: must be object, but is unsigned integer for parameter \"value\""},"id":8,"jsonrpc":"2.0"}

But there should be no comparison needed as the param_type is already json.

Suggest changing:

template<typename T>
inline void check_param_type...
  • inline void check_param_type(...) { /* No type checks just pass it along */}
  • rest operate as expected.

The following changes to check_param_type fixes this issue:

  template <typename T>
  inline void check_param_type(size_t index, const json &x, json::value_t expectedType, typename std::enable_if<std::is_same<T, json>::value>::type * = 0) {
    // No checking if type is Json - already json.
  }

  template <typename T>
  inline void check_param_type(size_t index, const json &x, json::value_t expectedType,
                               typename std::enable_if<std::is_arithmetic<T>::value && !std::is_same<T, json>::value>::type * = 0) {
    if (expectedType == json::value_t::number_unsigned && x.type() == json::value_t::number_integer) {
      if (x.get<long long int>() < 0)
        throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
    } else if (x.type() == json::value_t::number_unsigned && expectedType == json::value_t::number_integer) {
      if (x.get<long long unsigned>() > (long long unsigned)std::numeric_limits<T>::max()) {
        throw JsonRpcException(invalid_params, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
      }
    } else if ((x.type() == json::value_t::number_unsigned || x.type() == json::value_t::number_integer) && expectedType == json::value_t::number_float) {
      if (static_cast<long long int>(x.get<double>()) != x.get<long long int>()) {
        throw JsonRpcException(invalid_params, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
      }
    } else if (x.type() != expectedType) {
      throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
    }
  }

  template <typename T>
  inline void check_param_type(size_t index, const json &x, json::value_t expectedType,
                               typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_same<T, json>::value>::type * = 0) {
    if (x.type() != expectedType) {
      throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
    }
  }

Also note this is hard to read - suggest improving since you are using C++17 and above:

template <typename T>
inline void check_param_type(size_t index, const json &x, json::value_t expectedType) {
    if constexpr (std::is_same_v<T, json>) {
        // No checking if type is Json - already json.
        return;
    } else if constexpr (std::is_arithmetic_v<T>) {
        if (expectedType == json::value_t::number_unsigned && x.type() == json::value_t::number_integer) {
            if (x.get<long long int>() < 0)
                throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
        } else if (x.type() == json::value_t::number_unsigned && expectedType == json::value_t::number_integer) {
            if (x.get<long long unsigned>() > (long long unsigned)std::numeric_limits<T>::max()) {
                throw JsonRpcException(invalid_params, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
            }
        } else if ((x.type() == json::value_t::number_unsigned || x.type() == json::value_t::number_integer) && expectedType == json::value_t::number_float) {
            if (static_cast<long long int>(x.get<double>()) != x.get<long long int>()) {
                throw JsonRpcException(invalid_params, "invalid parameter: exceeds value range of " + type_name(expectedType), index);
            }
        } else if (x.type() != expectedType) {
            throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
        }
    } else {
        if (x.type() != expectedType) {
            throw JsonRpcException(invalid_params, "invalid parameter: must be " + type_name(expectedType) + ", but is " + type_name(x.type()), index);
        }
    }
}

Adding Patch File: Cannot push a branch:

fix51.patch