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
28static size_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
39static auto 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 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(
138 false,
139 "Not an " +
140 ((type == array ? "array: " : "object: ") + message));
141 }
142 if (stack_.top().isFirst)
143 stack_.top().isFirst = false;
144 else
145 output_({&comma, 1});
146 }
147
148 void
150 {
151#ifndef NDEBUG
152 // Make sure we haven't already seen this tag.
153 auto& tags = stack_.top().tags;
154 check(tags.find(tag) == tags.end(), "Already seen tag " + tag);
155 tags.insert(tag);
156#endif
157
158 stringOutput(tag);
159 output_({&colon, 1});
160 }
161
162 bool
164 {
165 return isStarted_ && empty();
166 }
167
168 void
170 {
171 check(!empty(), "Empty stack in finish()");
172
173 auto isArray = stack_.top().type == array;
174 auto ch = isArray ? closeBracket : closeBrace;
175 output_({&ch, 1});
176 stack_.pop();
177 }
178
179 void
181 {
182 if (isStarted_)
183 {
184 while (!isFinished())
185 finish();
186 }
187 }
188
189 Output const&
190 getOutput() const
191 {
192 return output_;
193 }
194
195private:
196 // JSON collections are either arrrays, or objects.
198 {
199 explicit Collection() = default;
200
203
206 bool isFirst = true;
207
208#ifndef NDEBUG
211#endif
212 };
213
215
218
219 bool isStarted_ = false;
220};
221
222Writer::Writer(Output const& output) : impl_(std::make_unique<Impl>(output))
223{
224}
225
227{
228 if (impl_)
229 impl_->finishAll();
230}
231
233{
234 impl_ = std::move(w.impl_);
235}
236
237Writer&
239{
240 impl_ = std::move(w.impl_);
241 return *this;
242}
243
244void
245Writer::output(char const* s)
246{
247 impl_->stringOutput(s);
248}
249
250void
252{
253 impl_->stringOutput(s);
254}
255
256void
258{
259 impl_->markStarted();
260 outputJson(value, impl_->getOutput());
261}
262
263void
265{
266 auto s = ripple::to_string(f);
267 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
268}
269
270void
272{
273 auto s = ripple::to_string(f);
274 impl_->output({s.data(), lengthWithoutTrailingZeros(s)});
275}
276
277void
279{
280 impl_->output("null");
281}
282
283void
285{
286 impl_->output(b ? "true" : "false");
287}
288
289void
291{
292 impl_->output(s);
293}
294
295void
297{
298 if (impl_)
299 impl_->finishAll();
300}
301
302void
304{
305 impl_->nextCollectionEntry(array, "append");
306}
307
308void
310{
311 check(!tag.empty(), "Tag can't be empty");
312
313 impl_->nextCollectionEntry(object, "set");
314 impl_->writeObjectTag(tag);
315}
316
317void
319{
320 impl_->start(type);
321}
322
323void
325{
326 impl_->nextCollectionEntry(array, "startAppend");
327 impl_->start(type);
328}
329
330void
332{
333 impl_->nextCollectionEntry(object, "startSet");
334 impl_->writeObjectTag(key);
335 impl_->start(type);
336}
337
338void
340{
341 if (impl_)
342 impl_->finish();
343}
344
345} // 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:6
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)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
STL namespace.
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)