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 {
204
207 bool isFirst = true;
208
209#ifndef NDEBUG
211 std::set<std::string> tags{}; // NOLINT(readability-redundant-member-init)
212#endif
213 };
214
216
219
220 bool isStarted_ = false;
221};
222
223Writer::Writer(Output const& output) : impl_(std::make_unique<Impl>(output))
224{
225}
226
228{
229 if (impl_)
230 impl_->finishAll();
231}
232
234{
235 impl_ = std::move(w.impl_);
236}
237
238Writer&
240{
241 impl_ = std::move(w.impl_);
242 return *this;
243}
244
245void
246Writer::output(char const* s)
247{
248 impl_->stringOutput(s);
249}
250
251void
253{
254 impl_->stringOutput(s);
255}
256
257void
259{
260 impl_->markStarted();
261 outputJson(value, impl_->getOutput());
262}
263
264void
266{
267 auto s = xrpl::to_string(f);
268 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
269}
270
271void
273{
274 auto s = xrpl::to_string(f);
275 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
276}
277
278void
280{
281 impl_->output("null");
282}
283
284void
286{
287 impl_->output(b ? "true" : "false");
288}
289
290void
292{
293 impl_->output(s);
294}
295
296void
298{
299 if (impl_)
300 impl_->finishAll();
301}
302
303void
305{
306 impl_->nextCollectionEntry(CollectionType::Array, "append");
307}
308
309void
311{
312 check(!tag.empty(), "Tag can't be empty");
313
314 impl_->nextCollectionEntry(CollectionType::Object, "set");
315 impl_->writeObjectTag(tag);
316}
317
318void
320{
321 impl_->start(type);
322}
323
324void
326{
327 impl_->nextCollectionEntry(CollectionType::Array, "startAppend");
328 impl_->start(type);
329}
330
331void
333{
334 impl_->nextCollectionEntry(CollectionType::Object, "startSet");
335 impl_->writeObjectTag(key);
336 impl_->start(type);
337}
338
339void
341{
342 if (impl_)
343 impl_->finish();
344}
345
346} // namespace json
Represents a JSON value.
Definition json_value.h:130
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:633
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?