mirror of
https://github.com/nlohmann/json.git
synced 2026-10-06 22:47:13 +00:00
Add editable json_documents: set, push_back, insert, and erase
basic_json_document gets a second template parameter, Editable (false by default), plus the aliases json_editable_document, json_editable_view, ordered_json_editable_document and ordered_json_editable_view. Editable documents can change values and structure without rewriting the source text: set()/push_back() on values, keys, array indices and JSON pointers; insert() before an array element; erase() of an object key, array index or JSON pointer. New values and element sequences go into edit storage that the document owns and never moves, so views keep referring to their value across edits and a parsed node never moves. Read-only documents walk the plain node array and are unaffected. Strings are checked for UTF-8 on entry, so dump() of an editable document never throws type_error.316. Binary values cannot be stored (type_error.319). A seeded differential test applies random edits to an editable document and to the equivalent ordered_json and compares both after every step. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
6ee5e89803
commit
a0b98325cf
43 files changed
+4969
-496
No files matched your search
@@ -12,6 +12,9 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <functional> // less
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#include <new> // operator new, placement new
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
@@ -50,9 +53,30 @@ struct document_data
|
||||
std::vector<object_index> indexes{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint32_t> index_slots{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::uint32_t> large_objects{}; ///< positions of the objects to index (noted while parsing) // NOLINT(readability-redundant-member-init)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena (indexed by flags & node_flags::storage)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage)
|
||||
bool discarded = true;
|
||||
|
||||
/// The storage of edits (editable documents only; see edit_storage.hpp).
|
||||
/// Edits never move or resize the parsed index, so views stay valid: an
|
||||
/// array/object whose elements change gets node_flags::moved, and its
|
||||
/// elements then live in a separate sequence (a header node, then the
|
||||
/// entries), whose entries link to the values.
|
||||
struct edit_state
|
||||
{
|
||||
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::size_t> moved_cap{}; ///< capacity in nodes of a growable block; 0: a fixed sequence (a new value) // NOLINT(readability-redundant-member-init)
|
||||
std::vector<std::unique_ptr<node[]>> chunks{}; ///< storage of new values and blocks; never moved // NOLINT(readability-redundant-member-init,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::map<const node*, node*, std::less<const node*>> regions{}; ///< new arrays/objects: root -> container that uses it as its element sequence (nullptr: linked from a block) // NOLINT(readability-redundant-member-init)
|
||||
node* chunk_cur = nullptr;
|
||||
node* chunk_end = nullptr;
|
||||
std::size_t chunk_next = 64;
|
||||
std::vector<std::unique_ptr<char[]>> texts{}; ///< edit arena, the current buffer last; earlier ones stay alive for string views // NOLINT(readability-redundant-member-init,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
std::size_t text_used = 0;
|
||||
std::size_t text_cap = 0;
|
||||
std::size_t bytes = 0; ///< memory held by edits
|
||||
};
|
||||
std::unique_ptr<edit_state> edits{}; ///< created by the first edit // NOLINT(readability-redundant-member-init)
|
||||
|
||||
/// one allocation for the header and room for `nodes` nodes; large
|
||||
/// documents get a separate node array instead (so it can be trimmed)
|
||||
static document_data* create(std::size_t nodes)
|
||||
@@ -136,6 +160,78 @@ struct document_data
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
|
||||
/// (editable documents) first element or key, also of a moved container
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* first_child_edited(const node* n) const noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_LIKELY((n->flags & node_flags::moved) == 0) ? n + 1 : edits->moved[n->off] + 1;
|
||||
}
|
||||
|
||||
/// (editable documents) end of the elements, also of a moved container
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* child_end_edited(const node* n) const noexcept
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY((n->flags & node_flags::moved) == 0))
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
const node* const h = edits->moved[n->off];
|
||||
return h + h->next;
|
||||
}
|
||||
|
||||
/// (editable documents) the value at an element position: entries of
|
||||
/// moved sequences are links. The link case is out of line, so that this
|
||||
/// compiles to a predicted branch rather than a select that delays the
|
||||
/// following loads.
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* deref(const node* n) noexcept
|
||||
{
|
||||
return NLOHMANN_VIEW_LIKELY(n->kind != kind_link) ? n : follow_link(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_NOINLINE const node* follow_link(const node* n) noexcept
|
||||
{
|
||||
return link_target(*n);
|
||||
}
|
||||
};
|
||||
|
||||
/// How the index is walked: views of read-only documents follow the node
|
||||
/// array alone and compile without any of the edit handling; views of
|
||||
/// editable documents also follow moved element sequences and links.
|
||||
template<bool Editable>
|
||||
struct navigation
|
||||
{
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first(const document_data& /*d*/, const node* n) noexcept
|
||||
{
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* end(const document_data& /*d*/, const node* n) noexcept
|
||||
{
|
||||
return n + n->next;
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* value(const node* n) noexcept
|
||||
{
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct navigation<true>
|
||||
{
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* first(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.first_child_edited(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* end(const document_data& d, const node* n) noexcept
|
||||
{
|
||||
return d.child_end_edited(n);
|
||||
}
|
||||
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* value(const node* n) noexcept
|
||||
{
|
||||
return document_data::deref(n);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
|
||||
Reference in new issue
Block a user