Added Value::findType with String key (#1574)

This adds a convenience function to return a member if it has a specific
json type. All isType values are supported.

Co-authored-by: Jordan Bayles <bayles.jordan@gmail.com>
diff --git a/include/json/value.h b/include/json/value.h
index 3074932..5f65443 100644
--- a/include/json/value.h
+++ b/include/json/value.h
@@ -549,6 +549,29 @@
   /// Most general and efficient version of isMember()const, get()const,
   /// and operator[]const
   Value const* find(const String& key) const;
+
+  /// Calls find and only returns a valid pointer if the type is found
+  template <typename T, bool (T::*TMemFn)() const>
+  Value const* findValue(const String& key) const {
+    Value const* found = find(key);
+    if (!found || !(found->*TMemFn)())
+      return nullptr;
+    return found;
+  }
+
+  Value const* findNull(const String& key) const;
+  Value const* findBool(const String& key) const;
+  Value const* findInt(const String& key) const;
+  Value const* findInt64(const String& key) const;
+  Value const* findUInt(const String& key) const;
+  Value const* findUInt64(const String& key) const;
+  Value const* findIntegral(const String& key) const;
+  Value const* findDouble(const String& key) const;
+  Value const* findNumeric(const String& key) const;
+  Value const* findString(const String& key) const;
+  Value const* findArray(const String& key) const;
+  Value const* findObject(const String& key) const;
+
   /// Most general and efficient version of object-mutators.
   /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30
   /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue.
diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp
index 527d716..a875d28 100644
--- a/src/lib_json/json_value.cpp
+++ b/src/lib_json/json_value.cpp
@@ -1129,6 +1129,44 @@
 Value const* Value::find(const String& key) const {
   return find(key.data(), key.data() + key.length());
 }
+
+Value const* Value::findNull(const String& key) const {
+  return findValue<Value, &Value::isNull>(key);
+}
+Value const* Value::findBool(const String& key) const {
+  return findValue<Value, &Value::isBool>(key);
+}
+Value const* Value::findInt(const String& key) const {
+  return findValue<Value, &Value::isInt>(key);
+}
+Value const* Value::findInt64(const String& key) const {
+  return findValue<Value, &Value::isInt64>(key);
+}
+Value const* Value::findUInt(const String& key) const {
+  return findValue<Value, &Value::isUInt>(key);
+}
+Value const* Value::findUInt64(const String& key) const {
+  return findValue<Value, &Value::isUInt64>(key);
+}
+Value const* Value::findIntegral(const String& key) const {
+  return findValue<Value, &Value::isIntegral>(key);
+}
+Value const* Value::findDouble(const String& key) const {
+  return findValue<Value, &Value::isDouble>(key);
+}
+Value const* Value::findNumeric(const String& key) const {
+  return findValue<Value, &Value::isNumeric>(key);
+}
+Value const* Value::findString(const String& key) const {
+  return findValue<Value, &Value::isString>(key);
+}
+Value const* Value::findArray(const String& key) const {
+  return findValue<Value, &Value::isArray>(key);
+}
+Value const* Value::findObject(const String& key) const {
+  return findValue<Value, &Value::isObject>(key);
+}
+
 Value* Value::demand(char const* begin, char const* end) {
   JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue,
                       "in Json::Value::demand(begin, end): requires "
diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp
index 5a0ce01..60f149d 100644
--- a/src/test_lib_json/main.cpp
+++ b/src/test_lib_json/main.cpp
@@ -76,6 +76,8 @@
   Json::Value float_{0.00390625f};
   Json::Value array1_;
   Json::Value object1_;
+  Json::Value object2_;
+  Json::Value object3_;
   Json::Value emptyString_{""};
   Json::Value string1_{"a"};
   Json::Value string_{"sometext with space"};
@@ -85,6 +87,34 @@
   ValueTest() {
     array1_.append(1234);
     object1_["id"] = 1234;
+
+    // object2 with matching values
+    object2_["null"] = Json::nullValue;
+    object2_["bool"] = true;
+    object2_["int"] = Json::Int{Json::Value::maxInt};
+    object2_["int64"] = Json::Int64{Json::Value::maxInt64};
+    object2_["uint"] = Json::UInt{Json::Value::maxUInt};
+    object2_["uint64"] = Json::UInt64{Json::Value::maxUInt64};
+    object2_["integral"] = 1234;
+    object2_["double"] = 1234.56789;
+    object2_["numeric"] = 0.12345f;
+    object2_["string"] = "string";
+    object2_["array"] = Json::arrayValue;
+    object2_["object"] = Json::objectValue;
+
+    // object3 with not matching values
+    object3_["object"] = Json::nullValue;
+    object3_["null"] = true;
+    object3_["bool"] = Json::Int{Json::Value::maxInt};
+    object3_["int"] = "not_an_int";
+    object3_["int64"] = "not_an_int64";
+    object3_["uint"] = "not_an_uint";
+    object3_["uin64"] = "not_an_uint64";
+    object3_["integral"] = 1234.56789;
+    object3_["double"] = false;
+    object3_["numeric"] = "string";
+    object3_["string"] = Json::arrayValue;
+    object3_["array"] = Json::objectValue;
   }
 
   struct IsCheck {
@@ -234,6 +264,62 @@
   const Json::Value* stringFoundUnknownId = object1_.find(stringUnknownIdKey);
   JSONTEST_ASSERT_EQUAL(nullptr, stringFoundUnknownId);
 
+  // Access through find<Type>()
+  const Json::Value* nullFound = object2_.findNull("null");
+  JSONTEST_ASSERT(nullFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(Json::nullValue, *nullFound);
+  JSONTEST_ASSERT(object3_.findNull("null") == nullptr);
+
+  const Json::Value* boolFound = object2_.findBool("bool");
+  JSONTEST_ASSERT(boolFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(true, *boolFound);
+  JSONTEST_ASSERT(object3_.findBool("bool") == nullptr);
+
+  const Json::Value* intFound = object2_.findInt("int");
+  JSONTEST_ASSERT(intFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(Json::Int{Json::Value::maxInt}, *intFound);
+  JSONTEST_ASSERT(object3_.findInt("int") == nullptr);
+
+  const Json::Value* int64Found = object2_.findInt64("int64");
+  JSONTEST_ASSERT(int64Found != nullptr);
+  JSONTEST_ASSERT_EQUAL(Json::Int64{Json::Value::maxInt64}, *int64Found);
+  JSONTEST_ASSERT(object3_.findInt64("int64") == nullptr);
+
+  const Json::Value* uintFound = object2_.findUInt("uint");
+  JSONTEST_ASSERT(uintFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(Json::UInt{Json::Value::maxUInt}, *uintFound);
+  JSONTEST_ASSERT(object3_.findUInt("uint") == nullptr);
+
+  const Json::Value* uint64Found = object2_.findUInt64("uint64");
+  JSONTEST_ASSERT(uint64Found != nullptr);
+  JSONTEST_ASSERT_EQUAL(Json::UInt64{Json::Value::maxUInt64}, *uint64Found);
+  JSONTEST_ASSERT(object3_.findUInt64("uint64") == nullptr);
+
+  const Json::Value* integralFound = object2_.findIntegral("integral");
+  JSONTEST_ASSERT(integralFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(1234, *integralFound);
+  JSONTEST_ASSERT(object3_.findIntegral("integral") == nullptr);
+
+  const Json::Value* doubleFound = object2_.findDouble("double");
+  JSONTEST_ASSERT(doubleFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(1234.56789, *doubleFound);
+  JSONTEST_ASSERT(object3_.findDouble("double") == nullptr);
+
+  const Json::Value* numericFound = object2_.findNumeric("numeric");
+  JSONTEST_ASSERT(numericFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(0.12345f, *numericFound);
+  JSONTEST_ASSERT(object3_.findNumeric("numeric") == nullptr);
+
+  const Json::Value* stringFound = object2_.findString("string");
+  JSONTEST_ASSERT(stringFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(std::string{"string"}, *stringFound);
+  JSONTEST_ASSERT(object3_.findString("string") == nullptr);
+
+  const Json::Value* arrayFound = object2_.findArray("array");
+  JSONTEST_ASSERT(arrayFound != nullptr);
+  JSONTEST_ASSERT_EQUAL(Json::arrayValue, *arrayFound);
+  JSONTEST_ASSERT(object3_.findArray("array") == nullptr);
+
   // Access through demand()
   const char yetAnotherIdKey[] = "yet another id";
   const Json::Value* foundYetAnotherId =