rippled
Loading...
Searching...
No Matches
libxrpl/json/Writer.cpp
1#include <xrpl/basics/ToString.h>
2#include <xrpl/json/Output.h>
3#include <xrpl/json/Writer.h>
4
5#include <cstddef>
6#include <map>
7#include <memory>
8#include <set>
9#include <stack>
10#include <string>
11#include <utility>
12#include <vector>
13
14namespace Json {
15
16namespace {
17
18std::map<char, char const*> jsonSpecialCharacterEscape = {
19 {'"', "\\\""},
20 {'\\', "\\\\"},
21 {'/', "\\/"},
22 {'\b', "\\b"},
23 {'\f', "\\f"},
24 {'\n', "\\n"},
25 {'\r', "\\r"},
26 {'\t', "\\t"}};
27
28size_t const jsonEscapeLength = 2;
29
30// All other JSON punctuation.
31char const closeBrace = '}';
32char const closeBracket = ']';
33char const colon = ':';
34char const comma = ',';
35char const openBrace = '{';
36char const openBracket = '[';
37char const quote = '"';
38
39auto const integralFloatsBecomeInts = false;
40
41size_t
42lengthWithoutTrailingZeros(std::string const& s)
43{
44 auto dotPos = s.find('.');
45 if (dotPos == std::string::npos)
46 return s.size();
47
48 auto lastNonZero = s.find_last_not_of('0');
49 auto hasDecimals = dotPos != lastNonZero;
50
51 if (hasDecimals)
52 return lastNonZero + 1;
53
54 if (integralFloatsBecomeInts || lastNonZero + 2 > s.size())
55 return lastNonZero;
56
57 return lastNonZero + 2;
58}
59
60} // namespace
61
63{
64public:
65 explicit Impl(Output const& output) : output_(output)
66 {
67 }
68 ~Impl() = default;
69
70 Impl(Impl&&) = delete;
71 Impl&
72 operator=(Impl&&) = delete;
73
74 bool
75 empty() const
76 {
77 return stack_.empty();
78 }
79
80 void
82 {
83 char const ch = (ct == array) ? openBracket : openBrace;
84 output({&ch, 1});
86 stack_.top().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_({&quote, 1});
103 auto data = bytes.data();
104 for (; position < bytes.size(); ++position)
105 {
106 auto i = jsonSpecialCharacterEscape.find(data[position]);
107 if (i != jsonSpecialCharacterEscape.end())
108 {
109 if (writtenUntil < position)
110 {
111 output_({data + writtenUntil, position - writtenUntil});
112 }
113 output_({i->second, jsonEscapeLength});
114 writtenUntil = position + 1;
115 };
116 }
117 if (writtenUntil < position)
118 output_({data + writtenUntil, position - writtenUntil});
119 output_({&quote, 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(false, "Not an " + ((type == array ? "array: " : "object: ") + message));
138 }
139 if (stack_.top().isFirst)
140 {
141 stack_.top().isFirst = false;
142 }
143 else
144 {
145 output_({&comma, 1});
146 }
147 }
148
149 void
151 {
152#ifndef NDEBUG
153 // Make sure we haven't already seen this tag.
154 auto& tags = stack_.top().tags;
155 check(!tags.contains(tag), "Already seen tag " + tag);
156 tags.insert(tag);
157#endif
158
159 stringOutput(tag);
160 output_({&colon, 1});
161 }
162
163 bool
165 {
166 return isStarted_ && empty();
167 }
168
169 void
171 {
172 check(!empty(), "Empty stack in finish()");
173
174 auto isArray = stack_.top().type == array;
175 auto ch = isArray ? closeBracket : closeBrace;
176 output_({&ch, 1});
177 stack_.pop();
178 }
179
180 void
182 {
183 if (isStarted_)
184 {
185 while (!isFinished())
186 finish();
187 }
188 }
189
190 Output const&
191 getOutput() const
192 {
193 return output_;
194 }
195
196private:
197 // JSON collections are either arrays, or objects.
199 {
200 explicit Collection() = default;
201
204
207 bool isFirst = true;
208
209#ifndef NDEBUG
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(array, "append");
307}
308
309void
311{
312 check(!tag.empty(), "Tag can't be empty");
313
314 impl_->nextCollectionEntry(object, "set");
315 impl_->writeObjectTag(tag);
316}
317
318void
320{
321 impl_->start(type);
322}
323
324void
326{
327 impl_->nextCollectionEntry(array, "startAppend");
328 impl_->start(type);
329}
330
331void
333{
334 impl_->nextCollectionEntry(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
void nextCollectionEntry(CollectionType type, std::string const &message)
void stringOutput(boost::beast::string_view const &bytes)
Impl(Impl &&)=delete
void writeObjectTag(std::string const &tag)
Impl & operator=(Impl &&)=delete
Output const & getOutput() const
void output(boost::beast::string_view const &bytes)
Impl(Output const &output)
void start(CollectionType ct)
Writer implements an O(1)-space, O(1)-granular output JSON writer.
Writer & operator=(Writer &&) noexcept
void finish()
Finish the collection most recently started.
void output(std::string const &)
void implOutput(std::string const &)
void startRoot(CollectionType)
Start a new collection at the root level.
void rawAppend()
Add a comma before this next item if not the first item in an array.
void rawSet(std::string const &key)
Emit just "tag": as part of an object.
void finishAll()
Finish all objects and arrays.
Writer(Output const &output)
void startAppend(CollectionType)
Start a new collection inside an array.
void startSet(CollectionType, std::string const &key)
Start a new collection inside an object.
std::unique_ptr< Impl > impl_
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.
void check(bool condition, std::string const &message)
STL namespace.
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
T pop(T... args)
T push(T... args)
T size(T... args)
std::set< std::string > tags
What tags have we already seen in this collection?
bool isFirst
Is this the first entry in a collection? If false, we have to emit a , before we write the next entry...
Writer::CollectionType type
What type of collection are we in?
T top(T... args)