88
99The ` cbor::view ` namespace reads the * encoded* structure of
1010[ Concise Binary Object Representation] ( http://cbor.io/ ) data in place,
11- without copying it or building a data structure. Its spine is four stages:
12-
13- > owned bytes -> structural validation -> borrowed checked items -> application semantics
14-
15- A scan validates that bytes are * structurally* well-formed CBOR once
16- (framing only — not validity such as UTF-8); everything afterwards
17- operates on checked ` item ` views and cannot fail structurally. The last
18- stage — what tag 2 bytes or a tag 25 string reference * mean* — belongs to
19- [ decode_cbor] ( decode_cbor.md ) and [ basic_cbor_cursor] ( basic_cbor_cursor.md ) ,
20- not here: tags are exposed, never interpreted.
11+ without copying it or building a data structure. Its checked-item layer
12+ borrows complete encodings; its move-only ` navigator ` owns reusable traversal
13+ workspace while borrowing the same bytes. The data flow is:
14+ > owned bytes -> structural validation -> checked item or navigator -> application semantics
15+
16+ Validation checks structural well-formedness once (framing only — not content
17+ validity such as UTF-8). Checked items and navigator movement then cannot fail
18+ structurally. The last stage — what tag 2 bytes or a tag 25 string reference
19+ * mean* — belongs to [ decode_cbor] ( decode_cbor.md ) and
20+ [ basic_cbor_cursor] ( basic_cbor_cursor.md ) , not here: tags are exposed, never
21+ interpreted.
2122
2223This namespace is experimental.
2324
2425#### Ownership and lifetime
2526
26- Everything in this namespace borrows. An ` item ` , the items produced by
27- iterating it, and every span and string view obtained from them point
28- into the scanned input bytes, and are invalidated by anything that
29- invalidates those bytes: destroying the container, mutating it, or any
30- reallocation. Scanning a temporary container is rejected at compile
31- time. The copying accessors (` text ` into ` std::string ` , ` bytes ` into
32- ` std::vector ` ) are the way to keep content beyond the input's lifetime.
27+ All views borrow the input bytes. An ` item ` , a ` navigator ` , and every span or
28+ string view obtained from them are
29+ invalidated by anything that invalidates those bytes: destroying the container,
30+ mutating it, or reallocation. A navigator owns only its mutable navigation and
31+ validation workspace; it does not own the encoded bytes. Factory, reset, and
32+ ` wire_cursor ` entry points reject temporary owning containers at compile time.
33+ The copying item accessors (` text ` into ` std::string ` , ` bytes ` into
34+ ` std::vector ` ) retain content independently.
3335
3436#### Scanning
3537
@@ -62,8 +64,93 @@ anything after it as `cbor_errc::trailing_data`.
6264
6365Scanning enforces `scan_context::max_nesting_depth` (default
6466`default_max_nesting_depth`, 1024) using constant call-stack space at
65- any depth. It allocates only when nesting exceeds 32 open containers;
66- a reused `scan_context` retains that capacity across scans.
67+ any depth. It allocates only when nesting exceeds 32 open containers; a reused
68+ `scan_context`, `wire_cursor` with a supplied context, or navigator reset retains
69+ that validation capacity.
70+
71+ #### Structural navigation
72+
73+ ```cpp
74+ enum class position_role { root, array_element, map_key, map_value };
75+
76+ struct navigation_result
77+ {
78+ navigator first;
79+ span<const uint8_t> remainder;
80+ };
81+
82+ expected<navigation_result, scan_error> navigate_prefix(
83+ span<const uint8_t> input,
84+ int max_nesting_depth = default_max_nesting_depth);
85+
86+ expected<navigator, scan_error> navigate_exact(
87+ span<const uint8_t> input,
88+ int max_nesting_depth = default_max_nesting_depth);
89+
90+ class navigator
91+ {
92+ public:
93+ navigator(navigator&&) noexcept;
94+ navigator& operator=(navigator&&) noexcept;
95+ navigator(const navigator&) = delete;
96+
97+ item_kind kind() const noexcept;
98+ uint64_t argument() const noexcept;
99+ bool indefinite() const noexcept;
100+ tag_range tags() const noexcept;
101+ position_role role() const noexcept;
102+ std::size_t depth() const noexcept;
103+
104+ bool uint64_value(uint64_t&) const noexcept;
105+ bool int64_value(int64_t&) const noexcept;
106+ bool bool_value(bool&) const noexcept;
107+ bool double_value(double&) const noexcept;
108+ bool text(string_view&) const noexcept;
109+ bool bytes(span<const uint8_t>&) const noexcept;
110+
111+ bool enter() noexcept;
112+ bool next() noexcept;
113+ bool leave() noexcept;
114+ void rewind() noexcept;
115+
116+ item finish_item() noexcept;
117+ bool extent_known() const noexcept;
118+
119+ expected<span<const uint8_t>, scan_error> reset_prefix(
120+ span<const uint8_t> input,
121+ int max_nesting_depth = default_max_nesting_depth);
122+ expected<span<const uint8_t>, scan_error> reset_exact(
123+ span<const uint8_t> input,
124+ int max_nesting_depth = default_max_nesting_depth);
125+ };
126+ ```
127+
128+ A navigator begins at the checked root. ` enter() ` moves into a nonempty array
129+ or map; maps expose raw children with alternating key/value roles. ` next() `
130+ moves to the next raw sibling, skipping the current unopened container if
131+ necessary. At the end of the siblings it returns ` false ` ; ` leave() ` restores
132+ the completed parent. Calling ` leave() ` early skips only the unread remainder.
133+ ` rewind() ` restores the root.
134+
135+ The current position exposes its already parsed head and scalar/string content.
136+ A container child may not yet have a known end. ` finish_item() ` establishes and
137+ caches that end, walking the current subtree only when necessary, and returns an
138+ ordinary complete ` item ` . ` extent_known() ` makes that cost visible. Descent after
139+ ` finish_item() ` is legal and revisits the subtree.
140+
141+ Validation records the observed peak open-container depth, prepares that many
142+ navigation frames, and retains the validation scratch. Movement uses indexed
143+ frames; subtree skips reuse the retained scratch. Neither allocates after
144+ successful construction. Resets are transactional and retain both capacities.
145+ Both reset forms return the unconsumed remainder; it is empty after a
146+ successful exact reset.
147+
148+ A known parent boundary propagates to its final definite child. Consequently,
149+ the last payload in a definite transport tuple can be finished in O(1), even
150+ when it is a large container. Non-final arbitrary containers have the honest
151+ O(depth) tradeoff: descend immediately without a pre-walk, or establish/capture
152+ the exact span with one subtree walk.
153+
67154
68155#### The checked item
69156
@@ -76,9 +163,8 @@ class item
76163 bool indefinite() const noexcept;
77164 tag_range tags() const noexcept; // leading tags, outermost first
78165
79- element_range elements() const noexcept; // array elements
80- entry_range entries() const noexcept; // map entries
81166 chunk_range chunks() const noexcept; // string content spans
167+ child_range children(scan_context& context) const noexcept; // raw children as items
82168
83169 bool uint64_value(uint64_t& value) const noexcept;
84170 bool int64_value(int64_t& value) const noexcept;
@@ -96,27 +182,25 @@ enum class item_kind
96182 array, map, simple
97183};
98184
99- struct map_entry
100- {
101- item key;
102- item value;
103- };
104185```
105186
106- An ` item ` is one complete, well-formed encoded item: its leading
187+ An ` item ` is one complete, structurally well-formed encoded item: its leading
107188semantic tags, head, and content. ` kind() ` classifies the content after
108189tags (` simple ` covers major type 7: simple values and floating point);
109190` argument() ` is the head's argument — an integer's value, a string's
110191length, a container's count, a simple value's number, or the bit
111192pattern of a floating-point value.
112193
113- The ranges iterate with plain range-` for ` and cannot fail: validation
114- already happened. ` elements() ` and ` entries() ` yield checked items;
115- ` chunks() ` yields the contiguous spans of a string's content, one per
116- chunk for indefinite-length strings. Kind mismatches yield empty
117- ranges. Iterating a container walks its encoding, so navigating to
118- depth * d* of a document rescans the subtrees on that path; iteration
119- beyond 32 levels of nesting inside one item may allocate.
194+ ` chunks() ` yields the contiguous spans of a string's content, one per chunk
195+ for indefinite-length strings.
196+
197+ ` children(context) ` yields a container's raw children in order — array
198+ elements, or a map's keys and values alternating — each a complete checked
199+ item, measured once on the way past. The item's bytes are checked, so
200+ iteration cannot fail; the context, which must outlive the range, supplies
201+ skip workspace for container children and grows only past 32 open
202+ containers. ` children ` serves sibling iteration over checked bytes;
203+ ` navigator ` serves stateful traversal with retained parents and extents.
120204
121205The typed accessors return ` false ` , leaving ` value ` untouched, exactly
122206when the item is not of the requested kind; conversions are strict.
@@ -143,9 +227,6 @@ struct length_first_compare; // RFC 8949 4.2.3 order, three-way
143227struct bytewise_less; // strict weak orders for sorting
144228struct length_first_less;
145229
146- template <typename Compare = bytewise_compare>
147- bool map_keys_sorted(const item& map_item, Compare compare = Compare());
148-
149230// Span overloads for raw-span consumers; validate input, then order.
150231template <typename Order = bytewise_compare>
151232expected<int, scan_error> compare(span<const uint8_t> a, span<const uint8_t> b,
@@ -156,14 +237,11 @@ expected<bool, scan_error> map_keys_sorted(span<const uint8_t> input,
156237 Order order = Order(), int max_nesting_depth = default_max_nesting_depth);
157238```
158239
159- The order function objects compare encoded bytes and accept either two
160- items or two raw ` span<const uint8_t> ` . The ` *_compare ` forms return
161- negative, zero, or positive; the ` *_less ` forms are predicates for
162- ` std::sort ` and ordered containers. ` map_keys_sorted ` is true if
163- ` map_item ` is a map whose keys are strictly ascending in the given
164- order — the deterministic-encoding key condition. The span overloads
165- validate before ordering and, on malformed input, return an error carrying
166- the code and byte offset; trailing bytes after the first item are tolerated.
240+ The order function objects compare encoded bytes and accept either two items
241+ or two raw spans. The ` *_compare ` forms return a three-way result; the ` *_less `
242+ forms are predicates for sorting and ordered containers. The span-based
243+ ` map_keys_sorted ` validates once, then walks keys with a navigator; malformed
244+ input returns the code and byte offset, and trailing bytes are tolerated.
167245
168246#### Low-level tier
169247
@@ -172,16 +250,29 @@ enum class major_type; // CBOR major types 0-7
172250
173251struct item_head { major_type major_type; uint8_t additional_info; uint64_t value; bool indefinite(); };
174252
175- bool read_head(const uint8_t* & p, const uint8_t* end, item_head& head, std::error_code& ec);
176- bool skip_item(const uint8_t* & p, const uint8_t* end, std::error_code& ec,
177- int max_nesting_depth = default_max_nesting_depth);
253+ class wire_cursor
254+ {
255+ public:
256+ explicit wire_cursor(span<const uint8_t > input) noexcept;
257+
258+ std::size_t position() const noexcept;
259+ span<const uint8_t> remaining() const noexcept;
260+ expected<item_head, scan_error> read_head() noexcept;
261+ expected<item, scan_error> read_item(scan_context& context);
262+ expected<span<const uint8_t>, scan_error> skip_item(scan_context& context);
263+ bool skip(std::size_t count) noexcept;
264+ };
178265```
179266
180- The wire-level head decoding the checked item layer is built on, for
181- consumers that need sub-item head access. Prefer the checked item layer
182- unless you are walking objects yourself with intention. `read_head`
183- advances `p` past one head only (tags are returned as their own heads) and
184- validates just that head; `skip_item` advances past one complete item.
267+ ` wire_cursor ` is the offset-based low-level tier for consumers that need
268+ sub-item head access. It borrows one input span and exposes its position and
269+ remaining bytes without a mutable pointer/end pair. ` read_head ` advances past
270+ one head only (tags are returned as their own heads); ` read_item ` validates and
271+ returns one complete checked item; ` skip_item ` validates and passes over one
272+ complete item, returning its encoded bytes unparsed; ` skip ` advances past
273+ ` count ` bytes of already-measured content, such as a definite string payload
274+ after its head, refusing when fewer bytes remain. Errors report offsets from
275+ the beginning of the cursor's input.
185276
186277### Examples
187278
@@ -190,63 +281,68 @@ validates just that head; `skip_item` advances past one complete item.
190281``` cpp
191282#include < jsoncons_ext/cbor/cbor_view.hpp>
192283#include < iostream>
284+ #include < utility>
285+ #include < vector>
193286
194287int main ()
195288{
196- // {"id": 42, "name": "ada", " scores": [1, 2]}
289+ // {"id": 42, "scores": [1, 2]}
197290 const std::vector<uint8_t> data = {
198- 0xa3 ,
291+ 0xa2 ,
199292 0x62,'i','d', 0x18,0x2a,
200- 0x64,'n','a','m','e', 0x63,'a','d','a',
201293 0x66,'s','c','o','r','e','s', 0x82,0x01,0x02
202294 };
203295
204- auto doc = jsoncons::cbor::view::parse_exact (
205- jsoncons::span<const uint8_t>(data.data(), data.size() ));
206- if (!doc.has_value() )
296+ auto result = jsoncons::cbor::view::navigate_exact (
297+ jsoncons::span<const uint8_t>(data));
298+ if (!result )
207299 {
208- std::cout << "malformed at offset " << doc.error().offset << "\n";
209300 return 1;
210301 }
211302
212- for (jsoncons::cbor::view::map_entry entry : doc.value().entries())
303+ auto nav = std::move(result.value());
304+ if (!nav.enter())
305+ {
306+ return 0;
307+ }
308+ for (;;)
213309 {
214310 jsoncons::string_view name;
215- if (!entry.key. text(name))
311+ if (!nav. text(name) || !nav.next( ))
216312 {
217- continue ;
313+ break ;
218314 }
219315 std::cout << name << ":";
220316
221317 uint64_t number = 0;
222- jsoncons::string_view text;
223- if (entry.value.text(text))
224- {
225- std::cout << " " << text;
226- }
227- else if (entry.value.uint64_value(number))
318+ if (nav.uint64_value(number))
228319 {
229320 std::cout << " " << number;
230321 }
231- else
322+ else if (nav.enter())
232323 {
233- for (jsoncons::cbor::view::item element : entry.value.elements())
324+ do
234325 {
235- if (element .uint64_value(number))
326+ if (nav .uint64_value(number))
236327 {
237328 std::cout << " " << number;
238329 }
239330 }
331+ while (nav.next());
332+ nav.leave();
240333 }
241334 std::cout << "\n";
335+ if (!nav.next())
336+ {
337+ break;
338+ }
242339 }
243340}
244341```
245342
246343Output:
247344```
248345id: 42
249- name: ada
250346scores: 1 2
251347```
252348
0 commit comments