feat: support std::string_view in Value API (#1584)

This adds direct support for `std::string_view` when available (C++17
and above). The current API can be used with `std::string_view` via the
low-level two-pointer methods, but is not ergonomic. E.g., compare:

```
Json::Value node;
std::string foo, bar, baz;
std::string_view foo_sv, bar_sv, baz_sv;

// Efficient & readable:
node[foo][bar][baz];
// Less efficient, less readable:
node[std::string(foo_sv)][std::string(bar_sv)][std::string(baz_sv)];
// Efficient, but a lot less readable:
*node.demand(foo_sv.data(), foo_sv.data() + foo_sv.size())
    ->demand(bar_sv.data(), bar_sv.data() + bar_sv.size())
    ->demand(baz_sv.data(), baz_sv.data() + baz_sv.size())
// After this change, efficient & readable:
node[foo_sv][bar_sv][baz_sv];
```

*   The constructor can take a `std::string_view` parameter. The existing
    overloads taking `const std::string&` and `const char*` are still necessary
    to support assignment from those types.
*   `operator[]`, `get()`, `isMember()` and `removeMember()` take a
    `std::string_view` parameter. This supersedes the overloads taking
    `const std::string&` and `const char*`. The overloads taking a pair of
    pointers (begin, end) are preserved for source compatibility.
*   `getString()` has an overload with a `std::string_view` output parameter.
    The one with a pair of pointers is preserved for source compatibility.

Signed-off-by: Lev Kandel <lmakhlis@google.com>
Co-authored-by: Jordan Bayles <bayles.jordan@gmail.com>
diff --git a/include/json/value.h b/include/json/value.h
index 073ed30..3074932 100644
--- a/include/json/value.h
+++ b/include/json/value.h
@@ -39,6 +39,10 @@
 #endif
 #endif
 
+#if __cplusplus >= 201703L
+#define JSONCPP_HAS_STRING_VIEW 1
+#endif
+
 #include <array>
 #include <exception>
 #include <map>
@@ -46,6 +50,10 @@
 #include <string>
 #include <vector>
 
+#ifdef JSONCPP_HAS_STRING_VIEW
+#include <string_view>
+#endif
+
 // Disable warning C4251: <data member>: <type> needs to have dll-interface to
 // be used by...
 #if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING)
@@ -342,6 +350,9 @@
    */
   Value(const StaticString& value);
   Value(const String& value);
+#ifdef JSONCPP_HAS_STRING_VIEW
+  Value(std::string_view value);
+#endif
   Value(bool value);
   Value(std::nullptr_t ptr) = delete;
   Value(const Value& other);
@@ -384,6 +395,12 @@
    *  \return false if !string. (Seg-fault if str or end are NULL.)
    */
   bool getString(char const** begin, char const** end) const;
+#ifdef JSONCPP_HAS_STRING_VIEW
+  /** Get string_view of string-value.
+   *  \return false if !string. (Seg-fault if str is NULL.)
+   */
+  bool getString(std::string_view* str) const;
+#endif
   Int asInt() const;
   UInt asUInt() const;
 #if defined(JSON_HAS_INT64)
@@ -470,6 +487,15 @@
   bool insert(ArrayIndex index, const Value& newValue);
   bool insert(ArrayIndex index, Value&& newValue);
 
+#ifdef JSONCPP_HAS_STRING_VIEW
+  /// Access an object value by name, create a null member if it does not exist.
+  /// \param key may contain embedded nulls.
+  Value& operator[](std::string_view key);
+  /// Access an object value by name, returns null if there is no member with
+  /// that name.
+  /// \param key may contain embedded nulls.
+  const Value& operator[](std::string_view key) const;
+#else
   /// Access an object value by name, create a null member if it does not exist.
   /// \note Because of our implementation, keys are limited to 2^30 -1 chars.
   /// Exceeding that will cause an exception.
@@ -484,6 +510,7 @@
   /// that name.
   /// \param key may contain embedded nulls.
   const Value& operator[](const String& key) const;
+#endif
   /** \brief Access an object value by name, create a null member if it does not
    * exist.
    *
@@ -497,18 +524,24 @@
    *   \endcode
    */
   Value& operator[](const StaticString& key);
+#ifdef JSONCPP_HAS_STRING_VIEW
+  /// Return the member named key if it exist, defaultValue otherwise.
+  /// \note deep copy
+  Value get(std::string_view key, const Value& defaultValue) const;
+#else
   /// Return the member named key if it exist, defaultValue otherwise.
   /// \note deep copy
   Value get(const char* key, const Value& defaultValue) const;
   /// Return the member named key if it exist, defaultValue otherwise.
   /// \note deep copy
+  /// \param key may contain embedded nulls.
+  Value get(const String& key, const Value& defaultValue) const;
+#endif
+  /// Return the member named key if it exist, defaultValue otherwise.
+  /// \note deep copy
   /// \note key may contain embedded nulls.
   Value get(const char* begin, const char* end,
             const Value& defaultValue) const;
-  /// Return the member named key if it exist, defaultValue otherwise.
-  /// \note deep copy
-  /// \param key may contain embedded nulls.
-  Value get(const String& key, const Value& defaultValue) const;
   /// Most general and efficient version of isMember()const, get()const,
   /// and operator[]const
   /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30
@@ -525,20 +558,28 @@
   /// Do nothing if it did not exist.
   /// \pre type() is objectValue or nullValue
   /// \post type() is unchanged
+#if JSONCPP_HAS_STRING_VIEW
+  void removeMember(std::string_view key);
+#else
   void removeMember(const char* key);
   /// Same as removeMember(const char*)
   /// \param key may contain embedded nulls.
   void removeMember(const String& key);
-  /// Same as removeMember(const char* begin, const char* end, Value* removed),
-  /// but 'key' is null-terminated.
-  bool removeMember(const char* key, Value* removed);
+#endif
   /** \brief Remove the named map member.
    *
    *  Update 'removed' iff removed.
    *  \param key may contain embedded nulls.
    *  \return true iff removed (no exceptions)
    */
+#if JSONCPP_HAS_STRING_VIEW
+  bool removeMember(std::string_view key, Value* removed);
+#else
   bool removeMember(String const& key, Value* removed);
+  /// Same as removeMember(const char* begin, const char* end, Value* removed),
+  /// but 'key' is null-terminated.
+  bool removeMember(const char* key, Value* removed);
+#endif
   /// Same as removeMember(String const& key, Value* removed)
   bool removeMember(const char* begin, const char* end, Value* removed);
   /** \brief Remove the indexed array element.
@@ -549,12 +590,18 @@
    */
   bool removeIndex(ArrayIndex index, Value* removed);
 
+#ifdef JSONCPP_HAS_STRING_VIEW
+  /// Return true if the object has a member named key.
+  /// \param key may contain embedded nulls.
+  bool isMember(std::string_view key) const;
+#else
   /// Return true if the object has a member named key.
   /// \note 'key' must be null-terminated.
   bool isMember(const char* key) const;
   /// Return true if the object has a member named key.
   /// \param key may contain embedded nulls.
   bool isMember(const String& key) const;
+#endif
   /// Same as isMember(String const& key)const
   bool isMember(const char* begin, const char* end) const;
 
diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp
index d9dee50..527d716 100644
--- a/src/lib_json/json_value.cpp
+++ b/src/lib_json/json_value.cpp
@@ -17,6 +17,10 @@
 #include <sstream>
 #include <utility>
 
+#ifdef JSONCPP_HAS_STRING_VIEW
+#include <string_view>
+#endif
+
 // Provide implementation equivalent of std::snprintf for older _MSC compilers
 #if defined(_MSC_VER) && _MSC_VER < 1900
 #include <stdarg.h>
@@ -420,6 +424,14 @@
       value.data(), static_cast<unsigned>(value.length()));
 }
 
