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(