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