+#ifdef JSONCPP_HAS_STRING_VIEW
+Value::Value(std::string_view value) {
+  initBasic(stringValue, true);
+  value_.string_ = duplicateAndPrefixStringValue(
+      value.data(), static_cast<unsigned>(value.length()));
+}
+#endif
+
 Value::Value(const StaticString& value) {
   initBasic(stringValue);
   value_.string_ = const_cast<char*>(value.c_str());
@@ -627,6 +639,21 @@
   return true;
 }
 
+#ifdef JSONCPP_HAS_STRING_VIEW
+bool Value::getString(std::string_view* str) const {
+  if (type() != stringValue)
+    return false;
+  if (value_.string_ == nullptr)
+    return false;
+  const char* begin;
+  unsigned length;
+  decodePrefixedString(this->isAllocated(), this->value_.string_, &length,
+                       &begin);
+  *str = std::string_view(begin, length);
+  return true;
+}
+#endif
+
 String Value::asString() const {
   switch (type()) {
   case nullValue:
@@ -1108,6 +1135,17 @@
                       "objectValue or nullValue");
   return &resolveReference(begin, end);
 }
+#ifdef JSONCPP_HAS_STRING_VIEW
+const Value& Value::operator[](std::string_view key) const {
+  Value const* found = find(key.data(), key.data() + key.length());
+  if (!found)
+    return nullSingleton();
+  return *found;
+}
+Value& Value::operator[](std::string_view key) {
+  return resolveReference(key.data(), key.data() + key.length());
+}
+#else
 const Value& Value::operator[](const char* key) const {
   Value const* found = find(key, key + strlen(key));
   if (!found)
@@ -1128,6 +1166,7 @@
 Value& Value::operator[](const String& key) {
   return resolveReference(key.data(), key.data() + key.length());
 }
+#endif
 
 Value& Value::operator[](const StaticString& key) {
   return resolveReference(key.c_str());
@@ -1167,12 +1206,18 @@
   Value const* found = find(begin, end);
   return !found ? defaultValue : *found;
 }
+#ifdef JSONCPP_HAS_STRING_VIEW
+Value Value::get(std::string_view key, const Value& defaultValue) const {
+  return get(key.data(), key.data() + key.length(), defaultValue);
+}
+#else
 Value Value::get(char const* key, Value const& defaultValue) const {
   return get(key, key + strlen(key), defaultValue);
 }
 Value Value::get(String const& key, Value const& defaultValue) const {
   return get(key.data(), key.data() + key.length(), defaultValue);
 }
+#endif
 
 bool Value::removeMember(const char* begin, const char* end, Value* removed) {
   if (type() != objectValue) {
@@ -1188,12 +1233,31 @@
   value_.map_->erase(it);
   return true;
 }
+#ifdef JSONCPP_HAS_STRING_VIEW
+bool Value::removeMember(std::string_view key, Value* removed) {
+  return removeMember(key.data(), key.data() + key.length(), removed);
+}
+#else
 bool Value::removeMember(const char* key, Value* removed) {
   return removeMember(key, key + strlen(key), removed);
 }
 bool Value::removeMember(String const& key, Value* removed) {
   return removeMember(key.data(), key.data() + key.length(), removed);
 }
+#endif
+
+#ifdef JSONCPP_HAS_STRING_VIEW
+void Value::removeMember(std::string_view key) {
+  JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue,
+                      "in Json::Value::removeMember(): requires objectValue");
+  if (type() == nullValue)
+    return;
+
+  CZString actualKey(key.data(), unsigned(key.length()),
+                     CZString::noDuplication);
+  value_.map_->erase(actualKey);
+}
+#else
 void Value::removeMember(const char* key) {
   JSON_ASSERT_MESSAGE(type() == nullValue || type() == objectValue,
                       "in Json::Value::removeMember(): requires objectValue");
@@ -1204,6 +1268,7 @@
   value_.map_->erase(actualKey);
 }
 void Value::removeMember(const String& key) { removeMember(key.c_str()); }
+#endif
 
 bool Value::removeIndex(ArrayIndex index, Value* removed) {
   if (type() != arrayValue) {
@@ -1233,12 +1298,18 @@
   Value const* value = find(begin, end);
   return nullptr != value;
 }
+#ifdef JSONCPP_HAS_STRING_VIEW
+bool Value::isMember(std::string_view key) const {
+  return isMember(key.data(), key.data() + key.length());
+}
+#else
 bool Value::isMember(char const* key) const {
   return isMember(key, key + strlen(key));
 }
 bool Value::isMember(String const& key) const {
   return isMember(key.data(), key.data() + key.length());
 }
+#endif
 
 Value::Members Value::getMemberNames() const {
   JSON_ASSERT_MESSAGE(