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