Commit 0e2c4290 authored by Xiao Shi's avatar Xiao Shi Committed by Facebook Github Bot

use heterogeneous lookup and mutation in folly::dynamic

Summary:
This diff allows `folly::dynamic` to lookup and mutate using any type that is
convertible to `StringPiece` (i.e., `const char*`, `std::string`, `fbstring`)
without having to construct a `dynamic` object (which requires copying the
string).

Currently dynamic string types uses fnv32 hash, which is OK since F14 is going
to do a mixing step anyway; we are changing this in D8304491.

Reviewed By: yfeldblum

Differential Revision: D8299863

fbshipit-source-id: a154bfe8f17e1d83968cdc1755ec54b6eed4299e
parent c150d3e6
......@@ -23,6 +23,59 @@
#include <folly/Likely.h>
#include <folly/detail/Iterators.h>
namespace folly {
namespace detail {
struct DynamicHasher {
using is_transparent = void;
size_t operator()(dynamic const& d) const {
return d.hash();
}
template <typename T>
std::enable_if_t<std::is_convertible<T, StringPiece>::value, size_t>
operator()(T const& val) const {
// keep consistent with dynamic::hash() for strings
return Hash()(static_cast<StringPiece>(val));
}
};
struct DynamicKeyEqual {
using is_transparent = void;
bool operator()(const dynamic& lhs, const dynamic& rhs) const {
return std::equal_to<dynamic>()(lhs, rhs);
}
// Dynamic objects contains a map<dynamic, dynamic>. At least one of the
// operands should be a dynamic. Hence, an operator() where both operands are
// convertible to StringPiece is unnecessary.
template <typename A, typename B>
std::enable_if_t<
std::is_convertible<A, StringPiece>::value &&
std::is_convertible<B, StringPiece>::value,
bool>
operator()(A const& lhs, B const& rhs) const = delete;
template <typename A>
std::enable_if_t<std::is_convertible<A, StringPiece>::value, bool> operator()(
A const& lhs,
dynamic const& rhs) const {
return FOLLY_LIKELY(rhs.type() == dynamic::Type::STRING) &&
std::equal_to<StringPiece>()(lhs, rhs.stringPiece());
}
template <typename B>
std::enable_if_t<std::is_convertible<B, StringPiece>::value, bool> operator()(
dynamic const& lhs,
B const& rhs) const {
return FOLLY_LIKELY(lhs.type() == dynamic::Type::STRING) &&
std::equal_to<StringPiece>()(lhs.stringPiece(), rhs);
}
};
} // namespace detail
} // namespace folly
//////////////////////////////////////////////////////////////////////
namespace std {
......@@ -147,7 +200,11 @@ dynamic numericOp(dynamic const& a, dynamic const& b) {
* Note: Later we may add separate order tracking here (a multi-index
* type of thing.)
*/
struct dynamic::ObjectImpl : F14NodeMap<dynamic, dynamic> {};
struct dynamic::ObjectImpl : F14NodeMap<
dynamic,
dynamic,
detail::DynamicHasher,
detail::DynamicKeyEqual> {};
//////////////////////////////////////////////////////////////////////
......@@ -590,21 +647,43 @@ inline dynamic&& dynamic::operator[](dynamic const& idx) && {
return std::move((*this)[idx]);
}
template <class K, class V> inline dynamic& dynamic::setDefault(K&& k, V&& v) {
template <typename K>
inline std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&>
dynamic::operator[](K&& k) & {
auto& obj = get<ObjectImpl>();
return obj.insert(std::make_pair(std::forward<K>(k),
std::forward<V>(v))).first->second;
auto ret = obj.emplace(std::forward<K>(k), nullptr);
return ret.first->second;
}
template <typename K>
inline std::
enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic const&>
dynamic::operator[](K&& k) const& {
return at(std::forward<K>(k));
}
template <typename K>
inline std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&&>
dynamic::operator[](K&& k) && {
return std::move((*this)[std::forward<K>(k)]);
}
template <class K> inline dynamic& dynamic::setDefault(K&& k, dynamic&& v) {
template <class K, class V>
inline dynamic& dynamic::setDefault(K&& k, V&& v) {
auto& obj = get<ObjectImpl>();
return obj.insert(std::make_pair(std::forward<K>(k),
std::move(v))).first->second;
return obj.emplace(std::forward<K>(k), std::forward<V>(v)).first->second;
}
template <class K> inline dynamic& dynamic::setDefault(K&& k, const dynamic& v) {
template <class K>
inline dynamic& dynamic::setDefault(K&& k, dynamic&& v) {
auto& obj = get<ObjectImpl>();
return obj.insert(std::make_pair(std::forward<K>(k), v)).first->second;
return obj.emplace(std::forward<K>(k), std::move(v)).first->second;
}
template <class K>
inline dynamic& dynamic::setDefault(K&& k, const dynamic& v) {
auto& obj = get<ObjectImpl>();
return obj.emplace(std::forward<K>(k), v).first->second;
}
inline dynamic* dynamic::get_ptr(dynamic const& idx) & {
......@@ -624,6 +703,33 @@ inline dynamic&& dynamic::at(dynamic const& idx) && {
return std::move(at(idx));
}
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic const&>
dynamic::at(K const& idx) const& {
auto* pobject = get_nothrow<ObjectImpl>();
if (!pobject) {
throwTypeError_("object/array", type());
}
auto it = pobject->find(idx);
if (it == pobject->end()) {
throw_exception<std::out_of_range>(
sformat("couldn't find key {} in dynamic object", idx));
}
return it->second;
}
template <typename K>
inline std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&>
dynamic::at(K const& idx) & {
return const_cast<dynamic&>(const_cast<dynamic const*>(this)->at(idx));
}
template <typename K>
inline std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&&>
dynamic::at(K const& idx) && {
return std::move(at(idx));
}
inline bool dynamic::empty() const {
if (isNull()) {
return true;
......@@ -634,6 +740,11 @@ inline bool dynamic::empty() const {
inline std::size_t dynamic::count(dynamic const& key) const {
return find(key) != items().end() ? 1u : 0u;
}
template <typename K>
inline std::enable_if_t<std::is_convertible<K, StringPiece>::value, std::size_t>
dynamic::count(K const& key) const {
return find(key) != items().end() ? 1u : 0u;
}
inline dynamic::const_item_iterator dynamic::find(dynamic const& key) const {
return get<ObjectImpl>().find(key);
......@@ -642,10 +753,25 @@ inline dynamic::item_iterator dynamic::find(dynamic const& key) {
return get<ObjectImpl>().find(key);
}
template <class K, class V> inline void dynamic::insert(K&& key, V&& val) {
template <typename K>
inline std::enable_if_t<
std::is_convertible<K, StringPiece>::value,
dynamic::const_item_iterator>
dynamic::find(K const& key) const {
return get<ObjectImpl>().find(key);
}
template <typename K>
inline std::enable_if_t<
std::is_convertible<K, StringPiece>::value,
dynamic::item_iterator>
dynamic::find(K const& key) {
return get<ObjectImpl>().find(key);
}
template <class K, class V>
inline void dynamic::insert(K&& key, V&& val) {
auto& obj = get<ObjectImpl>();
auto rv = obj.insert({ std::forward<K>(key), nullptr });
rv.first->second = std::forward<V>(val);
obj[std::forward<K>(key)] = std::forward<V>(val);
}
inline void dynamic::update(const dynamic& mergeObj) {
......@@ -710,6 +836,12 @@ inline std::size_t dynamic::erase(dynamic const& key) {
auto& obj = get<ObjectImpl>();
return obj.erase(key);
}
template <typename K>
inline std::enable_if_t<std::is_convertible<K, StringPiece>::value, std::size_t>
dynamic::erase(K&& key) {
auto& obj = get<ObjectImpl>();
return obj.erase(std::forward<K>(key));
}
inline dynamic::iterator dynamic::erase(const_iterator it) {
auto& arr = get<Array>();
......
......@@ -188,7 +188,7 @@ dynamic& dynamic::operator[](dynamic const& k) & {
return at(k);
}
auto& obj = get<ObjectImpl>();
auto ret = obj.insert({k, nullptr});
auto ret = obj.emplace(k, nullptr);
return ret.first->second;
}
......@@ -246,11 +246,6 @@ const dynamic* dynamic::get_ptr(dynamic const& idx) const& {
}
}
[[noreturn]] static void throwOutOfRangeAtMissingKey(dynamic const& idx) {
auto msg = sformat("couldn't find key {} in dynamic object", idx.asString());
throw_exception<std::out_of_range>(msg);
}
dynamic const& dynamic::at(dynamic const& idx) const& {
if (auto* parray = get_nothrow<Array>()) {
if (!idx.isInt()) {
......@@ -263,7 +258,8 @@ dynamic const& dynamic::at(dynamic const& idx) const& {
} else if (auto* pobject = get_nothrow<ObjectImpl>()) {
auto it = pobject->find(idx);
if (it == pobject->end()) {
throwOutOfRangeAtMissingKey(idx);
throw_exception<std::out_of_range>(
sformat("couldn't find key {} in dynamic object", idx.asString()));
}
return it->second;
} else {
......@@ -316,6 +312,7 @@ std::size_t dynamic::hash() const {
case BOOL:
return std::hash<bool>()(getBool());
case STRING:
// keep consistent with detail::DynamicHasher
return Hash()(getString());
}
assume_unreachable();
......
......@@ -379,12 +379,25 @@ struct dynamic : private boost::operators<dynamic> {
const_item_iterator find(dynamic const&) const;
item_iterator find(dynamic const&);
template <typename K>
std::enable_if_t<
std::is_convertible<K, StringPiece>::value,
const_item_iterator>
find(K const&) const;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, item_iterator>
find(K const&);
/*
* If this is an object, returns whether it contains a field with
* the given name. Otherwise throws TypeError.
*/
std::size_t count(dynamic const&) const;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, std::size_t>
count(K const&) const;
/*
* For objects or arrays, provides access to sub-fields by index or
* field name.
......@@ -397,6 +410,16 @@ struct dynamic : private boost::operators<dynamic> {
dynamic& at(dynamic const&) &;
dynamic&& at(dynamic const&) &&;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic const&>
at(K const&) const&;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&> at(
K const&) &;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&&> at(
K const&) &&;
/*
* Locate element using JSON pointer, per RFC 6901. Returns nullptr if
* element could not be located. Throws if pointer does not match the
......@@ -440,6 +463,16 @@ struct dynamic : private boost::operators<dynamic> {
dynamic const& operator[](dynamic const&) const&;
dynamic&& operator[](dynamic const&) &&;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&>
operator[](K&&) &;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic const&>
operator[](K&&) const&;
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, dynamic&&>
operator[](K&&) &&;
/*
* Only defined for objects, throws TypeError otherwise.
*
......@@ -477,7 +510,8 @@ struct dynamic : private boost::operators<dynamic> {
/*
* Inserts the supplied key-value pair to an object, or throws if
* it's not an object.
* it's not an object. If the key already exists, insert will overwrite the
* value, i.e., similar to insert_or_assign.
*
* Invalidates iterators.
*/
......@@ -519,6 +553,9 @@ struct dynamic : private boost::operators<dynamic> {
* Returns the number of elements erased (i.e. 1 or 0).
*/
std::size_t erase(dynamic const& key);
template <typename K>
std::enable_if_t<std::is_convertible<K, StringPiece>::value, std::size_t>
erase(K&&);
/*
* Erase an element from a dynamic object or array, using an
......
......@@ -33,11 +33,31 @@ TEST(Dynamic, ObjectBasics) {
EXPECT_EQ(obj.at("a"), false);
EXPECT_EQ(obj.size(), 1);
obj.insert("a", true);
dynamic key{"a"};
folly::StringPiece sp{"a"};
std::string s{"a"};
EXPECT_EQ(obj.size(), 1);
EXPECT_EQ(obj.at("a"), true);
obj.at("a") = nullptr;
EXPECT_EQ(obj.at(sp), true);
EXPECT_EQ(obj.at(key), true);
obj.at(sp) = nullptr;
EXPECT_EQ(obj.size(), 1);
EXPECT_TRUE(obj.at("a") == nullptr);
EXPECT_TRUE(obj.at(s) == nullptr);
obj["a"] = 12;
EXPECT_EQ(obj[sp], 12);
obj[key] = "foo";
EXPECT_EQ(obj["a"], "foo");
(void)obj["b"];
EXPECT_EQ(obj.size(), 2);
obj.erase("a");
EXPECT_TRUE(obj.find(sp) == obj.items().end());
obj.erase("b");
EXPECT_EQ(obj.size(), 0);
dynamic newObject = dynamic::object;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment