• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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"
12 #include "SkTArray.h"
13 
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  */
29 class SkJSONWriter : SkNoncopyable {
30 public:
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)
fBlock(new char[kBlockSize])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);
56         fNewlineStack.push_back(true);
57     }
58 
~SkJSONWriter()59     ~SkJSONWriter() {
60         this->flush();
61         delete[] fBlock;
62         SkASSERT(fScopeStack.count() == 1);
63         SkASSERT(fNewlineStack.count() == 1);
64     }
65 
66     /**
67      *  Force all buffered output to be flushed to the underlying stream.
68      */
flush()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      */
appendName(const char * name)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         }
90         this->separator(this->multiline());
91         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().
100      *  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.
104      */
105     void beginObject(const char* name = nullptr, bool multiline = true) {
106         this->appendName(name);
107         this->beginValue(true);
108         this->write("{", 1);
109         fScopeStack.push_back(Scope::kObject);
110         fNewlineStack.push_back(multiline);
111         fState = State::kObjectBegin;
112     }
113 
114     /**
115      *  Ends an object that was previously started with beginObject().
116      */
endObject()117     void endObject() {
118         SkASSERT(Scope::kObject == this->scope());
119         SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState);
120         bool emptyObject = State::kObjectBegin == fState;
121         bool wasMultiline = this->multiline();
122         this->popScope();
123         if (!emptyObject) {
124             this->separator(wasMultiline);
125         }
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().
132      *  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.
136      */
137     void beginArray(const char* name = nullptr, bool multiline = true) {
138         this->appendName(name);
139         this->beginValue(true);
140         this->write("[", 1);
141         fScopeStack.push_back(Scope::kArray);
142         fNewlineStack.push_back(multiline);
143         fState = State::kArrayBegin;
144     }
145 
146     /**
147      *  Ends an array that was previous started with beginArray().
148      */
endArray()149     void endArray() {
150         SkASSERT(Scope::kArray == this->scope());
151         SkASSERT(State::kArrayBegin == fState || State::kArrayValue == fState);
152         bool emptyArray = State::kArrayBegin == fState;
153         bool wasMultiline = this->multiline();
154         this->popScope();
155         if (!emptyArray) {
156             this->separator(wasMultiline);
157         }
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      */
appendString(const char * value)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 
appendPointer(const void * value)188     void appendPointer(const void* value) { this->beginValue(); this->appendf("\"%p\"", value); }
appendBool(bool 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     }
appendS32(int32_t value)197     void appendS32(int32_t value) { this->beginValue(); this->appendf("%d", value); }
198     void appendS64(int64_t value);
appendU32(uint32_t value)199     void appendU32(uint32_t value) { this->beginValue(); this->appendf("%u", value); }
200     void appendU64(uint64_t value);
appendFloat(float value)201     void appendFloat(float value) { this->beginValue(); this->appendf("%f", value); }
appendDouble(double value)202     void appendDouble(double value) { this->beginValue(); this->appendf("%f", value); }
appendFloatDigits(float value,int digits)203     void appendFloatDigits(float value, int digits) {
204         this->beginValue();
205         this->appendf("%.*f", digits, value);
206     }
appendDoubleDigits(double value,int digits)207     void appendDoubleDigits(double value, int digits) {
208         this->beginValue();
209         this->appendf("%.*f", digits, value);
210     }
appendHexU32(uint32_t value)211     void appendHexU32(uint32_t value) { this->beginValue(); this->appendf("\"0x%x\"", value); }
212     void appendHexU64(uint64_t value);
213 
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      */
DEFINE_NAMED_APPEND(appendString,const char *)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 
235     void appendFloatDigits(const char* name, float value, int digits) {
236         this->appendName(name);
237         this->appendFloatDigits(value, digits);
238     }
appendDoubleDigits(const char * name,double value,int digits)239     void appendDoubleDigits(const char* name, double value, int digits) {
240         this->appendName(name);
241         this->appendDoubleDigits(value, digits);
242     }
243 
244 private:
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 
268     void appendf(const char* fmt, ...);
269 
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()) {
279             this->separator(this->multiline());
280         } 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 
separator(bool multiline)290     void separator(bool multiline) {
291         if (Mode::kPretty == fMode) {
292             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);
299             }
300         }
301     }
302 
write(const char * buf,size_t length)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 
scope()317     Scope scope() const {
318         SkASSERT(!fScopeStack.empty());
319         return fScopeStack.back();
320     }
321 
multiline()322     bool multiline() const {
323         SkASSERT(!fNewlineStack.empty());
324         return fNewlineStack.back();
325     }
326 
popScope()327     void popScope() {
328         fScopeStack.pop_back();
329         fNewlineStack.pop_back();
330         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;
354     SkSTArray<16, bool, true> fNewlineStack;
355 };
356 
357 #endif
358