/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This is a runtime dynamically typed value. It holds types from a * specific predetermined set of types (ints, bools, arrays, etc). In * particular, it can be used as a convenient in-memory representation * for complete json objects. * * In general you can try to use these objects as if they were the * type they represent (although in some cases with a slightly less * complete interface than the raw type), and it'll just throw a * TypeError if it is used in an illegal way. * * Some examples: * * dynamic twelve = 12; * dynamic str = "string"; * dynamic map = dynamic::object; * map[str] = twelve; * map[str + "another_str"] = dynamic::array("array", "of", 4, "elements"); * map.insert("null_element", nullptr); * ++map[str]; * assert(map[str] == 13); * * // Building a complex object with a sub array inline: * dynamic d = dynamic::object * ("key", "value") * ("key2", dynamic::array("a", "array")) * ; * * Also see folly/json.h for the serialization and deserialization * functions for JSON. * * Additional documentation is in folly/docs/Dynamic.md. * * @author Jordan DeLong */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include namespace folly { ////////////////////////////////////////////////////////////////////// struct dynamic; struct TypeError; ////////////////////////////////////////////////////////////////////// struct dynamic : private boost::operators { enum Type { NULLT, ARRAY, BOOL, DOUBLE, INT64, OBJECT, STRING, }; template struct NumericTypeHelper; /* * We support direct iteration of arrays, and indirect iteration of objects. * See begin(), end(), keys(), values(), and items() for more. * * Array iterators dereference as the elements in the array. * Object key iterators dereference as the keys in the object. * Object value iterators dereference as the values in the object. * Object item iterators dereference as pairs of (key, value). */ private: typedef std::vector Array; /* * Violating spec, std::vector::const_reference is not bool in libcpp: * http://howardhinnant.github.io/onvectorbool.html * * This is used to add a public ctor which is only enabled under libcpp taking * std::vector::const_reference without using the preprocessor. */ struct VectorBoolConstRefFake : std::false_type {}; using VectorBoolConstRefCtorType = std::conditional_t< std::is_same::const_reference, bool>::value, VectorBoolConstRefFake, std::vector::const_reference>; public: typedef Array::iterator iterator; typedef Array::const_iterator const_iterator; typedef dynamic value_type; struct const_key_iterator; struct const_value_iterator; struct const_item_iterator; struct value_iterator; struct item_iterator; /* * Creation routines for making dynamic objects and arrays. Objects * are maps from key to value (so named due to json-related origins * here). * * Example: * * // Make a fairly complex dynamic: * dynamic d = dynamic::object("key", "value1") * ("key2", dynamic::array("value", * "with", * 4, * "words")); * * // Build an object in a few steps: * dynamic d = dynamic::object; * d["key"] = 12; * d["something_else"] = dynamic::array(1, 2, 3, nullptr); */ private: struct EmptyArrayTag {}; struct ObjectMaker; public: static void array(EmptyArrayTag); template static dynamic array(Args&&... args); static ObjectMaker object(); static ObjectMaker object(dynamic, dynamic); /** * Default constructor, initializes with nullptr. */ dynamic(); /* * String compatibility constructors. */ /* implicit */ dynamic(std::nullptr_t); /* implicit */ dynamic(StringPiece val); /* implicit */ dynamic(char const* val); /* implicit */ dynamic(std::string val); /* * This is part of the plumbing for array() and object(), above. * Used to create a new array or object dynamic. */ /* implicit */ dynamic(void (*)(EmptyArrayTag)); /* implicit */ dynamic(ObjectMaker (*)()); /* implicit */ dynamic(ObjectMaker const&) = delete; /* implicit */ dynamic(ObjectMaker&&); /* * Constructors for integral and float types. * Other types are SFINAEd out with NumericTypeHelper. */ template ::type> /* implicit */ dynamic(T t); /* * If v is vector, v[idx] is a proxy object implicitly convertible to * bool. Calling a function f(dynamic) with f(v[idx]) would require a double * implicit conversion (reference -> bool -> dynamic) which is not allowed, * hence we explicitly accept the reference proxy. */ /* implicit */ dynamic(std::vector::reference val); /* implicit */ dynamic(VectorBoolConstRefCtorType val); /* * Create a dynamic that is an array of the values from the supplied * iterator range. */ template explicit dynamic(Iterator first, Iterator last); dynamic(dynamic const&); dynamic(dynamic&&) noexcept; ~dynamic() noexcept; /* * "Deep" equality comparison. This will compare all the way down * an object or array, and is potentially expensive. */ bool operator==(dynamic const& o) const; /* * For all types except object this returns the natural ordering on * those types. For objects, we throw TypeError. */ bool operator<(dynamic const& o) const; /* * General operators. * * These throw TypeError when used with types or type combinations * that don't support them. * * These functions may also throw if you use 64-bit integers with * doubles when the integers are too big to fit in a double. */ dynamic& operator+=(dynamic const&); dynamic& operator-=(dynamic const&); dynamic& operator*=(dynamic const&); dynamic& operator/=(dynamic const&); dynamic& operator%=(dynamic const&); dynamic& operator|=(dynamic const&); dynamic& operator&=(dynamic const&); dynamic& operator^=(dynamic const&); dynamic& operator++(); dynamic& operator--(); /* * Assignment from other dynamics. Because of the implicit conversion * to dynamic from its potential types, you can use this to change the * type pretty intuitively. * * Basic guarantee only. */ dynamic& operator=(dynamic const&); dynamic& operator=(dynamic&&) noexcept; /* * For simple dynamics (not arrays or objects), this prints the * value to an std::ostream in the expected way. Respects the * formatting manipulators that have been sent to the stream * already. * * If the dynamic holds an object or array, this prints them in a * format very similar to JSON. (It will in fact actually be JSON * as long as the dynamic validly represents a JSON object---i.e. it * can't have non-string keys.) */ friend std::ostream& operator<<(std::ostream&, dynamic const&); /* * Returns true if this dynamic is of the specified type. */ bool isString() const; bool isObject() const; bool isBool() const; bool isNull() const; bool isArray() const; bool isDouble() const; bool isInt() const; /* * Returns: isInt() || isDouble(). */ bool isNumber() const; /* * Returns the type of this dynamic. */ Type type() const; /* * Returns the type of this dynamic as a printable string. */ const char* typeName() const; /* * Extract a value while trying to convert to the specified type. * Throws exceptions if we cannot convert from the real type to the * requested type. * * Note you can only use this to access integral types or strings, * since arrays and objects are generally best dealt with as a * dynamic. */ std::string asString() const; double asDouble() const; int64_t asInt() const; bool asBool() const; /* * Extract the value stored in this dynamic without type conversion. * * These will throw a TypeError if the dynamic has a different type. */ const std::string& getString() const&; double getDouble() const&; int64_t getInt() const&; bool getBool() const&; std::string& getString() &; double& getDouble() &; int64_t& getInt() &; bool& getBool() &; std::string&& getString() &&; double getDouble() &&; int64_t getInt() &&; bool getBool() &&; /* * It is occasionally useful to access a string's internal pointer * directly, without the type conversion of `asString()`. * * These will throw a TypeError if the dynamic is not a string. */ const char* data() const&; const char* data() && = delete; const char* c_str() const&; const char* c_str() && = delete; StringPiece stringPiece() const; /* * Returns: true if this dynamic is null, an empty array, an empty * object, or an empty string. */ bool empty() const; /* * If this is an array or an object, returns the number of elements * contained. If it is a string, returns the length. Otherwise * throws TypeError. */ std::size_t size() const; /* * You can iterate over the values of the array. Calling these on * non-arrays will throw a TypeError. */ const_iterator begin() const; const_iterator end() const; iterator begin(); iterator end(); private: /* * Helper object returned by keys(), values(), and items(). */ template struct IterableProxy; /* * Helper for heterogeneous lookup and mutation on objects: at(), find(), * count(), erase(), operator[] */ template using IfIsNonStringDynamicConvertible = std::enable_if_t< !std::is_convertible::value && std::is_convertible::value, T>; template using IfNotIterator = std::enable_if_t::value, T>; public: /* * You can iterate over the keys, values, or items (std::pair of key and * value) in an object. Calling these on non-objects will throw a TypeError. */ IterableProxy keys() const; IterableProxy values() const; IterableProxy items() const; IterableProxy values(); IterableProxy items(); /* * AssociativeContainer-style find interface for objects. Throws if * this is not an object. * * Returns: items().end() if the key is not present, or a * const_item_iterator pointing to the item. */ template IfIsNonStringDynamicConvertible find(K&&) const; template IfIsNonStringDynamicConvertible find(K&&); const_item_iterator find(StringPiece) const; item_iterator find(StringPiece); /* * If this is an object, returns whether it contains a field with * the given name. Otherwise throws TypeError. */ template IfIsNonStringDynamicConvertible count(K&&) const; std::size_t count(StringPiece) const; /* * For objects or arrays, provides access to sub-fields by index or * field name. * * Using these with dynamic objects that are not arrays or objects * will throw a TypeError. Using an index that is out of range or * object-element that's not present throws std::out_of_range. */ private: dynamic const& atImpl(dynamic const&) const&; public: template IfIsNonStringDynamicConvertible at(K&&) const&; template IfIsNonStringDynamicConvertible at(K&&) &; template IfIsNonStringDynamicConvertible at(K&&) &&; dynamic const& at(StringPiece) const&; dynamic& at(StringPiece) &; dynamic&& at(StringPiece) &&; /* * Locate element using JSON pointer, per RFC 6901 */ enum class json_pointer_resolution_error_code : uint8_t { other = 0, // key not found in object key_not_found, // array index out of bounds index_out_of_bounds, // special index "-" requesting append append_requested, // indexes in arrays must be numeric index_not_numeric, // indexes in arrays should not have leading zero index_has_leading_zero, // element not subscribable, i.e. neither object or array element_not_object_or_array, // hit document boundary, but pointer not exhausted yet json_pointer_out_of_bounds, }; template struct json_pointer_resolution_error { // error code encountered while resolving JSON pointer json_pointer_resolution_error_code error_code{}; // index of the JSON pointer's token that caused the error size_t index{0}; // Last correctly resolved element in object. You can use it, // for example, to add last element to the array Dynamic* context{nullptr}; }; template struct json_pointer_resolved_value { // parent element of the value in dynamic, if exist Dynamic* parent{nullptr}; // pointer to the value itself Dynamic* value{nullptr}; // if parent isObject, this is the key in object to get value StringPiece parent_key; // if parent isArray, this is the index in array to get value size_t parent_index{0}; }; // clang-format off template using resolved_json_pointer = Expected< json_pointer_resolved_value, json_pointer_resolution_error>; resolved_json_pointer try_get_ptr(json_pointer const&) const&; resolved_json_pointer try_get_ptr(json_pointer const&) &; resolved_json_pointer try_get_ptr(json_pointer const&) const&& = delete; resolved_json_pointer try_get_ptr(json_pointer const&) && = delete; // clang-format on /* * The following versions return nullptr if element could not be located. * Throws if pointer does not match the shape of the document, e.g. uses * string to index in array. */ const dynamic* get_ptr(json_pointer const&) const&; dynamic* get_ptr(json_pointer const&) &; const dynamic* get_ptr(json_pointer const&) const&& = delete; dynamic* get_ptr(json_pointer const&) && = delete; /* * Like 'at', above, except it returns either a pointer to the contained * object or nullptr if it wasn't found. This allows a key to be tested for * containment and retrieved in one operation. Example: * * if (auto* found = d.get_ptr(key)) * // use *found; * * Using these with dynamic objects that are not arrays or objects * will throw a TypeError. */ private: const dynamic* get_ptrImpl(dynamic const&) const&; public: template IfIsNonStringDynamicConvertible get_ptr(K&&) const&; template IfIsNonStringDynamicConvertible get_ptr(K&&) &; template IfIsNonStringDynamicConvertible get_ptr(K&&) && = delete; const dynamic* get_ptr(StringPiece) const&; dynamic* get_ptr(StringPiece) &; dynamic* get_ptr(StringPiece) && = delete; /* * This works for access to both objects and arrays. * * In the case of an array, the index must be an integer, and this * will throw std::out_of_range if it is less than zero or greater * than size(). * * In the case of an object, the non-const overload inserts a null * value if the key isn't present. The const overload will throw * std::out_of_range if the key is not present. * * These functions do not invalidate iterators except when a null value * is inserted into an object as described above. */ template IfIsNonStringDynamicConvertible operator[](K&&) &; template IfIsNonStringDynamicConvertible operator[](K&&) const&; template IfIsNonStringDynamicConvertible operator[](K&&) &&; dynamic& operator[](StringPiece) &; dynamic const& operator[](StringPiece) const&; dynamic&& operator[](StringPiece) &&; /* * Only defined for objects, throws TypeError otherwise. * * getDefault will return the value associated with the supplied key, the * supplied default otherwise. setDefault will set the key to the supplied * default if it is not yet set, otherwise leaving it. setDefault returns * a reference to the existing value if present, the new value otherwise. */ template IfIsNonStringDynamicConvertible getDefault( K&& k, const dynamic& v = dynamic::object) const&; template IfIsNonStringDynamicConvertible getDefault(K&& k, dynamic&& v) const&; template IfIsNonStringDynamicConvertible getDefault( K&& k, const dynamic& v = dynamic::object) &&; template IfIsNonStringDynamicConvertible getDefault(K&& k, dynamic&& v) &&; dynamic getDefault(StringPiece k, const dynamic& v = dynamic::object) const&; dynamic getDefault(StringPiece k, dynamic&& v) const&; dynamic getDefault(StringPiece k, const dynamic& v = dynamic::object) &&; dynamic getDefault(StringPiece k, dynamic&& v) &&; template IfIsNonStringDynamicConvertible setDefault(K&& k, V&& v); template dynamic& setDefault(StringPiece k, V&& v); // MSVC 2015 Update 3 needs these extra overloads because if V were a // defaulted template parameter, it causes MSVC to consider v an rvalue // reference rather than a universal reference, resulting in it not being // able to find the correct overload to construct a dynamic with. template IfIsNonStringDynamicConvertible setDefault(K&& k, dynamic&& v); template IfIsNonStringDynamicConvertible setDefault( K&& k, const dynamic& v = dynamic::object); dynamic& setDefault(StringPiece k, dynamic&& v); dynamic& setDefault(StringPiece k, const dynamic& v = dynamic::object); /* * Resizes an array so it has at n elements, using the supplied * default to fill new elements. Throws TypeError if this dynamic * is not an array. * * May invalidate iterators. * * Post: size() == n */ void resize(std::size_t n, dynamic const& = nullptr); /* * Inserts the supplied key-value pair to an object, or throws if * it's not an object. If the key already exists, insert will overwrite the * value, i.e., similar to insert_or_assign. * * Invalidates iterators. */ template IfNotIterator insert(K&&, V&& val); /* * Inserts the supplied value into array, or throw if not array * Shifts existing values in the array to the right * * Invalidates iterators. */ template iterator insert(const_iterator pos, T&& value); /* * These functions merge two folly dynamic objects. * The "update" and "update_missing" functions extend the object by * inserting the key/value pairs of mergeObj into the current object. * For update, if key is duplicated between the two objects, it * will overwrite with the value of the object being inserted (mergeObj). * For "update_missing", it will prefer the value in the original object * * The "merge" function creates a new object consisting of the key/value * pairs of both mergeObj1 and mergeObj2 * If the key is duplicated between the two objects, * it will prefer value in the second object (mergeObj2) */ void update(const dynamic& mergeObj); void update_missing(const dynamic& other); static dynamic merge(const dynamic& mergeObj1, const dynamic& mergeObj2); /* * Implement recursive version of RFC7386: JSON merge patch. This modifies * the current object. */ void merge_patch(const dynamic& patch); /* * Computes JSON merge patch (RFC7386) needed to mutate from source to target */ static dynamic merge_diff(const dynamic& source, const dynamic& target); /* * Erase an element from a dynamic object, by key. * * Invalidates iterators to the element being erased. * * Returns the number of elements erased (i.e. 1 or 0). */ template IfIsNonStringDynamicConvertible erase(K&&); std::size_t erase(StringPiece); /* * Erase an element from a dynamic object or array, using an * iterator or an iterator range. * * In arrays, invalidates iterators to elements after the element * being erased. In objects, invalidates iterators to the elements * being erased. * * Returns a new iterator to the first element beyond any elements * removed, or end() if there are none. (The iteration order does * not change.) */ iterator erase(const_iterator it); iterator erase(const_iterator first, const_iterator last); const_key_iterator erase(const_key_iterator it); const_key_iterator erase(const_key_iterator first, const_key_iterator last); value_iterator erase(const_value_iterator it); value_iterator erase(const_value_iterator first, const_value_iterator last); item_iterator erase(const_item_iterator it); item_iterator erase(const_item_iterator first, const_item_iterator last); /* * Append elements to an array. If this is not an array, throws * TypeError. * * Invalidates iterators. */ void push_back(dynamic const&); void push_back(dynamic&&); /* * Remove an element from the back of an array. If this is not an array, * throws TypeError. * * Does not invalidate iterators. */ void pop_back(); /* * Return reference to the last element in an array. If this is not * an array, throws TypeError. */ const dynamic& back() const; /* * Get a hash code. This function is called by a std::hash<> * specialization, also. * * Throws TypeError if this is an object, array, or null. */ std::size_t hash() const; private: friend struct TypeError; struct ObjectImpl; template struct TypeInfo; template struct CompareOp; template struct GetAddrImpl; template struct PrintImpl; explicit dynamic(Array&& array); template T const& get() const; template T& get(); // clang-format off template T* get_nothrow() & noexcept; // clang-format on template T const* get_nothrow() const& noexcept; // clang-format off template T* get_nothrow() && noexcept = delete; // clang-format on template T* getAddress() noexcept; template T const* getAddress() const noexcept; template T asImpl() const; static char const* typeName(Type); void destroy() noexcept; void print(std::ostream&) const; void print_as_pseudo_json(std::ostream&) const; // see json.cpp private: Type type_; union Data { explicit Data() : nul(nullptr) {} ~Data() {} std::nullptr_t nul; Array array; bool boolean; double doubl; int64_t integer; std::string string; /* * Objects are placement new'd here. We have to use a char buffer * because we don't know the type here (F14NodeMap<> with * dynamic would be parameterizing a std:: template with an * incomplete type right now). (Note that in contrast we know it * is ok to do this with fbvector because we own it.) */ aligned_storage_for_t> objectBuffer; } u_; }; ////////////////////////////////////////////////////////////////////// } // namespace folly #include