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 <string_view>
13#include <utility>
14#include <vector>
15
16namespace json {
17
18namespace {
19
20std::map<char, char const*> gJsonSpecialCharacterEscape = {
21 {'"', "\\\""},
22 {'\\', "\\\\"},
23 {'/', "\\/"},
24 {'\b', "\\b"},
25 {'\f', "\\f"},
26 {'\n', "\\n"},
27 {'\r', "\\r"},
28 {'\t', "\\t"}};
29
30size_t const kJsonEscapeLength = 2;
31
32// All other JSON punctuation.
33char const kCloseBrace = '}';
34char const kCloseBracket = ']';
35char const kColon = ':';
36char const kComma = ',';
37char const kOpenBrace = '{';
38char const kOpenBracket = '[';
39char const kQuote = '"';
40
41auto const kIntegralFloatsBecomeInts = false;
42
43size_t
44lengthWithoutTrailingZeros(std::string const& s)
45{
46 auto dotPos = s.find('.');
47 if (dotPos == std::string::npos)
48 return s.size();
49
50 auto lastNonZero = s.find_last_not_of('0');
51 auto hasDecimals = dotPos != lastNonZero;
52
53 if (hasDecimals)
54 return lastNonZero + 1;
55
56 if (kIntegralFloatsBecomeInts || lastNonZero + 2 > s.size())
57 return lastNonZero;
58
59 return lastNonZero + 2;
60}
61
62} // namespace
63
65{
66public:
67 explicit Impl(Output output) : output_(std::move(output))
68 {
69 }
70 ~Impl() = default;
71
72 Impl(Impl&&) = delete;
73 Impl&
74 operator=(Impl&&) = delete;
75
76 [[nodiscard]] bool
77 empty() const
78 {
79 return stack_.empty();
80 }
81
82 void
84 {
85 char const ch = (ct == CollectionType::Array) ? kOpenBracket : kOpenBrace;
86 output({&ch, 1});
87 stack_.emplace(Collection{.type = ct});
88 }
89
90 void
92 {
94 output_(bytes);
95 }
96
97 void
99 {
100 markStarted();
101 std::size_t position = 0, writtenUntil = 0;
102
103 output_({&kQuote, 1});
104 auto data = bytes.data();
105 for (; position < bytes.size(); ++position)
106 {
107 auto i = gJsonSpecialCharacterEscape.find(data[position]);
108 if (i != gJsonSpecialCharacterEscape.end())
109 {
110 if (writtenUntil < position)
111 {
112 output_({data + writtenUntil, position - writtenUntil});
113 }
114 output_({i->second, kJsonEscapeLength});
115 writtenUntil = position + 1;
116 };
117 }
118 if (writtenUntil < position)
119 output_({data + writtenUntil, position - writtenUntil});
120 output_({&kQuote, 1});
121 }
122
123 void
125 {
126 check(!isFinished(), "isFinished() in output.");
127 isStarted_ = true;
128 }
129
130 void
132 {
133 check(!empty(), "empty () in " + message);
134
135 auto t = stack_.top().type;
136 if (t != type)
137 {
138 check(
139 false,
140 "Not an " + ((type == CollectionType::Array ? "array: " : "object: ") + message));
141 }
142 if (stack_.top().isFirst)
143 {
144 stack_.top().isFirst = false;
145 }
146 else
147 {
148 output_({&kComma, 1});
149 }
150 }
151
152 void
154 {
155#ifndef NDEBUG
156 // Make sure we haven't already seen this tag.
157 auto& tags = stack_.top().tags;
158 check(!tags.contains(tag), "Already seen tag " + tag);
159 tags.insert(tag);
160#endif
161
162 stringOutput(tag);
163 output_({&kColon, 1});
164 }
165
166 [[nodiscard]] bool
168 {
169 return isStarted_ && empty();
170 }
171
172 void
174 {
175 check(!empty(), "Empty stack in finish()");
176
177 auto isArray = stack_.top().type == CollectionType::Array;
178 auto ch = isArray ? kCloseBracket : kCloseBrace;
179 output_({&ch, 1});
180 stack_.pop();
181 }
182
183 void
185 {
186 if (isStarted_)
187 {
188 while (!isFinished())
189 finish();
190 }
191 }
192
193 [[nodiscard]] Output const&
194 getOutput() const
195 {
196 return output_;
197 }
198
199private:
200 // JSON collections are either arrays, or objects.
202 {
207
212 bool isFirst = true;
213
214#ifndef NDEBUG
218 std::set<std::string> tags{}; // NOLINT(readability-redundant-member-init)
219#endif
220 };
221
223
226
227 bool isStarted_ = false;
228};
229
230Writer::Writer(Output const& output) : impl_(std::make_unique<Impl>(output))
231{
232}
233
235{
236 if (impl_)
237 impl_->finishAll();
238}
239
240Writer::Writer(Writer&& w) noexcept : impl_(std::move(w.impl_))
241{
242}
243
244Writer&
246{
247 impl_ = std::move(w.impl_);
248 return *this;
249}
250
251void
252Writer::output(char const* s)
253{
254 impl_->stringOutput(s);
255}
256
257void
259{
260 impl_->stringOutput(s);
261}
262
263void
265{
266 impl_->markStarted();
267 outputJson(value, impl_->getOutput());
268}
269
270void
272{
273 auto s = xrpl::to_string(f);
274 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
275}
276
277void
279{
280 auto s = xrpl::to_string(f);
281 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
282}
283
284void
286{
287 impl_->output("null");
288}
289
290void
292{
293 impl_->output(b ? "true" : "false");
294}
295
296void
298{
299 impl_->output(s);
300}
301
302void
304{
305 if (impl_)
306 impl_->finishAll();
307}
308
309void
311{
312 impl_->nextCollectionEntry(CollectionType::Array, "append");
313}
314
315void
317{
318 check(!tag.empty(), "Tag can't be empty");
319
320 impl_->nextCollectionEntry(CollectionType::Object, "set");
321 impl_->writeObjectTag(tag);
322}
323
324void
326{
327 impl_->start(type);
328}
329
330void
332{
333 impl_->nextCollectionEntry(CollectionType::Array, "startAppend");
334 impl_->start(type);
335}
336
337void
339{
340 impl_->nextCollectionEntry(CollectionType::Object, "startSet");
341 impl_->writeObjectTag(key);
342 impl_->start(type);
343}
344
345void
347{
348 if (impl_)
349 impl_->finish();
350}
351
352} // namespace json
Represents a JSON value.
Definition json_value.h:117
Impl(Impl &&)=delete
void stringOutput(std::string_view bytes)
Output const & getOutput() const
std::stack< Collection, std::vector< Collection > > Stack
void output(std::string_view bytes)
void writeObjectTag(std::string const &tag)
Impl & operator=(Impl &&)=delete
void nextCollectionEntry(CollectionType type, std::string const &message)
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
std::function< void(std::string_view)> Output
void outputJson(json::Value const &, Output const &)
Writes a minimal representation of a Json value to an Output in O(n) time.
void check(bool condition, std::string const &message)
STL namespace.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
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?