#ifndef SIMDJSON_GENERIC_ONDEMAND_OBJECT_H #ifndef SIMDJSON_CONDITIONAL_INCLUDE #define SIMDJSON_GENERIC_ONDEMAND_OBJECT_H #include "simdjson/generic/ondemand/base.h" #include "simdjson/generic/implementation_simdjson_result_base.h" #include "simdjson/generic/ondemand/value_iterator.h" #include #if SIMDJSON_STATIC_REFLECTION && SIMDJSON_SUPPORTS_CONCEPTS #include "simdjson/generic/ondemand/json_string_builder.h" // for constevalutil::fixed_string #endif #endif // SIMDJSON_CONDITIONAL_INCLUDE namespace simdjson { namespace SIMDJSON_IMPLEMENTATION { namespace ondemand { /** * A forward-only JSON object field iterator. */ class object { public: /** * Create a new invalid object. * * Exists so you can declare a variable and later assign to it before use. */ simdjson_inline object() noexcept = default; /** * Get an iterator to the start of the object. We recommend using a range-based for loop. * * Using the iterator directly is also possible but error-prone and discouraged. In particular, * you must dereference the iterator exactly once per iteration (before calling '++'). * Doing otherwise is unsafe and may lead to errors. You are responsible for ensuring */ simdjson_inline simdjson_result begin() noexcept; simdjson_inline simdjson_result end() noexcept; /** * Look up a field by name on an object (order-sensitive). By order-sensitive, we mean that * fields must be accessed in the order they appear in the JSON text (although you can * skip fields). See find_field_unordered() and operator[] for an order-insensitive version. * * The following code reads z, then y, then x, and thus will not retrieve x or y if fed the * JSON `{ "x": 1, "y": 2, "z": 3 }`: * * ```cpp * simdjson::ondemand::parser parser; * auto obj = parser.parse(R"( { "x": 1, "y": 2, "z": 3 } )"_padded); * double z = obj.find_field("z"); * double y = obj.find_field("y"); * double x = obj.find_field("x"); * ``` * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful * that only one field is returned. * * **Raw Keys:** The lookup will be done against the *raw* key, and will not unescape keys. * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. * * You must consume the fields on an object one at a time. A request for a new key * invalidates previous field values: it makes them unsafe. The value instance you get * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array * given by content["bids"].get_array() should not be accessed after you have called * content["asks"].get_array(). You can detect such mistakes by first compiling and running * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an * OUT_OF_ORDER_ITERATION error is generated. * * You are expected to access keys only once. You should access the value corresponding to a * key a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() * is an error. * * If you expect to have keys with escape characters, please review our documentation. * * @param key The key to look up. * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. */ simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; /** @overload simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; */ simdjson_inline simdjson_result find_field(std::string_view key) && noexcept; /** * Look up a field by name on an object, without regard to key order. * * **Performance Notes:** This is a bit less performant than find_field(), though its effect varies * and often appears negligible. It starts out normally, starting out at the last field; but if * the field is not found, it scans from the beginning of the object to see if it missed it. That * missing case has a non-cache-friendly bump and lots of extra scanning, especially if the object * in question is large. The fact that the extra code is there also bumps the executable size. * * We default operator[] on find_field_unordered() for convenience. * It is the default because it would be highly surprising (and hard to debug) if the * default behavior failed to look up a field just because it was in the wrong order--and many * APIs assume this. Therefore, you must be explicit if you want to treat objects as out of order. * * Use find_field() if you are sure fields will be in order (or are willing to treat it as if the * field was not there when they are not in order). * * If you have multiple fields with a matching key ({"x": 1, "x": 1}) be mindful * that only one field is returned. * * You must consume the fields on an object one at a time. A request for a new key * invalidates previous field values: it makes them unsafe. The value instance you get * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array * given by content["bids"].get_array() should not be accessed after you have called * content["asks"].get_array(). You can detect such mistakes by first compiling and running * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an * OUT_OF_ORDER_ITERATION error is generated. * * You are expected to access keys only once. You should access the value corresponding to a key * a single time. Doing object["mykey"].to_string() and then again object["mykey"].to_string() is an error. * * If you expect to have keys with escape characters, please review our documentation. * * @param key The key to look up. * @returns The value of the field, or NO_SUCH_FIELD if the field is not in the object. */ simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; /** @overload simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; */ simdjson_inline simdjson_result operator[](std::string_view key) && noexcept; /** * Get the value associated with the given JSON pointer. We use the RFC 6901 * https://tools.ietf.org/html/rfc6901 standard, interpreting the current node * as the root of its own JSON document. * * ondemand::parser parser; * auto json = R"({ "foo": { "a": [ 10, 20, 30 ] }})"_padded; * auto doc = parser.iterate(json); * doc.at_pointer("/foo/a/1") == 20 * * It is allowed for a key to be the empty string: * * ondemand::parser parser; * auto json = R"({ "": { "a": [ 10, 20, 30 ] }})"_padded; * auto doc = parser.iterate(json); * doc.at_pointer("//a/1") == 20 * * Note that at_pointer() called on the document automatically calls the document's rewind * method between each call. It invalidates all previously accessed arrays, objects and values * that have not been consumed. Yet it is not the case when calling at_pointer on an object * instance: there is no rewind and no invalidation. * * You may call at_pointer more than once on an object, but each time the pointer is advanced * to be within the value matched by the key indicated by the JSON pointer query. Thus any preceding * key (as well as the current key) can no longer be used with following JSON pointer calls. * * Also note that at_pointer() relies on find_field() which implies that we do not unescape keys when matching. * * @return The value associated with the given JSON pointer, or: * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array * - INVALID_JSON_POINTER if the JSON pointer is invalid and cannot be parsed */ inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; /** * Get the value associated with the given JSONPath expression. We only support * JSONPath queries that trivially convertible to JSON Pointer queries: key * names and array indices. * * @return The value associated with the given JSONPath expression, or: * - INVALID_JSON_POINTER if the JSONPath to JSON Pointer conversion fails * - NO_SUCH_FIELD if a field does not exist in an object * - INDEX_OUT_OF_BOUNDS if an array index is larger than an array length * - INCORRECT_TYPE if a non-integer is used to access an array */ inline simdjson_result at_path(std::string_view json_path) noexcept; /** * Get all values matching the given JSONPath expression with wildcard support. * Supports wildcard patterns like ".*" to match all object fields. * * @param json_path JSONPath expression with wildcards * @return Vector of values matching the wildcard pattern */ inline simdjson_result> at_path_with_wildcard(std::string_view json_path) noexcept; /** * Reset the iterator so that we are pointing back at the * beginning of the object. You should still consume values only once even if you * can iterate through the object more than once. If you unescape a string or a key * within the object more than once, you have unsafe code. Note that rewinding an object * means that you may need to reparse it anew: it is not a free operation. * * @returns true if the object contains some elements (not empty) */ inline simdjson_result reset() & noexcept; /** * This method scans the beginning of the object and checks whether the * object is empty. * The runtime complexity is constant time. After * calling this function, if successful, the object is 'rewinded' at its * beginning as if it had never been accessed. If the JSON is malformed (e.g., * there is a missing comma), then an error is returned and it is no longer * safe to continue. */ inline simdjson_result is_empty() & noexcept; /** * This method scans the object and counts the number of key-value pairs. * The count_fields method should always be called before you have begun * iterating through the object: it is expected that you are pointing at * the beginning of the object. * The runtime complexity is linear in the size of the object. After * calling this function, if successful, the object is 'rewinded' at its * beginning as if it had never been accessed. If the JSON is malformed (e.g., * there is a missing comma), then an error is returned and it is no longer * safe to continue. * * To check that an object is empty, it is more performant to use * the is_empty() method. * * Performance hint: You should only call count_fields() as a last * resort as it may require scanning the document twice or more. */ simdjson_inline simdjson_result count_fields() & noexcept; /** * Consumes the object and returns a string_view instance corresponding to the * object as represented in JSON. It points inside the original byte array containing * the JSON document. */ simdjson_inline simdjson_result raw_json() noexcept; #if SIMDJSON_SUPPORTS_CONCEPTS /** * Get this object as the given type. * * @param out This is set to a value of the given type, parsed from the JSON. If there is an error, this may not be initialized. * @returns INCORRECT_TYPE If the JSON object is not of the given type. * @returns SUCCESS If the parse succeeded and the out parameter was set to the value. */ template simdjson_warn_unused simdjson_inline error_code get(T &out) noexcept(custom_deserializable ? nothrow_custom_deserializable : true) { static_assert(custom_deserializable); return deserialize(*this, out); } /** * Get this array as the given type. * * @returns A value of the given type, parsed from the JSON. * @returns INCORRECT_TYPE If the JSON value is not the given type. */ template simdjson_inline simdjson_result get() noexcept(custom_deserializable ? nothrow_custom_deserializable : true) { static_assert(std::is_default_constructible::value, "The specified type is not default constructible."); T out{}; SIMDJSON_TRY(get(out)); return out; } #if SIMDJSON_STATIC_REFLECTION /** * Extract only specific fields from the JSON object into a struct. * * This allows selective deserialization of only the fields you need, * potentially improving performance by skipping unwanted fields. * * Example: * ```cpp * struct Car { * std::string make; * std::string model; * int year; * double price; * }; * * Car car; * object.extract_into<"make", "model">(car); * // Only 'make' and 'model' fields are extracted from JSON * ``` * * @tparam FieldNames Compile-time string literals specifying which fields to extract * @param out The output struct to populate with selected fields * @returns SUCCESS on success, or an error code if a required field is missing or has wrong type */ template requires(std::is_class_v && (sizeof...(FieldNames) > 0)) simdjson_warn_unused simdjson_inline error_code extract_into(T& out) & noexcept; #endif // SIMDJSON_STATIC_REFLECTION #endif // SIMDJSON_SUPPORTS_CONCEPTS protected: /** * Go to the end of the object, no matter where you are right now. */ simdjson_warn_unused simdjson_inline error_code consume() noexcept; static simdjson_inline simdjson_result start(value_iterator &iter) noexcept; static simdjson_inline simdjson_result start_root(value_iterator &iter) noexcept; static simdjson_inline simdjson_result started(value_iterator &iter) noexcept; static simdjson_inline object resume(const value_iterator &iter) noexcept; simdjson_inline object(const value_iterator &iter) noexcept; simdjson_warn_unused simdjson_inline error_code find_field_raw(const std::string_view key) noexcept; value_iterator iter{}; friend class value; friend class document; friend struct simdjson_result; }; } // namespace ondemand } // namespace SIMDJSON_IMPLEMENTATION } // namespace simdjson namespace simdjson { template<> struct simdjson_result : public SIMDJSON_IMPLEMENTATION::implementation_simdjson_result_base { public: simdjson_inline simdjson_result(SIMDJSON_IMPLEMENTATION::ondemand::object &&value) noexcept; ///< @private simdjson_inline simdjson_result(error_code error) noexcept; ///< @private simdjson_inline simdjson_result() noexcept = default; simdjson_inline simdjson_result begin() noexcept; simdjson_inline simdjson_result end() noexcept; simdjson_inline simdjson_result find_field(std::string_view key) & noexcept; simdjson_inline simdjson_result find_field(std::string_view key) && noexcept; simdjson_inline simdjson_result find_field_unordered(std::string_view key) & noexcept; simdjson_inline simdjson_result find_field_unordered(std::string_view key) && noexcept; simdjson_inline simdjson_result operator[](std::string_view key) & noexcept; simdjson_inline simdjson_result operator[](std::string_view key) && noexcept; simdjson_inline simdjson_result at_pointer(std::string_view json_pointer) noexcept; simdjson_inline simdjson_result at_path(std::string_view json_path) noexcept; simdjson_inline simdjson_result> at_path_with_wildcard(std::string_view json_path) noexcept; inline simdjson_result reset() noexcept; inline simdjson_result is_empty() noexcept; inline simdjson_result count_fields() & noexcept; inline simdjson_result raw_json() noexcept; #if SIMDJSON_SUPPORTS_CONCEPTS // TODO: move this code into object-inl.h template simdjson_inline simdjson_result get() noexcept { if (error()) { return error(); } if constexpr (std::is_same_v) { return first; } return first.get(); } template simdjson_warn_unused simdjson_inline error_code get(T& out) noexcept { if (error()) { return error(); } if constexpr (std::is_same_v) { out = first; } else { SIMDJSON_TRY( first.get(out) ); } return SUCCESS; } #if SIMDJSON_STATIC_REFLECTION // TODO: move this code into object-inl.h template requires(std::is_class_v && (sizeof...(FieldNames) > 0)) simdjson_warn_unused simdjson_inline error_code extract_into(T& out) noexcept { if (error()) { return error(); } return first.extract_into(out); } #endif // SIMDJSON_STATIC_REFLECTION #endif // SIMDJSON_SUPPORTS_CONCEPTS }; } // namespace simdjson #endif // SIMDJSON_GENERIC_ONDEMAND_OBJECT_H