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