JSON Voorhees
Killer JSON for C++
Loading...
Searching...
No Matches
container_adapter.hpp
Go to the documentation of this file.
1/// \file jsonv/serialization/container_adapter.hpp
2///
3/// Copyright (c) 2015-2026 by Travis Gockel. All rights reserved.
4///
5/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License
6/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later
7/// version.
8///
9/// \author Travis Gockel (travis@gockelhut.com)
10#pragma once
11
12#include <jsonv/ast.hpp>
13#include <jsonv/config.hpp>
15#include <jsonv/reader.hpp>
17
18#include <cstddef>
19#include <expected>
20#include <iterator>
21#include <utility>
22
23#include "adapter_for.hpp"
24
25namespace jsonv
26{
27
28/// \addtogroup Serialization
29/// \{
30
31/// An adapter for container types. This is for convenience of creating an \c adapter for things like \c std::vector,
32/// \c std::set and such.
33///
34/// \tparam TContainer is the container to create and encode. It must have a member type \c value_type, support
35/// iteration and an \c insert operation.
36template <typename TContainer>
38 public adapter_for<TContainer>
39{
40 using element_type = typename TContainer::value_type;
41
42protected:
44 virtual std::expected<TContainer, ast_node_type>
46 {
47 using std::end;
48
49 auto opened = context.current_as<ast_node::array_begin>(from);
50 if (!opened)
51 return std::unexpected(opened.error());
52
53 TContainer out;
54
55 // The opening token carries how many elements the array holds, so the container is sized once rather than
56 // grown. A failed parse leaves that count determinate and bounded by what is actually on the tape, which is
57 // what keeps a malformed document from driving an arbitrary reservation.
58 detail::reserve_if_possible(out, opened->element_count());
59
60 bool recovered = false;
61 bool closed = false;
62
63 // Step off the `[` and onto the first element, or onto the `]` of an empty array. The loop never advances
64 // itself: deserializing an element leaves the cursor one past it, which is what every deserializer owes its
65 // caller.
66 (void) from.next_token();
67
68 try
69 {
70 for (std::size_t idx = 0U; from.good(); ++idx)
71 {
72 // A parse which failed part-way through an array still hands back a usable tape; it just ends with
73 // an `error` describing what cut it short, where the rest of the elements should have been. Saying
74 // the array never closed is more use than letting the deserialization below report it as a mismatch
75 // against a node type no element can have, and there is nothing after it to recover into.
76 if (auto type = from.current_type();
77 type == ast_node_type::document_end || type == ast_node_type::error)
78 {
79 return context.problem(context.problem_path(from), "Unterminated array");
80 }
81
82 if (from.current_type() == ast_node_type::array_end)
83 {
84 (void) from.next_token();
85 closed = true;
86
87 // Moving `out` into the result is the last thing which can fail, and `TContainer` may be a
88 // user's; `closed` is what tells the handler below that there is nothing left to walk.
89 if (!recovered)
90 return out;
91
92 // Recovering collected the rest of the problems; it did not make the container valid. The whole
93 // array has been read either way, so the cursor lands in the same place whether this succeeds
94 // or fails -- which only works because the failure says the value is behind it.
95 context.note_value_consumed(from);
96 return std::unexpected(ast_node_type::error);
97 }
98
99 // Naming the element is what puts `[3]` into a problem raised inside it. The scope lives on this
100 // frame and is two stores to push; no `jsonv::path` is built unless a problem is actually recorded,
101 // which is why a wholly successful deserialization of a large array allocates nothing to track where it
102 // is.
103 //
104 // It covers the deserialization and not the insertion, which is `TContainer`'s code and may be a
105 // user's.
106 auto element = [&] () -> std::expected<element_type, ast_node_type>
107 {
109
110 return context.deserialize<element_type>(from);
111 }();
112
113 if (!element)
114 {
115 // The next element starts at a known place, so a bad one does not have to hide every problem
116 // after it. Only in `collect_all`, and only while the budget lasts -- otherwise the failure is
117 // reported as it stands, with the cursor still naming the element which caused it.
118 if (!context.recover())
119 return std::unexpected(element.error());
120
121 recovered = true;
122 context.skip_failed_value(from);
123 continue;
124 }
125
126 out.insert(end(out), *std::move(element));
127 }
128 }
129 catch (...)
130 {
131 // Something left the walk -- inserting into `TContainer`, which is often a user's code, or moving an
132 // element into place. Unless the array was already closed, the cursor is somewhere inside it: a
133 // position only this adapter can make sense of, so finish the walk before letting the failure out.
134 //
135 // Stepping over whole child values is what finds this array's own end. `reader::next_structure` cannot:
136 // on a child which is itself a structure it leaves *that* child, landing back inside this array, and on
137 // a scalar child it leaves this array without consuming the `]` consistently. `reader::next_value`
138 // crosses a child of either shape, so the only closing token this loop can stop on is its own.
139 if (!closed)
140 {
141 while (from.good())
142 {
143 auto type = from.current_type();
144 if (type == ast_node_type::array_end)
145 {
146 (void) from.next_token();
147 break;
148 }
149 else if (type == ast_node_type::document_end || type == ast_node_type::error)
150 {
151 // A truncated tape has no `]` to find. Stopping here leaves the cursor on the end of the
152 // document, which the loop above reports as an unterminated array anyway.
153 break;
154 }
155
156 (void) from.next_value();
157 }
158 }
159
160 context.note_value_consumed(from);
161 throw;
162 }
163
164 // Only reachable by recovering off the end of a truncated tape; the check at the top of the loop is what an
165 // unterminated array normally arrives as.
166 return context.problem(context.problem_path(from), "Unterminated array");
167 }
168
169 virtual void serialize(const serialization_context& context, const TContainer& from, writer& to) const override
170 {
171 to.array_begin();
172 for (const element_type& x : from)
173 context.serialize(x, to);
174 to.array_end();
175 }
176};
177
178/// \}
179
180}
Copyright (c) 2015-2026 by Travis Gockel.
Utilities for directly dealing with a JSON AST.
An adapter for the type T.
The beginning of an kind::array ([).
Definition ast.hpp:282
An adapter for container types.
virtual void serialize(const serialization_context &context, const TContainer &from, writer &to) const override
Write from into to.
virtual std::expected< TContainer, ast_node_type > create(deserialization_context &context, reader &from) const override
Create an instance of T by reading from.
Provides extra information to routines used for deserialization and serialization.
Definition context.hpp:26
An RAII guard naming one step of the deserialization path while it is alive.
Provides extra information to routines used for deserialization, collects the problems they encounter...
A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON...
Definition reader.hpp:106
bool next_value() noexcept
Go to one past the value this reader is on.
ast_node_type current_type() const
Get the type of the current AST node, which is always current().type().
bool next_token() noexcept
Go to the next token.
bool good() const
Check if this reader is still good to read from.
Provides extra information to routines used for serialization: the formats to find other serializers ...
Definition serialize.hpp:42
A writer instance writes a JSON ast_node sequence to some form of sink: an encoder,...
Definition writer.hpp:68
writer & array_begin()
Write the token which opens or closes an object or an array.
writer & array_end()
Write the token which opens or closes an object or an array.
Copyright (c) 2014-2020 by Travis Gockel.
#define JSONV_NODISCARD
Warn if the caller discards the result of this function.
Definition config.hpp:143
Read a JSON AST.
Definition of the reserve_if_possible utility.
Conversion between C++ types and JSON values.