diff --git a/include/nlohmann/detail/json_pointer.hpp b/include/nlohmann/detail/json_pointer.hpp index 0608e023c..dd08b4c40 100644 --- a/include/nlohmann/detail/json_pointer.hpp +++ b/include/nlohmann/detail/json_pointer.hpp @@ -12,7 +12,9 @@ #include // isdigit #include // errno, ERANGE #include // size_t +#include // uint64_t #include // strtoull +#include // memcmp, memcpy #ifndef JSON_NO_IO #include // ostream #endif // JSON_NO_IO @@ -492,50 +494,111 @@ class json_pointer private: /*! - @brief the pointer prefixes of a flattened object, and which of them denote arrays + @brief the pointer prefixes of a flattened object that denote arrays - @ref unflatten collects the pointer prefixes that have a reference token 0 - among their children; @ref get_and_create creates arrays exactly below - those prefixes and objects everywhere else. Deciding this up front keeps - the result independent of the order in which the flattened object is - iterated, which is unspecified for some object types. + @ref unflatten creates arrays exactly below the prefixes that have a + reference token 0 among their children, and objects everywhere else. + Deciding this up front keeps the result independent of the order in which + the flattened object is iterated, which is unspecified for some object + types. - The prefixes form a tree and are numbered (number 0 is the empty prefix), - so each of them is stored only once (as a node) rather than as a copy of - all of its reference tokens. The tree does not own the reference tokens: - it refers to the tokens of the parsed keys, which must outlive it. + A prefix is identified by the text of the key it is the beginning of: + escaping is unambiguous, so equal prefixes of valid keys are equal texts. + The set refers to the keys, which must outlive it. */ - struct prefix_tree + class array_prefix_set { - // open-addressing hash table from (parent, last reference token) to - // the number of the node; empty slots have token == nullptr + public: + void insert(const char* data, std::size_t size) + { + // flattened objects are usually sorted, so most keys that have a + // 0 yield the prefix of the previous one + if (last_data != nullptr && last_size == size && std::memcmp(last_data, data, size) == 0) + { + return; + } + last_data = data; + last_size = size; + + if (slots.empty()) + { + slots.resize(16); + } + const std::size_t hash = hash_of(data, size); + const std::size_t mask = slots.size() - 1; + std::size_t i = hash & mask; + while (slots[i].data != nullptr) + { + if (slots[i].hash == hash && slots[i].size == size && std::memcmp(slots[i].data, data, size) == 0) + { + return; + } + i = (i + 1) & mask; + } + slots[i] = slot(hash, data, size); + + // keep the load factor below 1/2 + if (++used * 2 > slots.size()) + { + grow(); + } + } + + bool empty() const noexcept + { + return used == 0; + } + + bool contains(const char* data, std::size_t size) const noexcept + { + if (used == 0) + { + return false; + } + const std::size_t hash = hash_of(data, size); + const std::size_t mask = slots.size() - 1; + for (std::size_t i = hash & mask; slots[i].data != nullptr; i = (i + 1) & mask) + { + if (slots[i].hash == hash && slots[i].size == size && std::memcmp(slots[i].data, data, size) == 0) + { + return true; + } + } + return false; + } + + private: struct slot { - std::size_t hash; - std::size_t parent; - const string_t* token; - std::size_t id; + slot() noexcept = default; - slot() noexcept - : hash(0), parent(0), token(nullptr), id(0) + slot(std::size_t h, const char* d, std::size_t s) noexcept + : hash(h), data(d), size(s) {} - slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept - : hash(h), parent(p), token(t), id(i) - {} + std::size_t hash = 0; + const char* data = nullptr; // nullptr: the slot is empty + std::size_t size = 0; }; - std::vector slots; - std::size_t used = 0; - - static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept + static std::size_t hash_of(const char* data, std::size_t size) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & (std::numeric_limits::max)()); - for (std::size_t i = 0; i < token.size(); ++i) + std::uint64_t h = 0x9E3779B97F4A7C15ULL ^ size; + std::size_t i = 0; + for (; i + 8 <= size; i += 8) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & (std::numeric_limits::max)()); + std::uint64_t w = 0; + std::memcpy(&w, data + i, 8); + h = (h ^ w) * 0x100000001B3ULL; + h ^= h >> 32u; } - return h ^ (h >> 29u); + std::uint64_t w = 0; + if (i < size) + { + std::memcpy(&w, data + i, size - i); + } + h = (h ^ w) * 0x9E3779B97F4A7C15ULL; + return static_cast(h ^ (h >> 29u)); } void grow() @@ -545,10 +608,10 @@ class json_pointer const std::size_t mask = slots.size() - 1; for (const auto& s : old) { - if (s.token != nullptr) + if (s.data != nullptr) { std::size_t i = s.hash & mask; - while (slots[i].token != nullptr) + while (slots[i].data != nullptr) { i = (i + 1) & mask; } @@ -557,130 +620,95 @@ class json_pointer } } - // parent[id] is the number of the prefix without its last token - std::vector parent; - // is_array[id] is true iff some flattened key has the reference token - // 0 directly below the prefix with number id - std::vector is_array; - - // start with the empty prefix only - prefix_tree() - : slots(16) - , parent(1, 0) - , is_array(1, false) - {} - - // return the number of the prefix with number id extended by - // reference_token, adding it if it is new - std::size_t add_child(std::size_t id, const string_t& reference_token) - { - const std::size_t hash = hash_of(id, reference_token); - const std::size_t mask = slots.size() - 1; - std::size_t i = hash & mask; - while (slots[i].token != nullptr) - { - if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token) - { - return slots[i].id; - } - i = (i + 1) & mask; - } - - // new prefix - const std::size_t child = parent.size(); - slots[i] = slot(hash, id, &reference_token, child); - if (reference_token == "0") - { - is_array[id] = true; - } - parent.push_back(id); - is_array.push_back(false); - - // keep the load factor below 1/2 - if (++used * 2 > slots.size()) - { - grow(); - } - return child; - } + std::vector slots{}; + std::size_t used = 0; + const char* last_data = nullptr; + std::size_t last_size = 0; }; - /*! - @brief create and return a reference to the pointed to value - - @param[in] j the value to create the pointed to value in - @param[in] tokens the reference tokens of the pointer - @param[in] prefixes prefixes[i] is the number in @a tree of the prefix - consisting of the first i reference tokens - - Complexity: Linear in the number of reference tokens. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if array index is not a number - @throw type_error.313 if value cannot be unflattened - */ - template - static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector& tokens, - const std::vector& prefixes, const prefix_tree& tree) + /// @brief the length of the longest common prefix of the first @a limit bytes of @a a and @a b + static std::size_t common_prefix_length(const char* a, const char* b, const std::size_t limit) noexcept { - auto* result = &j; - - // in case no reference tokens exist, return a reference to the JSON value - // j which will be overwritten by a primitive value - for (std::size_t i = 0; i < tokens.size(); ++i) + std::size_t i = 0; + for (; i + 8 <= limit; i += 8) { - const auto& reference_token = tokens[i]; - switch (result->type()) + std::uint64_t x = 0; + std::uint64_t y = 0; + std::memcpy(&x, a + i, 8); + std::memcpy(&y, b + i, 8); + if (x != y) { - case detail::value_t::null: - { - if (tree.is_array[prefixes[i]]) - { - // some reference token below this position is 0, so the - // value is an array - result = &result->operator[](array_index(reference_token)); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } + break; + } + } + while (i < limit && a[i] == b[i]) + { + ++i; + } + return i; + } - case detail::value_t::object: - { - // create an entry in the object - result = &object_member(*result, reference_token); - break; - } + /*! + @brief check that @a key is a valid JSON pointer, and add the prefixes of + it that denote arrays to @a arrays - case detail::value_t::array: - { - // create an entry in the array - result = &result->operator[](array_index(reference_token)); - break; - } + @throw parse_error.107 if the key is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' - /* - The following code is only reached if there exists a reference - token _and_ the current value is primitive. In this case, we have - an error situation, because primitive values may only occur as - a single value; that is, with an empty list of reference tokens. - */ - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); + The same checks as @ref split, which throws the same exceptions, but + without building the reference tokens. + */ + static void scan_key(const string_t& key, const string_t* previous, array_prefix_set& arrays) + { + const std::size_t size = key.size(); + if (size == 0) + { + return; + } + + const char* const data = key.data(); + if (JSON_HEDLEY_UNLIKELY(data[0] != '/')) + { + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", key, "'"), nullptr)); + } + + // The beginning of the key that it has in common with the previous + // one, which was checked already, is skipped: the scan starts with + // the token after the last slash in it. + std::size_t begin = 1; + if (previous != nullptr) + { + std::size_t same = common_prefix_length(data, previous->data(), (std::min)(size, previous->size())); + while (same > 1 && data[same - 1] != '/') + { + --same; + } + if (same > 1) + { + begin = same; } } - return *result; + for (std::size_t i = begin; i <= size; ++i) + { + if (i == size || data[i] == '/') + { + // a token 0 makes the pointer before it an array + if (i - begin == 1 && data[begin] == '0') + { + arrays.insert(data, begin - 1); + } + begin = i + 1; + } + else if (data[i] == '~') + { + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(i + 1 == size || (data[i + 1] != '0' && data[i + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + } + } + } } /*! @@ -1338,53 +1366,30 @@ class json_pointer BasicJsonType result; - // parse every key once; the reference tokens are kept for the second - // pass (the vectors are never resized, so the tree may refer to them) - std::vector> keys; - keys.reserve(value.m_data.m_value.object->size()); + // check every key, and collect the prefixes that have a reference + // token 0 among their children; the values below them are arrays, + // all others are objects (see array_prefix_set) + array_prefix_set arrays; + const string_t* previous_key = nullptr; for (const auto& element : *value.m_data.m_value.object) { - json_pointer ptr(element.first); - keys.push_back(std::move(ptr.reference_tokens)); + scan_key(element.first, previous_key, arrays); + previous_key = &element.first; } - // collect the pointer prefixes that have a reference token 0 among - // their children; the values below them are arrays, all others are - // objects (see prefix_tree) - prefix_tree tree; - // the number of the prefix with i + 1 tokens of the previous key; keys - // are often sorted, so most of the prefix is the same as for the - // previous key and need not be looked up again - std::vector path; - const std::vector* previous = nullptr; - // the number of the complete prefix (i.e., the key) of each key - std::vector leaves; - leaves.reserve(keys.size()); - for (const auto& tokens : keys) - { - std::size_t common = 0; - if (previous != nullptr) - { - const std::size_t limit = (std::min)(tokens.size(), previous->size()); - while (common < limit && tokens[common] == (*previous)[common]) - { - ++common; - } - } - path.resize(common); - std::size_t id = common == 0 ? 0 : path[common - 1]; - for (std::size_t i = common; i < tokens.size(); ++i) - { - id = tree.add_child(id, tokens[i]); - path.push_back(id); - } - leaves.push_back(id); - previous = &tokens; - } + // values[i] is the value after the first i reference tokens of the + // previous key, ends[i] the length of that prefix of the key. Keys are + // often sorted, so most of the path to the value of a key is the + // same as for the previous key and need not be looked up again. Only + // the values below the common prefix are dropped, and only they can + // move when a value is added: the ones above are not inside the + // container that grows. + std::vector values(1, &result); + std::vector ends(1, 0); + const string_t* previous = nullptr; + string_t token; // iterate the JSON object values - std::vector prefixes; - std::size_t key_index = 0; for (const auto& element : *value.m_data.m_value.object) { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) @@ -1392,21 +1397,100 @@ class json_pointer JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); } - const auto& tokens = keys[key_index]; - // number the prefixes of this key from its last one to the first - prefixes.resize(tokens.size()); - for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i) + const string_t& key = element.first; + const char* const data = key.data(); + + // the number of reference tokens the key has in common with the + // previous one + std::size_t depth = 0; + if (previous != nullptr) { - id = tree.parent[id]; - prefixes[i - 1] = id; + const std::size_t same = common_prefix_length(data, previous->data(), (std::min)(key.size(), previous->size())); + depth = ends.size() - 1; + while (depth > 0 && (ends[depth] > same || (ends[depth] == same && same < key.size() && data[same] != '/'))) + { + --depth; + } } - ++key_index; + values.resize(depth + 1); + ends.resize(depth + 1); + previous = &key; // Assign the value to the reference pointed to by JSON pointer. Note // that if the JSON pointer is "" (i.e., points to the whole value), - // function get_and_create returns a reference to the result itself. - // An assignment will then create a primitive value. - get_and_create(result, tokens, prefixes, tree) = element.second; + // the value is the result itself. An assignment will then create a + // primitive value. + BasicJsonType* current = values.back(); + for (std::size_t pos = ends.back(); pos < key.size();) + { + // the reference token between the slash at pos and the next + std::size_t end = key.find_first_of('/', pos + 1); + if (end == string_t::npos) + { + end = key.size(); + } + token.clear(); + token.append(data + pos + 1, end - pos - 1); + if (token.find_first_of('~') != string_t::npos) + { + detail::unescape(token); + } + + switch (current->type()) + { + case detail::value_t::null: + { + if (arrays.contains(data, pos)) + { + // some reference token below this position is 0, so the + // value is an array + current = ¤t->operator[](array_index(token)); + } + else + { + // start a new object otherwise + current = ¤t->operator[](token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + current = &object_member(*current, token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + current = ¤t->operator[](array_index(token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + a single value; that is, with an empty list of reference tokens. + */ + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &result)); + } + + values.push_back(current); + ends.push_back(end); + pos = end; + } + + *current = element.second; } return result; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 6fdf062cc..3eba58e16 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20977,7 +20977,9 @@ NLOHMANN_JSON_NAMESPACE_END #include // isdigit #include // errno, ERANGE #include // size_t +#include // uint64_t #include // strtoull +#include // memcmp, memcpy #ifndef JSON_NO_IO #include // ostream #endif // JSON_NO_IO @@ -21464,50 +21466,111 @@ class json_pointer private: /*! - @brief the pointer prefixes of a flattened object, and which of them denote arrays + @brief the pointer prefixes of a flattened object that denote arrays - @ref unflatten collects the pointer prefixes that have a reference token 0 - among their children; @ref get_and_create creates arrays exactly below - those prefixes and objects everywhere else. Deciding this up front keeps - the result independent of the order in which the flattened object is - iterated, which is unspecified for some object types. + @ref unflatten creates arrays exactly below the prefixes that have a + reference token 0 among their children, and objects everywhere else. + Deciding this up front keeps the result independent of the order in which + the flattened object is iterated, which is unspecified for some object + types. - The prefixes form a tree and are numbered (number 0 is the empty prefix), - so each of them is stored only once (as a node) rather than as a copy of - all of its reference tokens. The tree does not own the reference tokens: - it refers to the tokens of the parsed keys, which must outlive it. + A prefix is identified by the text of the key it is the beginning of: + escaping is unambiguous, so equal prefixes of valid keys are equal texts. + The set refers to the keys, which must outlive it. */ - struct prefix_tree + class array_prefix_set { - // open-addressing hash table from (parent, last reference token) to - // the number of the node; empty slots have token == nullptr + public: + void insert(const char* data, std::size_t size) + { + // flattened objects are usually sorted, so most keys that have a + // 0 yield the prefix of the previous one + if (last_data != nullptr && last_size == size && std::memcmp(last_data, data, size) == 0) + { + return; + } + last_data = data; + last_size = size; + + if (slots.empty()) + { + slots.resize(16); + } + const std::size_t hash = hash_of(data, size); + const std::size_t mask = slots.size() - 1; + std::size_t i = hash & mask; + while (slots[i].data != nullptr) + { + if (slots[i].hash == hash && slots[i].size == size && std::memcmp(slots[i].data, data, size) == 0) + { + return; + } + i = (i + 1) & mask; + } + slots[i] = slot(hash, data, size); + + // keep the load factor below 1/2 + if (++used * 2 > slots.size()) + { + grow(); + } + } + + bool empty() const noexcept + { + return used == 0; + } + + bool contains(const char* data, std::size_t size) const noexcept + { + if (used == 0) + { + return false; + } + const std::size_t hash = hash_of(data, size); + const std::size_t mask = slots.size() - 1; + for (std::size_t i = hash & mask; slots[i].data != nullptr; i = (i + 1) & mask) + { + if (slots[i].hash == hash && slots[i].size == size && std::memcmp(slots[i].data, data, size) == 0) + { + return true; + } + } + return false; + } + + private: struct slot { - std::size_t hash; - std::size_t parent; - const string_t* token; - std::size_t id; + slot() noexcept = default; - slot() noexcept - : hash(0), parent(0), token(nullptr), id(0) + slot(std::size_t h, const char* d, std::size_t s) noexcept + : hash(h), data(d), size(s) {} - slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept - : hash(h), parent(p), token(t), id(i) - {} + std::size_t hash = 0; + const char* data = nullptr; // nullptr: the slot is empty + std::size_t size = 0; }; - std::vector slots; - std::size_t used = 0; - - static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept + static std::size_t hash_of(const char* data, std::size_t size) noexcept { - std::size_t h = parent * static_cast(0x9E3779B97F4A7C15ULL & (std::numeric_limits::max)()); - for (std::size_t i = 0; i < token.size(); ++i) + std::uint64_t h = 0x9E3779B97F4A7C15ULL ^ size; + std::size_t i = 0; + for (; i + 8 <= size; i += 8) { - h = (h ^ static_cast(token[i])) * static_cast(0x100000001B3ULL & (std::numeric_limits::max)()); + std::uint64_t w = 0; + std::memcpy(&w, data + i, 8); + h = (h ^ w) * 0x100000001B3ULL; + h ^= h >> 32u; } - return h ^ (h >> 29u); + std::uint64_t w = 0; + if (i < size) + { + std::memcpy(&w, data + i, size - i); + } + h = (h ^ w) * 0x9E3779B97F4A7C15ULL; + return static_cast(h ^ (h >> 29u)); } void grow() @@ -21517,10 +21580,10 @@ class json_pointer const std::size_t mask = slots.size() - 1; for (const auto& s : old) { - if (s.token != nullptr) + if (s.data != nullptr) { std::size_t i = s.hash & mask; - while (slots[i].token != nullptr) + while (slots[i].data != nullptr) { i = (i + 1) & mask; } @@ -21529,130 +21592,95 @@ class json_pointer } } - // parent[id] is the number of the prefix without its last token - std::vector parent; - // is_array[id] is true iff some flattened key has the reference token - // 0 directly below the prefix with number id - std::vector is_array; - - // start with the empty prefix only - prefix_tree() - : slots(16) - , parent(1, 0) - , is_array(1, false) - {} - - // return the number of the prefix with number id extended by - // reference_token, adding it if it is new - std::size_t add_child(std::size_t id, const string_t& reference_token) - { - const std::size_t hash = hash_of(id, reference_token); - const std::size_t mask = slots.size() - 1; - std::size_t i = hash & mask; - while (slots[i].token != nullptr) - { - if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token) - { - return slots[i].id; - } - i = (i + 1) & mask; - } - - // new prefix - const std::size_t child = parent.size(); - slots[i] = slot(hash, id, &reference_token, child); - if (reference_token == "0") - { - is_array[id] = true; - } - parent.push_back(id); - is_array.push_back(false); - - // keep the load factor below 1/2 - if (++used * 2 > slots.size()) - { - grow(); - } - return child; - } + std::vector slots{}; + std::size_t used = 0; + const char* last_data = nullptr; + std::size_t last_size = 0; }; - /*! - @brief create and return a reference to the pointed to value - - @param[in] j the value to create the pointed to value in - @param[in] tokens the reference tokens of the pointer - @param[in] prefixes prefixes[i] is the number in @a tree of the prefix - consisting of the first i reference tokens - - Complexity: Linear in the number of reference tokens. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if array index is not a number - @throw type_error.313 if value cannot be unflattened - */ - template - static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector& tokens, - const std::vector& prefixes, const prefix_tree& tree) + /// @brief the length of the longest common prefix of the first @a limit bytes of @a a and @a b + static std::size_t common_prefix_length(const char* a, const char* b, const std::size_t limit) noexcept { - auto* result = &j; - - // in case no reference tokens exist, return a reference to the JSON value - // j which will be overwritten by a primitive value - for (std::size_t i = 0; i < tokens.size(); ++i) + std::size_t i = 0; + for (; i + 8 <= limit; i += 8) { - const auto& reference_token = tokens[i]; - switch (result->type()) + std::uint64_t x = 0; + std::uint64_t y = 0; + std::memcpy(&x, a + i, 8); + std::memcpy(&y, b + i, 8); + if (x != y) { - case detail::value_t::null: - { - if (tree.is_array[prefixes[i]]) - { - // some reference token below this position is 0, so the - // value is an array - result = &result->operator[](array_index(reference_token)); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } + break; + } + } + while (i < limit && a[i] == b[i]) + { + ++i; + } + return i; + } - case detail::value_t::object: - { - // create an entry in the object - result = &object_member(*result, reference_token); - break; - } + /*! + @brief check that @a key is a valid JSON pointer, and add the prefixes of + it that denote arrays to @a arrays - case detail::value_t::array: - { - // create an entry in the array - result = &result->operator[](array_index(reference_token)); - break; - } + @throw parse_error.107 if the key is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' - /* - The following code is only reached if there exists a reference - token _and_ the current value is primitive. In this case, we have - an error situation, because primitive values may only occur as - a single value; that is, with an empty list of reference tokens. - */ - case detail::value_t::string: - case detail::value_t::boolean: - case detail::value_t::number_integer: - case detail::value_t::number_unsigned: - case detail::value_t::number_float: - case detail::value_t::binary: - case detail::value_t::discarded: - default: - JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j)); + The same checks as @ref split, which throws the same exceptions, but + without building the reference tokens. + */ + static void scan_key(const string_t& key, const string_t* previous, array_prefix_set& arrays) + { + const std::size_t size = key.size(); + if (size == 0) + { + return; + } + + const char* const data = key.data(); + if (JSON_HEDLEY_UNLIKELY(data[0] != '/')) + { + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", key, "'"), nullptr)); + } + + // The beginning of the key that it has in common with the previous + // one, which was checked already, is skipped: the scan starts with + // the token after the last slash in it. + std::size_t begin = 1; + if (previous != nullptr) + { + std::size_t same = common_prefix_length(data, previous->data(), (std::min)(size, previous->size())); + while (same > 1 && data[same - 1] != '/') + { + --same; + } + if (same > 1) + { + begin = same; } } - return *result; + for (std::size_t i = begin; i <= size; ++i) + { + if (i == size || data[i] == '/') + { + // a token 0 makes the pointer before it an array + if (i - begin == 1 && data[begin] == '0') + { + arrays.insert(data, begin - 1); + } + begin = i + 1; + } + else if (data[i] == '~') + { + // ~ must be followed by 0 or 1 + if (JSON_HEDLEY_UNLIKELY(i + 1 == size || (data[i + 1] != '0' && data[i + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr)); + } + } + } } /*! @@ -22310,53 +22338,30 @@ class json_pointer BasicJsonType result; - // parse every key once; the reference tokens are kept for the second - // pass (the vectors are never resized, so the tree may refer to them) - std::vector> keys; - keys.reserve(value.m_data.m_value.object->size()); + // check every key, and collect the prefixes that have a reference + // token 0 among their children; the values below them are arrays, + // all others are objects (see array_prefix_set) + array_prefix_set arrays; + const string_t* previous_key = nullptr; for (const auto& element : *value.m_data.m_value.object) { - json_pointer ptr(element.first); - keys.push_back(std::move(ptr.reference_tokens)); + scan_key(element.first, previous_key, arrays); + previous_key = &element.first; } - // collect the pointer prefixes that have a reference token 0 among - // their children; the values below them are arrays, all others are - // objects (see prefix_tree) - prefix_tree tree; - // the number of the prefix with i + 1 tokens of the previous key; keys - // are often sorted, so most of the prefix is the same as for the - // previous key and need not be looked up again - std::vector path; - const std::vector* previous = nullptr; - // the number of the complete prefix (i.e., the key) of each key - std::vector leaves; - leaves.reserve(keys.size()); - for (const auto& tokens : keys) - { - std::size_t common = 0; - if (previous != nullptr) - { - const std::size_t limit = (std::min)(tokens.size(), previous->size()); - while (common < limit && tokens[common] == (*previous)[common]) - { - ++common; - } - } - path.resize(common); - std::size_t id = common == 0 ? 0 : path[common - 1]; - for (std::size_t i = common; i < tokens.size(); ++i) - { - id = tree.add_child(id, tokens[i]); - path.push_back(id); - } - leaves.push_back(id); - previous = &tokens; - } + // values[i] is the value after the first i reference tokens of the + // previous key, ends[i] the length of that prefix of the key. Keys are + // often sorted, so most of the path to the value of a key is the + // same as for the previous key and need not be looked up again. Only + // the values below the common prefix are dropped, and only they can + // move when a value is added: the ones above are not inside the + // container that grows. + std::vector values(1, &result); + std::vector ends(1, 0); + const string_t* previous = nullptr; + string_t token; // iterate the JSON object values - std::vector prefixes; - std::size_t key_index = 0; for (const auto& element : *value.m_data.m_value.object) { if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) @@ -22364,21 +22369,100 @@ class json_pointer JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second)); } - const auto& tokens = keys[key_index]; - // number the prefixes of this key from its last one to the first - prefixes.resize(tokens.size()); - for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i) + const string_t& key = element.first; + const char* const data = key.data(); + + // the number of reference tokens the key has in common with the + // previous one + std::size_t depth = 0; + if (previous != nullptr) { - id = tree.parent[id]; - prefixes[i - 1] = id; + const std::size_t same = common_prefix_length(data, previous->data(), (std::min)(key.size(), previous->size())); + depth = ends.size() - 1; + while (depth > 0 && (ends[depth] > same || (ends[depth] == same && same < key.size() && data[same] != '/'))) + { + --depth; + } } - ++key_index; + values.resize(depth + 1); + ends.resize(depth + 1); + previous = &key; // Assign the value to the reference pointed to by JSON pointer. Note // that if the JSON pointer is "" (i.e., points to the whole value), - // function get_and_create returns a reference to the result itself. - // An assignment will then create a primitive value. - get_and_create(result, tokens, prefixes, tree) = element.second; + // the value is the result itself. An assignment will then create a + // primitive value. + BasicJsonType* current = values.back(); + for (std::size_t pos = ends.back(); pos < key.size();) + { + // the reference token between the slash at pos and the next + std::size_t end = key.find_first_of('/', pos + 1); + if (end == string_t::npos) + { + end = key.size(); + } + token.clear(); + token.append(data + pos + 1, end - pos - 1); + if (token.find_first_of('~') != string_t::npos) + { + detail::unescape(token); + } + + switch (current->type()) + { + case detail::value_t::null: + { + if (arrays.contains(data, pos)) + { + // some reference token below this position is 0, so the + // value is an array + current = ¤t->operator[](array_index(token)); + } + else + { + // start a new object otherwise + current = ¤t->operator[](token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + current = &object_member(*current, token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + current = ¤t->operator[](array_index(token)); + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + a single value; that is, with an empty list of reference tokens. + */ + case detail::value_t::string: + case detail::value_t::boolean: + case detail::value_t::number_integer: + case detail::value_t::number_unsigned: + case detail::value_t::number_float: + case detail::value_t::binary: + case detail::value_t::discarded: + default: + JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &result)); + } + + values.push_back(current); + ends.push_back(end); + pos = end; + } + + *current = element.second; } return result;