blob: 6e579a5f694cf3004aac7e98e9cc478ed424d032 [file] [log] [blame]
Brian Osman69fd0082017-08-09 09:25:39 -04001/*
2 * Copyright 2017 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#ifndef SkJSONWriter_DEFINED
9#define SkJSONWriter_DEFINED
10
11#include "SkStream.h"
Brian Osman71a18892017-08-10 10:23:25 -040012#include "SkTArray.h"
Brian Osman69fd0082017-08-09 09:25:39 -040013
14/**
15 * Lightweight class for writing properly structured JSON data. No random-access, everything must
16 * be generated in-order. The resulting JSON is written directly to the SkWStream supplied at
17 * construction time. Output is buffered, so writing to disk (via an SkFILEWStream) is ideal.
18 *
19 * There is a basic state machine to ensure that JSON is structured correctly, and to allow for
20 * (optional) pretty formatting.
21 *
22 * This class adheres to the RFC-4627 usage of JSON (not ECMA-404). In other words, all JSON
23 * created with this class must have a top-level object or array. Free-floating values of other
24 * types are not considered valid.
25 *
26 * Note that all error checking is in the form of asserts - invalid usage in a non-debug build
27 * will simply produce invalid JSON.
28 */
29class SkJSONWriter : SkNoncopyable {
30public:
31 enum class Mode {
32 /**
33 * Output the minimal amount of text. No additional whitespace (including newlines) is
34 * generated. The resulting JSON is suitable for fast parsing and machine consumption.
35 */
36 kFast,
37
38 /**
39 * Output human-readable JSON, with indented objects and arrays, and one value per line.
40 * Slightly slower than kFast, and produces data that is somewhat larger.
41 */
42 kPretty
43 };
44
45 /**
46 * Construct a JSON writer that will serialize all the generated JSON to 'stream'.
47 */
48 SkJSONWriter(SkWStream* stream, Mode mode = Mode::kFast)
49 : fBlock(new char[kBlockSize])
50 , fWrite(fBlock)
51 , fBlockEnd(fBlock + kBlockSize)
52 , fStream(stream)
53 , fMode(mode)
54 , fState(State::kStart) {
55 fScopeStack.push_back(Scope::kNone);
Brian Osman80488222017-08-10 13:29:30 -040056 fNewlineStack.push_back(true);
Brian Osman69fd0082017-08-09 09:25:39 -040057 }
58
59 ~SkJSONWriter() {
60 this->flush();
61 delete[] fBlock;
62 SkASSERT(fScopeStack.count() == 1);
Brian Osman80488222017-08-10 13:29:30 -040063 SkASSERT(fNewlineStack.count() == 1);
Brian Osman69fd0082017-08-09 09:25:39 -040064 }
65
66 /**
67 * Force all buffered output to be flushed to the underlying stream.
68 */
69 void flush() {
70 if (fWrite != fBlock) {
71 fStream->write(fBlock, fWrite - fBlock);
72 fWrite = fBlock;
73 }
74 }
75
76 /**
77 * Append the name (key) portion of an object member. Must be called between beginObject() and
78 * endObject(). If you have both the name and value of an object member, you can simply call
79 * the two argument versions of the other append functions.
80 */
81 void appendName(const char* name) {
82 if (!name) {
83 return;
84 }
85 SkASSERT(Scope::kObject == this->scope());
86 SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
87 if (State::kObjectValue == fState) {
88 this->write(",", 1);
89 }
Brian Osman80488222017-08-10 13:29:30 -040090 this->separator(this->multiline());
Brian Osman69fd0082017-08-09 09:25:39 -040091 this->write("\"", 1);
92 this->write(name, strlen(name));
93 this->write("\":", 2);
94 fState = State::kObjectName;
95 }
96
97 /**
98 * Adds a new object. A name must be supplied when called between beginObject() and
99 * endObject(). Calls to beginObject() must be balanced by corresponding calls to endObject().
Brian Osman80488222017-08-10 13:29:30 -0400100 * By default, objects are written out with one named value per line (when in kPretty mode).
101 * This can be overridden for a particular object by passing false for multiline, this will
102 * keep the entire object on a single line. This can help with readability in some situations.
103 * In kFast mode, this parameter is ignored.
Brian Osman69fd0082017-08-09 09:25:39 -0400104 */
Brian Osman80488222017-08-10 13:29:30 -0400105 void beginObject(const char* name = nullptr, bool multiline = true) {
Brian Osman69fd0082017-08-09 09:25:39 -0400106 this->appendName(name);
107 this->beginValue(true);
108 this->write("{", 1);
109 fScopeStack.push_back(Scope::kObject);
Brian Osman80488222017-08-10 13:29:30 -0400110 fNewlineStack.push_back(multiline);
Brian Osman69fd0082017-08-09 09:25:39 -0400111 fState = State::kObjectBegin;
112 }
113
114 /**
115 * Ends an object that was previously started with beginObject().
116 */
117 void endObject() {
118 SkASSERT(Scope::kObject == this->scope());
119 SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
120 bool emptyObject = State::kObjectBegin == fState;
Brian Osman80488222017-08-10 13:29:30 -0400121 bool wasMultiline = this->multiline();
Brian Osman69fd0082017-08-09 09:25:39 -0400122 this->popScope();
123 if (!emptyObject) {
Brian Osman80488222017-08-10 13:29:30 -0400124 this->separator(wasMultiline);
Brian Osman69fd0082017-08-09 09:25:39 -0400125 }
126 this->write("}", 1);
127 }
128
129 /**
130 * Adds a new array. A name must be supplied when called between beginObject() and
131 * endObject(). Calls to beginArray() must be balanced by corresponding calls to endArray().
Brian Osman80488222017-08-10 13:29:30 -0400132 * By default, arrays are written out with one value per line (when in kPretty mode).
133 * This can be overridden for a particular array by passing false for multiline, this will
134 * keep the entire array on a single line. This can help with readability in some situations.
135 * In kFast mode, this parameter is ignored.
Brian Osman69fd0082017-08-09 09:25:39 -0400136 */
Brian Osman80488222017-08-10 13:29:30 -0400137 void beginArray(const char* name = nullptr, bool multiline = true) {
Brian Osman69fd0082017-08-09 09:25:39 -0400138 this->appendName(name);
139 this->beginValue(true);
140 this->write("[", 1);
141 fScopeStack.push_back(Scope::kArray);
Brian Osman80488222017-08-10 13:29:30 -0400142 fNewlineStack.push_back(multiline);
Brian Osman69fd0082017-08-09 09:25:39 -0400143 fState = State::kArrayBegin;
144 }
145
146 /**
147 * Ends an array that was previous started with beginArray().
148 */
149 void endArray() {
150 SkASSERT(Scope::kArray == this->scope());
151 SkASSERT(State::kArrayBegin == fState || State::kArrayValue == fState);
152 bool emptyArray = State::kArrayBegin == fState;
Brian Osman80488222017-08-10 13:29:30 -0400153 bool wasMultiline = this->multiline();
Brian Osman69fd0082017-08-09 09:25:39 -0400154 this->popScope();
155 if (!emptyArray) {
Brian Osman80488222017-08-10 13:29:30 -0400156 this->separator(wasMultiline);
Brian Osman69fd0082017-08-09 09:25:39 -0400157 }
158 this->write("]", 1);
159 }
160
161 /**
162 * Functions for adding values of various types. The single argument versions add un-named
163 * values, so must be called either
164 * - Between beginArray() and endArray() -or-
165 * - Between beginObject() and endObject(), after calling appendName()
166 */
167 void appendString(const char* value) {
168 this->beginValue();
169 this->write("\"", 1);
170 if (value) {
171 while (*value) {
172 switch (*value) {
173 case '"': this->write("\\\"", 2); break;
174 case '\\': this->write("\\\\", 2); break;
175 case '\b': this->write("\\b", 2); break;
176 case '\f': this->write("\\f", 2); break;
177 case '\n': this->write("\\n", 2); break;
178 case '\r': this->write("\\r", 2); break;
179 case '\t': this->write("\\t", 2); break;
180 default: this->write(value, 1); break;
181 }
182 value++;
183 }
184 }
185 this->write("\"", 1);
186 }
187
188 void appendPointer(const void* value) { this->beginValue(); this->appendf("\"%p\"", value); }
189 void appendBool(bool value) {
190 this->beginValue();
191 if (value) {
192 this->write("true", 4);
193 } else {
194 this->write("false", 5);
195 }
196 }
197 void appendS32(int32_t value) { this->beginValue(); this->appendf("%d", value); }
Brian Osman71a18892017-08-10 10:23:25 -0400198 void appendS64(int64_t value);
Brian Osman69fd0082017-08-09 09:25:39 -0400199 void appendU32(uint32_t value) { this->beginValue(); this->appendf("%u", value); }
Brian Osman71a18892017-08-10 10:23:25 -0400200 void appendU64(uint64_t value);
Brian Osmanb6f82122017-08-16 16:56:04 -0400201 void appendFloat(float value) { this->beginValue(); this->appendf("%f", value); }
Brian Osman69fd0082017-08-09 09:25:39 -0400202 void appendDouble(double value) { this->beginValue(); this->appendf("%f", value); }
Brian Osmanb6f82122017-08-16 16:56:04 -0400203 void appendFloatDigits(float value, int digits) {
204 this->beginValue();
205 this->appendf("%.*f", digits, value);
206 }
207 void appendDoubleDigits(double value, int digits) {
208 this->beginValue();
209 this->appendf("%.*f", digits, value);
210 }
Brian Osman69fd0082017-08-09 09:25:39 -0400211 void appendHexU32(uint32_t value) { this->beginValue(); this->appendf("\"0x%x\"", value); }
Brian Osman71a18892017-08-10 10:23:25 -0400212 void appendHexU64(uint64_t value);
Brian Osman69fd0082017-08-09 09:25:39 -0400213
214#define DEFINE_NAMED_APPEND(function, type) \
215 void function(const char* name, type value) { this->appendName(name); this->function(value); }
216
217 /**
218 * Functions for adding named values of various types. These add a name field, so must be
219 * called between beginObject() and endObject().
220 */
221 DEFINE_NAMED_APPEND(appendString, const char *)
222 DEFINE_NAMED_APPEND(appendPointer, const void *)
223 DEFINE_NAMED_APPEND(appendBool, bool)
224 DEFINE_NAMED_APPEND(appendS32, int32_t)
225 DEFINE_NAMED_APPEND(appendS64, int64_t)
226 DEFINE_NAMED_APPEND(appendU32, uint32_t)
227 DEFINE_NAMED_APPEND(appendU64, uint64_t)
228 DEFINE_NAMED_APPEND(appendFloat, float)
229 DEFINE_NAMED_APPEND(appendDouble, double)
230 DEFINE_NAMED_APPEND(appendHexU32, uint32_t)
231 DEFINE_NAMED_APPEND(appendHexU64, uint64_t)
232
233#undef DEFINE_NAMED_APPEND
234
Brian Osmanb6f82122017-08-16 16:56:04 -0400235 void appendFloatDigits(const char* name, float value, int digits) {
236 this->appendName(name);
237 this->appendFloatDigits(value, digits);
238 }
239 void appendDoubleDigits(const char* name, double value, int digits) {
240 this->appendName(name);
241 this->appendDoubleDigits(value, digits);
242 }
243
Brian Osman69fd0082017-08-09 09:25:39 -0400244private:
245 enum {
246 // Using a 32k scratch block gives big performance wins, but we diminishing returns going
247 // any larger. Even with a 1MB block, time to write a large (~300 MB) JSON file only drops
248 // another ~10%.
249 kBlockSize = 32 * 1024,
250 };
251
252 enum class Scope {
253 kNone,
254 kObject,
255 kArray
256 };
257
258 enum class State {
259 kStart,
260 kEnd,
261 kObjectBegin,
262 kObjectName,
263 kObjectValue,
264 kArrayBegin,
265 kArrayValue,
266 };
267
Brian Osman71a18892017-08-10 10:23:25 -0400268 void appendf(const char* fmt, ...);
Brian Osman69fd0082017-08-09 09:25:39 -0400269
270 void beginValue(bool structure = false) {
271 SkASSERT(State::kObjectName == fState ||
272 State::kArrayBegin == fState ||
273 State::kArrayValue == fState ||
274 (structure && State::kStart == fState));
275 if (State::kArrayValue == fState) {
276 this->write(",", 1);
277 }
278 if (Scope::kArray == this->scope()) {
Brian Osman80488222017-08-10 13:29:30 -0400279 this->separator(this->multiline());
Brian Osman69fd0082017-08-09 09:25:39 -0400280 } else if (Scope::kObject == this->scope() && Mode::kPretty == fMode) {
281 this->write(" ", 1);
282 }
283 // We haven't added the value yet, but all (non-structure) callers emit something
284 // immediately, so transition state, to simplify the calling code.
285 if (!structure) {
286 fState = Scope::kArray == this->scope() ? State::kArrayValue : State::kObjectValue;
287 }
288 }
289
Brian Osman80488222017-08-10 13:29:30 -0400290 void separator(bool multiline) {
Brian Osman69fd0082017-08-09 09:25:39 -0400291 if (Mode::kPretty == fMode) {
Brian Osman80488222017-08-10 13:29:30 -0400292 if (multiline) {
293 this->write("\n", 1);
294 for (int i = 0; i < fScopeStack.count() - 1; ++i) {
295 this->write(" ", 3);
296 }
297 } else {
298 this->write(" ", 1);
Brian Osman69fd0082017-08-09 09:25:39 -0400299 }
300 }
301 }
302
303 void write(const char* buf, size_t length) {
304 if (static_cast<size_t>(fBlockEnd - fWrite) < length) {
305 // Don't worry about splitting writes that overflow our block.
306 this->flush();
307 }
308 if (length > kBlockSize) {
309 // Send particularly large writes straight through to the stream (unbuffered).
310 fStream->write(buf, length);
311 } else {
312 memcpy(fWrite, buf, length);
313 fWrite += length;
314 }
315 }
316
317 Scope scope() const {
318 SkASSERT(!fScopeStack.empty());
319 return fScopeStack.back();
320 }
321
Brian Osman80488222017-08-10 13:29:30 -0400322 bool multiline() const {
323 SkASSERT(!fNewlineStack.empty());
324 return fNewlineStack.back();
325 }
326
Brian Osman69fd0082017-08-09 09:25:39 -0400327 void popScope() {
328 fScopeStack.pop_back();
Brian Osman80488222017-08-10 13:29:30 -0400329 fNewlineStack.pop_back();
Brian Osman69fd0082017-08-09 09:25:39 -0400330 switch (this->scope()) {
331 case Scope::kNone:
332 fState = State::kEnd;
333 break;
334 case Scope::kObject:
335 fState = State::kObjectValue;
336 break;
337 case Scope::kArray:
338 fState = State::kArrayValue;
339 break;
340 default:
341 SkDEBUGFAIL("Invalid scope");
342 break;
343 }
344 }
345
346 char* fBlock;
347 char* fWrite;
348 char* fBlockEnd;
349
350 SkWStream* fStream;
351 Mode fMode;
352 State fState;
353 SkSTArray<16, Scope, true> fScopeStack;
Brian Osman80488222017-08-10 13:29:30 -0400354 SkSTArray<16, bool, true> fNewlineStack;
Brian Osman69fd0082017-08-09 09:25:39 -0400355};
356
357#endif