// Glaze Library // For the license information refer to glaze.hpp #pragma once #include #include "glaze/json/read.hpp" #include "glaze/json/skip.hpp" #include "glaze/json/write.hpp" #include "glaze/util/expected.hpp" namespace glz { // Forward declarations (needed for circular references) template struct lazy_document; template class lazy_iterator; template struct indexed_lazy_view; template class indexed_lazy_iterator; // ============================================================================ // Truly Lazy JSON Parser - No upfront processing // ============================================================================ namespace detail { // Skip string - returns position after closing quote template GLZ_ALWAYS_INLINE const char* skip_string_fast(const char* p, const char* end) noexcept { const char* const start = p; ++p; // skip opening quote // Find closing quote using memchr (SIMD-optimized in libc) while (p < end) { const char* q = static_cast(std::memchr(p, '"', static_cast(end - p))); if (!q) [[unlikely]] { return end; // unclosed string } // Count preceding backslashes to check if escaped size_t backslashes = 0; const char* check = q - 1; while (check > start && *check == '\\') { ++backslashes; --check; } p = q + 1; if ((backslashes & 1) == 0) { return p; // even backslashes = real quote } // odd backslashes = escaped quote, continue searching } return p; } // Skip from current position to depth zero (end of enclosing container) // Used after partial scanning to find container end efficiently template GLZ_ALWAYS_INLINE const char* skip_to_depth_zero(const char* p, const char* end, int depth) noexcept { using enum lazy_char_type; while (depth > 0) { if constexpr (Opts.null_terminated) { if (*p == '\0') break; } else { if (p >= end) break; } switch (lazy_char_class[uint8_t(*p)]) { case quote: p = skip_string_fast(p, end); break; case open: ++depth; ++p; break; case close: --depth; ++p; break; case number: ++p; if constexpr (Opts.null_terminated) { while (numeric_table[uint8_t(*p)]) ++p; } else { while (p < end && numeric_table[uint8_t(*p)]) ++p; } break; default: ++p; break; } } return p; } // Skip any JSON value - optimized container skip // Returns pointer after the value template GLZ_ALWAYS_INLINE const char* skip_value_lazy(const char* p, const char* end) noexcept { using enum lazy_char_type; switch (*p) { case '"': return skip_string_fast(p, end); case 't': return p + 4; case 'f': return p + 5; case 'n': return p + 4; case '[': case '{': { int depth = 1; ++p; while (depth > 0) { if constexpr (Opts.null_terminated) { if (*p == '\0') break; } else { if (p >= end) break; } switch (lazy_char_class[uint8_t(*p)]) { case quote: p = skip_string_fast(p, end); break; case open: ++depth; ++p; break; case close: --depth; ++p; break; case number: ++p; if constexpr (Opts.null_terminated) { while (numeric_table[uint8_t(*p)]) ++p; } else { while (p < end && numeric_table[uint8_t(*p)]) ++p; } break; default: ++p; break; } } return p; } default: // Number if constexpr (Opts.null_terminated) { while (numeric_table[uint8_t(*p)]) ++p; } else { while (p < end && numeric_table[uint8_t(*p)]) ++p; } return p; } } } // namespace detail // ============================================================================ // lazy_json_view - Truly lazy view with on-demand scanning // ============================================================================ /** * @brief A truly lazy view into JSON data. * * No upfront processing - all navigation uses SWAR-accelerated scanning. * * For objects, parse_pos_ tracks the current scan position to enable * efficient sequential key access (O(n) total instead of O(n²)). * * Memory layout (48 bytes on 64-bit, 24 bytes on 32-bit): * - doc_: pointer to document (8/4 bytes) * - data_: pointer to value start in JSON (8/4 bytes) * - parse_pos_: current scan position for progressive parsing (8/4 bytes) * - key_: stored key for iteration (16/8 bytes - string_view) * - error_: error code (4 bytes) * - padding: 4/0 bytes */ template struct lazy_json_view { private: const lazy_document* doc_{}; const char* data_{}; mutable const char* parse_pos_{}; // Current scan position (advances on key access) std::string_view key_{}; error_code error_{error_code::none}; lazy_json_view(error_code ec) noexcept : error_(ec) {} public: lazy_json_view() = default; lazy_json_view(const lazy_document* doc, const char* data) noexcept : doc_(doc), data_(data) {} [[nodiscard]] static lazy_json_view make_error(error_code ec) noexcept { return lazy_json_view{ec}; } [[nodiscard]] bool has_error() const noexcept { return error_ != error_code::none; } [[nodiscard]] error_code error() const noexcept { return error_; } // Type checking - direct from JSON byte [[nodiscard]] bool is_null() const noexcept { return has_error() || !data_ || *data_ == 'n'; } [[nodiscard]] bool is_boolean() const noexcept { return !has_error() && data_ && (*data_ == 't' || *data_ == 'f'); } [[nodiscard]] bool is_number() const noexcept { return !has_error() && data_ && (is_digit(uint8_t(*data_)) || *data_ == '-'); } [[nodiscard]] bool is_string() const noexcept { return !has_error() && data_ && *data_ == '"'; } [[nodiscard]] bool is_array() const noexcept { return !has_error() && data_ && *data_ == '['; } [[nodiscard]] bool is_object() const noexcept { return !has_error() && data_ && *data_ == '{'; } explicit operator bool() const noexcept { return !has_error() && data_ && *data_ != 'n'; } [[nodiscard]] const char* data() const noexcept { return data_; } [[nodiscard]] const char* json_end() const noexcept; /// @brief Get the raw JSON bytes for this value /// @return string_view of the raw JSON, or empty if error /// @note Useful for passing to glz::read_json to deserialize into a struct /// @note Consider using read_into() instead for better performance [[nodiscard]] std::string_view raw_json() const noexcept { if (has_error() || !data_) return {}; const char* end = detail::skip_value_lazy(data_, json_end()); return {data_, static_cast(end - data_)}; } /// @brief Parse this value directly into a C++ type (single-pass, no double scanning) /// @tparam T The type to parse into /// @param value The object to populate /// @return Error context (check error_ctx.error for success/failure) /// @note This is more efficient than raw_json() + read_json() as it avoids scanning twice template [[nodiscard]] error_ctx read_into(T& value) const { if (has_error()) { return error_ctx{0, error_}; } if (!data_) { return error_ctx{0, error_code::unexpected_end}; } // Use parse::op which naturally stops at value end // This avoids the need to pre-scan to find the value's extent context ctx{}; auto it = data_; auto end = json_end(); parse::op(value, ctx, it, end); finalize_read_context(ctx); if (bool(ctx.error)) { return error_ctx{static_cast(it - data_), ctx.error}; } return {}; } template [[nodiscard]] expected get() const; [[nodiscard]] lazy_json_view operator[](size_t index) const; [[nodiscard]] lazy_json_view operator[](std::string_view key) const; [[nodiscard]] bool contains(std::string_view key) const; [[nodiscard]] size_t size() const; [[nodiscard]] bool empty() const noexcept; // Key access for object iteration [[nodiscard]] std::string_view key() const noexcept { return key_; } [[nodiscard]] lazy_iterator begin() const; [[nodiscard]] lazy_iterator end() const; /// @brief Build an index for O(1) iteration and random access /// @return indexed_lazy_view with pre-computed element positions [[nodiscard]] indexed_lazy_view index() const; private: friend struct lazy_document; friend class lazy_iterator; friend struct indexed_lazy_view; friend class indexed_lazy_iterator; // Constructor with key for iteration lazy_json_view(const lazy_document* doc, const char* data, std::string_view key) noexcept : doc_(doc), data_(data), key_(key) {} // Skip whitespace helper // Note: The minified option is not used here because branch prediction // makes the whitespace loop essentially free when there's no whitespace. // Benchmarks show no benefit from skipping this check. static void skip_ws(const char*& p, [[maybe_unused]] const char* end) noexcept { if constexpr (Opts.null_terminated) { while (whitespace_table[uint8_t(*p)]) ++p; } else { while (p < end && whitespace_table[uint8_t(*p)]) ++p; } } // Parse a key from current position, return key and advance p static std::string_view parse_key(const char*& p, const char* end) noexcept; }; // ============================================================================ // lazy_document - Minimal container, no upfront processing // ============================================================================ template struct lazy_document { private: const char* json_{}; size_t len_{}; const char* root_data_{}; mutable lazy_json_view root_view_{}; // Cached root view with parse_pos_ friend struct lazy_json_view; friend class lazy_iterator; // Factory method for lazy_json template friend expected, error_ctx> lazy_json(Buffer&&); // Helper to initialize root_view_ with correct doc_ pointer void init_root_view() noexcept { root_view_ = lazy_json_view{this, root_data_}; } public: lazy_document() = default; // Copy constructor - fix doc_ pointer and preserve parse_pos_ lazy_document(const lazy_document& other) : json_(other.json_), len_(other.len_), root_data_(other.root_data_) { init_root_view(); root_view_.parse_pos_ = other.root_view_.parse_pos_; } lazy_document& operator=(const lazy_document& other) { if (this != &other) { json_ = other.json_; len_ = other.len_; root_data_ = other.root_data_; init_root_view(); root_view_.parse_pos_ = other.root_view_.parse_pos_; } return *this; } // Move constructor - fix doc_ pointer and transfer parse_pos_ lazy_document(lazy_document&& other) noexcept : json_(other.json_), len_(other.len_), root_data_(other.root_data_) { init_root_view(); root_view_.parse_pos_ = other.root_view_.parse_pos_; } lazy_document& operator=(lazy_document&& other) noexcept { if (this != &other) { json_ = other.json_; len_ = other.len_; root_data_ = other.root_data_; init_root_view(); root_view_.parse_pos_ = other.root_view_.parse_pos_; } return *this; } /// @brief Get the root view (cached, enables progressive key scanning) [[nodiscard]] lazy_json_view& root() noexcept { return root_view_; } [[nodiscard]] const lazy_json_view& root() const noexcept { return root_view_; } [[nodiscard]] lazy_json_view operator[](std::string_view key) const { return root_view_[key]; } [[nodiscard]] lazy_json_view operator[](size_t index) const { return root_view_[index]; } [[nodiscard]] bool is_null() const noexcept { return !root_data_ || *root_data_ == 'n'; } [[nodiscard]] bool is_array() const noexcept { return root_data_ && *root_data_ == '['; } [[nodiscard]] bool is_object() const noexcept { return root_data_ && *root_data_ == '{'; } explicit operator bool() const noexcept { return !is_null(); } [[nodiscard]] const char* json_data() const noexcept { return json_; } [[nodiscard]] size_t json_size() const noexcept { return len_; } /// @brief Reset parse position to beginning (for re-scanning from start) void reset_parse_pos() noexcept { root_view_.parse_pos_ = nullptr; } }; // ============================================================================ // lazy_iterator - Forward iterator with lazy scanning // ============================================================================ template class lazy_iterator { private: const lazy_document* doc_{}; const char* json_end_{}; char close_char_{}; bool is_object_{}; bool at_end_{true}; lazy_json_view current_view_{}; // Stored view for parse_pos_ optimization public: using iterator_category = std::forward_iterator_tag; using value_type = lazy_json_view; using difference_type = std::ptrdiff_t; using pointer = void; using reference = lazy_json_view&; lazy_iterator() = default; lazy_iterator(const lazy_document* doc, const char* container_start, const char* end, bool is_object); // Return reference to stored view - allows parse_pos_ optimization when user uses auto& reference operator*() { return current_view_; } const lazy_json_view& operator*() const { return current_view_; } lazy_iterator& operator++(); lazy_iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } bool operator==(const lazy_iterator& other) const { return at_end_ == other.at_end_; } bool operator!=(const lazy_iterator& other) const { return !(*this == other); } private: void advance_to_next_element(const char*& pos); void skip_ws(const char*& p) noexcept { if constexpr (Opts.null_terminated) { while (whitespace_table[uint8_t(*p)]) ++p; } else { while (p < json_end_ && whitespace_table[uint8_t(*p)]) ++p; } } }; // ============================================================================ // indexed_lazy_view - Lazy view with pre-built element index for O(1) access // ============================================================================ /** * @brief A lazy view with a pre-built index for O(1) iteration and random access. * * Created by calling `index()` on a lazy_json_view. Scans the container once * to build an index of element positions, then provides: * - O(1) iteration advancement (vs O(element_size) for lazy_json_view) * - O(1) random access by index * - O(1) size query * * Elements returned are still lazy_json_view objects, so nested access remains lazy. * * Memory: Base view + 8 bytes per element (+ 16 bytes per element for objects with keys) */ template struct indexed_lazy_view { private: const lazy_document* doc_{}; const char* json_end_{}; std::vector value_starts_; std::vector keys_; // Only populated for objects bool is_object_{}; friend class indexed_lazy_iterator; public: indexed_lazy_view() = default; /// @brief Number of elements - O(1) [[nodiscard]] size_t size() const noexcept { return value_starts_.size(); } /// @brief Check if empty - O(1) [[nodiscard]] bool empty() const noexcept { return value_starts_.empty(); } /// @brief Check if this is an indexed object [[nodiscard]] bool is_object() const noexcept { return is_object_; } /// @brief Check if this is an indexed array [[nodiscard]] bool is_array() const noexcept { return !is_object_; } /// @brief O(1) random access by index [[nodiscard]] lazy_json_view operator[](size_t index) const { if (index >= value_starts_.size()) { return lazy_json_view::make_error(error_code::exceeded_static_array_size); } std::string_view key = is_object_ ? keys_[index] : std::string_view{}; return lazy_json_view{doc_, value_starts_[index], key}; } /// @brief O(n) key lookup for objects (linear search in index) [[nodiscard]] lazy_json_view operator[](std::string_view key) const { if (!is_object_) { return lazy_json_view::make_error(error_code::get_wrong_type); } for (size_t i = 0; i < keys_.size(); ++i) { if (keys_[i] == key) { return lazy_json_view{doc_, value_starts_[i], keys_[i]}; } } return lazy_json_view::make_error(error_code::key_not_found); } /// @brief Check if object contains key - O(n) linear search [[nodiscard]] bool contains(std::string_view key) const { if (!is_object_) return false; for (const auto& k : keys_) { if (k == key) return true; } return false; } [[nodiscard]] indexed_lazy_iterator begin() const; [[nodiscard]] indexed_lazy_iterator end() const; private: friend struct lazy_json_view; // Private constructor used by lazy_json_view::index() indexed_lazy_view(const lazy_document* doc, const char* json_end, bool is_object) : doc_(doc), json_end_(json_end), is_object_(is_object) {} void reserve(size_t n) { value_starts_.reserve(n); if (is_object_) { keys_.reserve(n); } } void add_element(const char* value_start, std::string_view key = {}) { value_starts_.push_back(value_start); if (is_object_) { keys_.push_back(key); } } }; // ============================================================================ // indexed_lazy_iterator - O(1) advancement using pre-built index // ============================================================================ template class indexed_lazy_iterator { private: const indexed_lazy_view* parent_{}; size_t index_{}; mutable lazy_json_view current_view_{}; // Cached for reference return public: using iterator_category = std::random_access_iterator_tag; using value_type = lazy_json_view; using difference_type = std::ptrdiff_t; using pointer = void; using reference = lazy_json_view&; indexed_lazy_iterator() = default; indexed_lazy_iterator(const indexed_lazy_view* parent, size_t index) : parent_(parent), index_(index) {} reference operator*() const { std::string_view key = parent_->is_object_ ? parent_->keys_[index_] : std::string_view{}; current_view_ = lazy_json_view{parent_->doc_, parent_->value_starts_[index_], key}; return current_view_; } lazy_json_view* operator->() const { operator*(); // Update current_view_ return ¤t_view_; } indexed_lazy_iterator& operator++() { ++index_; return *this; } indexed_lazy_iterator operator++(int) { auto tmp = *this; ++index_; return tmp; } indexed_lazy_iterator& operator--() { --index_; return *this; } indexed_lazy_iterator operator--(int) { auto tmp = *this; --index_; return tmp; } indexed_lazy_iterator& operator+=(difference_type n) { index_ = static_cast(static_cast(index_) + n); return *this; } indexed_lazy_iterator& operator-=(difference_type n) { index_ = static_cast(static_cast(index_) - n); return *this; } indexed_lazy_iterator operator+(difference_type n) const { return {parent_, static_cast(static_cast(index_) + n)}; } indexed_lazy_iterator operator-(difference_type n) const { return {parent_, static_cast(static_cast(index_) - n)}; } difference_type operator-(const indexed_lazy_iterator& other) const { return static_cast(index_) - static_cast(other.index_); } reference operator[](difference_type n) const { std::string_view key = parent_->is_object_ ? parent_->keys_[index_ + static_cast(n)] : std::string_view{}; current_view_ = lazy_json_view{parent_->doc_, parent_->value_starts_[index_ + static_cast(n)], key}; return current_view_; } bool operator==(const indexed_lazy_iterator& other) const { return index_ == other.index_; } bool operator!=(const indexed_lazy_iterator& other) const { return index_ != other.index_; } bool operator<(const indexed_lazy_iterator& other) const { return index_ < other.index_; } bool operator<=(const indexed_lazy_iterator& other) const { return index_ <= other.index_; } bool operator>(const indexed_lazy_iterator& other) const { return index_ > other.index_; } bool operator>=(const indexed_lazy_iterator& other) const { return index_ >= other.index_; } friend indexed_lazy_iterator operator+(difference_type n, const indexed_lazy_iterator& it) { return it + n; } }; // ============================================================================ // Implementation: lazy_json_view methods (truly lazy - no structural index) // ============================================================================ template inline const char* lazy_json_view::json_end() const noexcept { return doc_ ? doc_->json_ + doc_->len_ : nullptr; } template inline std::string_view lazy_json_view::parse_key(const char*& p, const char* end) noexcept { if (*p != '"') return {}; const char* const start = p; ++p; // skip opening quote const char* key_start = p; while (p < end) { const char* quote = static_cast(std::memchr(p, '"', static_cast(end - p))); if (!quote) [[unlikely]] { p = end; return std::string_view{key_start, static_cast(end - key_start)}; } // Count preceding backslashes to check if escaped size_t backslashes = 0; const char* check = quote - 1; while (check > start && *check == '\\') { ++backslashes; --check; } if ((backslashes & 1) == 0) { // Even backslashes = real quote std::string_view key{key_start, static_cast(quote - key_start)}; p = quote + 1; // skip closing quote return key; } // Odd backslashes = escaped quote, continue searching p = quote + 1; } return std::string_view{key_start, static_cast(p - key_start)}; } template inline lazy_json_view lazy_json_view::operator[](size_t index) const { if (has_error()) return *this; if (!is_array()) return make_error(error_code::get_wrong_type); const char* end = json_end(); const char* p = data_ + 1; // skip '[' skip_ws(p, end); // Check for empty array if constexpr (Opts.null_terminated) { if (*p == ']') return make_error(error_code::exceeded_static_array_size); } else { if (p >= end || *p == ']') return make_error(error_code::exceeded_static_array_size); } // Skip 'index' elements using lazy scanning for (size_t i = 0; i < index; ++i) { p = detail::skip_value_lazy(p, end); skip_ws(p, end); if constexpr (Opts.null_terminated) { if (*p == ']') return make_error(error_code::exceeded_static_array_size); } else { if (p >= end || *p == ']') return make_error(error_code::exceeded_static_array_size); } if (*p == ',') { ++p; skip_ws(p, end); } } return {doc_, p}; } template inline lazy_json_view lazy_json_view::operator[](std::string_view key) const { if (has_error()) return *this; if (!is_object()) return make_error(error_code::get_wrong_type); const char* end = json_end(); const char* obj_start = data_ + 1; // skip '{' // Determine search start position // parse_pos_ points to the VALUE of the last found key (lazy skip) // We need to skip past it before continuing the search const char* search_start = obj_start; if (parse_pos_ && parse_pos_ > data_) { // Lazily skip past the last found value now const char* p = parse_pos_; p = detail::skip_value_lazy(p, end); skip_ws(p, end); if (*p == ',') { ++p; skip_ws(p, end); } search_start = p; } const char* p = search_start; skip_ws(p, end); // Forward pass: search from current position to end of object while (true) { // Check for end of object if constexpr (Opts.null_terminated) { if (*p == '}') break; } else { if (p >= end || *p == '}') break; } // Parse key auto k = parse_key(p, end); skip_ws(p, end); // Skip ':' if (*p == ':') { ++p; skip_ws(p, end); } // Check if key matches if (k == key) { parse_pos_ = p; // Store value position (lazy - don't skip yet) return {doc_, p}; } // Skip value using lazy scanning p = detail::skip_value_lazy(p, end); skip_ws(p, end); // Skip comma if (*p == ',') { ++p; skip_ws(p, end); } } // Wrap-around pass: search from beginning to where we started if (search_start != obj_start) { p = obj_start; skip_ws(p, end); while (p < search_start) { // Check for end of object if constexpr (Opts.null_terminated) { if (*p == '}') break; } else { if (p >= end || *p == '}') break; } // Parse key auto k = parse_key(p, end); skip_ws(p, end); // Skip ':' if (*p == ':') { ++p; skip_ws(p, end); } // Check if key matches if (k == key) { parse_pos_ = p; // Store value position (lazy - don't skip yet) return {doc_, p}; } // Skip value using lazy scanning p = detail::skip_value_lazy(p, end); skip_ws(p, end); // Skip comma if (*p == ',') { ++p; skip_ws(p, end); } } } return make_error(error_code::key_not_found); } template inline bool lazy_json_view::contains(std::string_view key) const { auto result = (*this)[key]; return !result.has_error(); } template inline size_t lazy_json_view::size() const { if (has_error() || !data_) return 0; if (!is_array() && !is_object()) return 0; const char* end = json_end(); const char* p = data_ + 1; skip_ws(p, end); const char close_char = is_array() ? ']' : '}'; if constexpr (Opts.null_terminated) { if (*p == close_char) return 0; } else { if (p >= end || *p == close_char) return 0; } size_t count = 0; const bool is_obj = is_object(); while (true) { if (is_obj) { // Skip key p = detail::skip_string_fast(p, end); skip_ws(p, end); if (*p == ':') { ++p; skip_ws(p, end); } } // Skip value p = detail::skip_value_lazy(p, end); ++count; skip_ws(p, end); if constexpr (Opts.null_terminated) { if (*p == close_char) return count; } else { if (p >= end || *p == close_char) return count; } if (*p == ',') { ++p; skip_ws(p, end); } } } template inline bool lazy_json_view::empty() const noexcept { if (has_error() || !data_) return true; if (is_null()) return true; if (!is_array() && !is_object()) return false; const char* end = json_end(); const char* p = data_ + 1; skip_ws(p, end); const char close_char = is_array() ? ']' : '}'; if constexpr (Opts.null_terminated) { return *p == close_char; } else { return p >= end || *p == close_char; } } // ============================================================================ // lazy_iterator implementation (truly lazy) // ============================================================================ template inline lazy_iterator::lazy_iterator(const lazy_document* doc, const char* container_start, const char* end, bool is_object) : doc_(doc), json_end_(end), is_object_(is_object), at_end_(false) { close_char_ = is_object ? '}' : ']'; const char* pos = container_start + 1; // skip '[' or '{' skip_ws(pos); // Check for empty container if constexpr (Opts.null_terminated) { if (*pos == close_char_) { at_end_ = true; return; } } else { if (pos >= json_end_ || *pos == close_char_) { at_end_ = true; return; } } // Position at first element and initialize current_view_ advance_to_next_element(pos); } template inline void lazy_iterator::advance_to_next_element(const char*& pos) { std::string_view key{}; if (is_object_) { // Parse key key = lazy_json_view::parse_key(pos, json_end_); skip_ws(pos); // Skip ':' if (*pos == ':') { ++pos; skip_ws(pos); } } // Store key in current_view_ (will be set properly after this call) current_view_ = lazy_json_view{doc_, pos, key}; } template inline lazy_iterator& lazy_iterator::operator++() { if (at_end_) return *this; const char* pos = current_view_.data(); // Optimization: if user accessed fields via operator[], parse_pos_ tells us how far we scanned // We can skip from there instead of re-scanning the entire element // Note: check current_view_.is_object(), not is_object_ (which is about the container) if (current_view_.is_object() && current_view_.parse_pos_ && current_view_.parse_pos_ > pos) { // Skip the last accessed value, then scan to end of object pos = current_view_.parse_pos_; pos = detail::skip_value_lazy(pos, json_end_); // Skip remaining content until we close this object (depth 1 -> 0) pos = detail::skip_to_depth_zero(pos, json_end_, 1); } else { // No optimization possible, skip entire value pos = detail::skip_value_lazy(pos, json_end_); } skip_ws(pos); // Check for end if constexpr (Opts.null_terminated) { if (*pos == close_char_) { at_end_ = true; return *this; } } else { if (pos >= json_end_ || *pos == close_char_) { at_end_ = true; return *this; } } // Skip comma if (*pos == ',') { ++pos; skip_ws(pos); } // Advance to next element (updates current_view_) advance_to_next_element(pos); return *this; } template inline lazy_iterator lazy_json_view::begin() const { if (has_error() || !data_) return end(); if (!is_array() && !is_object()) return end(); return lazy_iterator{doc_, data_, json_end(), is_object()}; } template inline lazy_iterator lazy_json_view::end() const { return lazy_iterator{}; } // ============================================================================ // indexed_lazy_view implementation // ============================================================================ template inline indexed_lazy_iterator indexed_lazy_view::begin() const { return indexed_lazy_iterator{this, 0}; } template inline indexed_lazy_iterator indexed_lazy_view::end() const { return indexed_lazy_iterator{this, value_starts_.size()}; } // ============================================================================ // lazy_json_view::index() implementation // ============================================================================ template inline indexed_lazy_view lazy_json_view::index() const { // Return empty indexed view for non-containers or errors if (has_error() || !data_ || (!is_array() && !is_object())) { return indexed_lazy_view{}; } const char* end = json_end(); indexed_lazy_view result{doc_, end, is_object()}; const char* p = data_ + 1; // skip '[' or '{' skip_ws(p, end); const char close_char = is_array() ? ']' : '}'; const bool is_obj = is_object(); // Check for empty container if constexpr (Opts.null_terminated) { if (*p == close_char) return result; } else { if (p >= end || *p == close_char) return result; } // Scan and record all element positions while (true) { std::string_view key{}; if (is_obj) { // Parse and record key key = parse_key(p, end); skip_ws(p, end); // Skip ':' if (*p == ':') { ++p; skip_ws(p, end); } } // Record element start position result.add_element(p, key); // Skip value p = detail::skip_value_lazy(p, end); skip_ws(p, end); // Check for end of container if constexpr (Opts.null_terminated) { if (*p == close_char) break; } else { if (p >= end || *p == close_char) break; } // Skip comma if (*p == ',') { ++p; skip_ws(p, end); } } return result; } // ============================================================================ // lazy_json_view::get() implementation // ============================================================================ template template [[nodiscard]] inline expected lazy_json_view::get() const { if (has_error()) { return unexpected(error_ctx{0, error_}); } const char* end = json_end(); if constexpr (std::is_same_v) { if (!is_boolean()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } return *data_ == 't'; } else if constexpr (std::is_same_v) { if (!is_null()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } return nullptr; } else if constexpr (std::is_same_v) { if (!is_string()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } auto it = data_; context ctx{}; skip_value::op(ctx, it, end); if (bool(ctx.error)) { return unexpected(error_ctx{0, ctx.error}); } std::string_view raw{data_, static_cast(it - data_)}; return glz::read_json(raw); } else if constexpr (std::is_same_v) { if (!is_string()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } auto it = data_ + 1; context ctx{}; skip_string_view(ctx, it, end); if (bool(ctx.error)) { return unexpected(error_ctx{0, ctx.error}); } return std::string_view{data_ + 1, static_cast(it - data_ - 1)}; } else if constexpr (std::is_same_v) { if (!is_number()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } double value{}; auto [ptr, ec] = glz::from_chars(data_, end, value); if (ec != std::errc()) { return unexpected(error_ctx{0, error_code::parse_number_failure}); } return value; } else if constexpr (std::is_same_v) { if (!is_number()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } float value{}; auto [ptr, ec] = glz::from_chars(data_, end, value); if (ec != std::errc()) { return unexpected(error_ctx{0, error_code::parse_number_failure}); } return value; } else if constexpr (std::is_same_v) { if (!is_number()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } int64_t value{}; auto it = data_; if (!glz::atoi(value, it, end)) { return unexpected(error_ctx{0, error_code::parse_number_failure}); } return value; } else if constexpr (std::is_same_v) { if (!is_number()) { return unexpected(error_ctx{0, error_code::get_wrong_type}); } uint64_t value{}; auto it = data_; if (!glz::atoi(value, it, end)) { return unexpected(error_ctx{0, error_code::parse_number_failure}); } return value; } else if constexpr (std::is_same_v) { auto result = get(); if (!result) return unexpected(result.error()); return static_cast(*result); } else if constexpr (std::is_same_v) { auto result = get(); if (!result) return unexpected(result.error()); return static_cast(*result); } else { static_assert(false_v, "Unsupported type for lazy_json_view::get()"); } } // ============================================================================ // JSON writer for lazy_json_view // ============================================================================ template struct to> { template GLZ_ALWAYS_INLINE static void op(const lazy_json_view& view, is_context auto&& ctx, B&& b, auto& ix) { if (view.has_error()) { ctx.error = view.error(); return; } if (!view.data()) { dump("null", b, ix); return; } auto it = view.data(); context parse_ctx{}; skip_value::op(parse_ctx, it, view.json_end()); if (bool(parse_ctx.error)) [[unlikely]] { ctx.error = parse_ctx.error; return; } const size_t n = static_cast(it - view.data()); if constexpr (resizable) { if (ix + n > b.size()) [[unlikely]] { b.resize((std::max)(b.size() * 2, ix + n)); } } else { if (ix + n > b.size()) [[unlikely]] { ctx.error = error_code::buffer_overflow; return; } } std::memcpy(&b[ix], view.data(), n); ix += n; } }; // ============================================================================ // lazy_json - Main entry point (truly lazy - minimal upfront work) // ============================================================================ /** * @brief Create a lazy JSON document - no upfront processing. * * - Just validates first byte and stores buffer reference - O(1) * - All work happens on-demand when accessing fields * * @tparam Opts Options for parsing * @param buffer The JSON text buffer (must remain valid for document lifetime) * @return lazy_document on success, error_ctx on failure */ template [[nodiscard]] inline expected, error_ctx> lazy_json(Buffer&& buffer) { lazy_document doc; doc.json_ = buffer.data(); doc.len_ = buffer.size(); // Find root value (skip leading whitespace) const char* p = buffer.data(); const char* end = buffer.data() + buffer.size(); if constexpr (Opts.null_terminated) { while (whitespace_table[uint8_t(*p)]) ++p; } else { while (p < end && whitespace_table[uint8_t(*p)]) ++p; } if (p >= end) { return unexpected(error_ctx{0, error_code::unexpected_end}); } // Validate first character is valid JSON start const char c = *p; if (c != '{' && c != '[' && c != '"' && c != 't' && c != 'f' && c != 'n' && !is_digit(uint8_t(c)) && c != '-') { return unexpected(error_ctx{0, error_code::syntax_error}); } doc.root_data_ = p; doc.init_root_view(); // Initialize cached root view return doc; } // ============================================================================ // read_json overload for lazy_json_view (single-pass deserialization) // ============================================================================ /// @brief Read JSON from a lazy_json_view into a C++ type /// @tparam T The type to parse into /// @tparam Opts The lazy view options /// @param value The object to populate /// @param view The lazy view to read from /// @return Error context if parsing failed /// @note This provides the familiar glz::read_json API while using the efficient single-pass read_into internally template [[nodiscard]] inline error_ctx read_json(T& value, const lazy_json_view& view) { return view.template read_into(value); } /// @brief Read JSON from a lazy_json_view rvalue into a C++ type /// @note Handles temporaries like glz::read_json(value, doc["field"]) template [[nodiscard]] inline error_ctx read_json(T& value, lazy_json_view&& view) { return view.template read_into(value); } } // namespace glz