xrpld
Loading...
Searching...
No Matches
libxrpl/json/Writer.cpp
1#include <xrpl/json/Writer.h>
2
3#include <xrpl/basics/ToString.h>
4#include <xrpl/json/Output.h>
5
6#include <cstddef>
7#include <map>
8#include <memory>
9#include <set> // IWYU pragma: keep
10#include <stack>
11#include <string>
12#include <utility>
13#include <vector>
14
15namespace json {
16
17namespace {
18
19std::map<char, char const*> gJsonSpecialCharacterEscape = {
20 {'"', "\\\""},
21 {'\\', "\\\\"},
22 {'/', "\\/"},
23 {'\b', "\\b"},
24 {'\f', "\\f"},
25 {'\n', "\\n"},
26 {'\r', "\\r"},
27 {'\t', "\\t"}};
28
29size_t const kJsonEscapeLength = 2;
30
31// All other JSON punctuation.
32char const kCloseBrace = '}';
33char const kCloseBracket = ']';
34char const kColon = ':';
35char const kComma = ',';
36char const kOpenBrace = '{';
37char const kOpenBracket = '[';
38char const kQuote = '"';
39
40auto const kIntegralFloatsBecomeInts = false;
41
42size_t
43lengthWithoutTrailingZeros(std::string const& s)
44{
45 auto dotPos = s.find('.');
46 if (dotPos == std::string::npos)
47 return s.size();
48
49 auto lastNonZero = s.find_last_not_of('0');
50 auto hasDecimals = dotPos != lastNonZero;
51
52 if (hasDecimals)
53 return lastNonZero + 1;
54
55 if (kIntegralFloatsBecomeInts || lastNonZero + 2 > s.size())
56 return lastNonZero;
57
58 return lastNonZero + 2;
59}
60
61} // namespace
62
64{
65public:
66 explicit Impl(Output output) : output_(std::move(output))
67 {
68 }
69 ~Impl() = default;
70
71 Impl(Impl&&) = delete;
72 Impl&
73 operator=(Impl&&) = delete;
74
75 [[nodiscard]] bool
76 empty() const
77 {
78 return stack_.empty();
79 }
80
81 void
83 {
84 char const ch = (ct == CollectionType::Array) ? kOpenBracket : kOpenBrace;
85 output({&ch, 1});
86 stack_.emplace(Collection{.type = ct});
87 }
88
89 void
90 output(boost::beast::string_view const& bytes)
91 {
93 output_(bytes);
94 }
95
96 void
97 stringOutput(boost::beast::string_view const& bytes)
98 {
100 std::size_t position = 0, writtenUntil = 0;
101
102 output_({&kQuote, 1});
103 auto data = bytes.data();
104 for (; position < bytes.size(); ++position)
105 {
106 auto i = gJsonSpecialCharacterEscape.find(data[position]);
107 if (i != gJsonSpecialCharacterEscape.end())
108 {
109 if (writtenUntil < position)
110 {
111 output_({data + writtenUntil, position - writtenUntil});
112 }
113 output_({i->second, kJsonEscapeLength});
114 writtenUntil = position + 1;
115 };
116 }
117 if (writtenUntil < position)
118 output_({data + writtenUntil, position - writtenUntil});
119 output_({&kQuote, 1});
120 }
121
122 void
124 {
125 check(!isFinished(), "isFinished() in output.");
126 isStarted_ = true;
127 }
128
129 void
131 {
132 check(!empty(), "empty () in " + message);
133
134 auto t = stack_.top().type;
135 if (t != type)
136 {
137 check(
138 false,
139 "Not an " + ((type == CollectionType::Array ? "array: " : "object: ") + message));
140 }
141 if (stack_.top().isFirst)
142 {
143 stack_.top().isFirst = false;
144 }
145 else
146 {
147 output_({&kComma, 1});
148 }
149 }
150
151 void
153 {
154#ifndef NDEBUG
155 // Make sure we haven't already seen this tag.
156 auto& tags = stack_.top().tags;
157 check(!tags.contains(tag), "Already seen tag " + tag);
158 tags.insert(tag);
159#endif
160
161 stringOutput(tag);
162 output_({&kColon, 1});
163 }
164
165 [[nodiscard]] bool
167 {
168 return isStarted_ && empty();
169 }
170
171 void
173 {
174 check(!empty(), "Empty stack in finish()");
175
176 auto isArray = stack_.top().type == CollectionType::Array;
177 auto ch = isArray ? kCloseBracket : kCloseBrace;
178 output_({&ch, 1});
179 stack_.pop();
180 }
181
182 void
184 {
185 if (isStarted_)
186 {
187 while (!isFinished())
188 finish();
189 }
190 }
191
192 [[nodiscard]] Output const&
193 getOutput() const
194 {
195 return output_;
196 }
197
198private:
199 // JSON collections are either arrays, or objects.
201 {
206
211 bool isFirst = true;
212
213#ifndef NDEBUG
217 std::set<std::string> tags{}; // NOLINT(readability-redundant-member-init)
218#endif
219 };
220
222
225
226 bool isStarted_ = false;
227};
228
229Writer::Writer(Output const& output) : impl_(std::make_unique<Impl>(output))
230{
231}
232
234{
235 if (impl_)
236 impl_->finishAll();
237}
238
239Writer::Writer(Writer&& w) noexcept : impl_(std::move(w.impl_))
240{
241}
242
243Writer&
245{
246 impl_ = std::move(w.impl_);
247 return *this;
248}
249
250void
251Writer::output(char const* s)
252{
253 impl_->stringOutput(s);
254}
255
256void
258{
259 impl_->stringOutput(s);
260}
261
262void
264{
265 impl_->markStarted();
266 outputJson(value, impl_->getOutput());
267}
268
269void
271{
272 auto s = xrpl::to_string(f);
273 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
274}
275
276void
278{
279 auto s = xrpl::to_string(f);
280 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
281}
282
283void
285{
286 impl_->output("null");
287}
288
289void
291{
292 impl_->output(b ? "true" : "false");
293}
294
295void
297{
298 impl_->output(s);
299}
300
301void
303{
304 if (impl_)
305 impl_->finishAll();
306}
307
308void
310{
311 impl_->nextCollectionEntry(CollectionType::Array, "append");
312}
313
314void
316{
317 check(!tag.empty(), "Tag can't be empty");
318
319 impl_->nextCollectionEntry(CollectionType::Object, "set");
320 impl_->writeObjectTag(tag);
321}
322
323void
325{
326 impl_->start(type);
327}
328
329void
331{
332 impl_->nextCollectionEntry(CollectionType::Array, "startAppend");
333 impl_->start(type);
334}
335
336void
338{
339 impl_->nextCollectionEntry(CollectionType::Object, "startSet");
340 impl_->writeObjectTag(key);
341 impl_->start(type);
342}
343
344void
346{
347 if (impl_)
348 impl_->finish();
349}
350
351} // namespace json
Represents a JSON value.
Definition json_value.h:117
Impl(Impl &&)=delete
Output const & getOutput() const
std::stack< Collection, std::vector< Collection > > Stack
void writeObjectTag(std::string const &tag)
Impl & operator=(Impl &&)=delete
void nextCollectionEntry(CollectionType type, std::string const &message)
void stringOutput(boost::beast::string_view const &bytes)
void output(boost::beast::string_view const &bytes)
void start(CollectionType ct)
Writer implements an O(1)-space, O(1)-granular output JSON writer.
void finishAll()
Finish all objects and arrays.
void rawAppend()
Add a comma before this next item if not the first item in an array.
void startRoot(CollectionType)
Start a new collection at the root level.
Writer & operator=(Writer &&) noexcept
void startAppend(CollectionType)
Start a new collection inside an array.
void implOutput(std::string const &)
void output(std::string const &)
Writer(Output const &output)
void startSet(CollectionType, std::string const &key)
Start a new collection inside an object.
std::unique_ptr< Impl > impl_
void rawSet(std::string const &key)
Emit just "tag": as part of an object.
void finish()
Finish the collection most recently started.
T data(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T find_last_not_of(T... args)
JSON (JavaScript Object Notation).
Definition json_errors.h:5
void outputJson(json::Value const &, Output const &)
Writes a minimal representation of a Json value to an Output in O(n) time.
std::function< void(boost::beast::string_view const &)> Output
void check(bool condition, std::string const &message)
STL namespace.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
T size(T... args)
Writer::CollectionType type
What type of collection are we in?
std::set< std::string > tags
What tags have we already seen in this collection?
bool isFirst
Is this the first entry in a collection?