/* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SkJSONWriter_DEFINED #define SkJSONWriter_DEFINED #include "SkNoncopyable.h" #include "SkStream.h" #include "SkTArray.h" /** * Lightweight class for writing properly structured JSON data. No random-access, everything must * be generated in-order. The resulting JSON is written directly to the SkWStream supplied at * construction time. Output is buffered, so writing to disk (via an SkFILEWStream) is ideal. * * There is a basic state machine to ensure that JSON is structured correctly, and to allow for * (optional) pretty formatting. * * This class adheres to the RFC-4627 usage of JSON (not ECMA-404). In other words, all JSON * created with this class must have a top-level object or array. Free-floating values of other * types are not considered valid. * * Note that all error checking is in the form of asserts - invalid usage in a non-debug build * will simply produce invalid JSON. */ class SkJSONWriter : SkNoncopyable { public: enum class Mode { /** * Output the minimal amount of text. No additional whitespace (including newlines) is * generated. The resulting JSON is suitable for fast parsing and machine consumption. */ kFast, /** * Output human-readable JSON, with indented objects and arrays, and one value per line. * Slightly slower than kFast, and produces data that is somewhat larger. */ kPretty }; /** * Construct a JSON writer that will serialize all the generated JSON to 'stream'. */ SkJSONWriter(SkWStream* stream, Mode mode = Mode::kFast) : fBlock(new char[kBlockSize]) , fWrite(fBlock) , fBlockEnd(fBlock + kBlockSize) , fStream(stream) , fMode(mode) , fState(State::kStart) { fScopeStack.push_back(Scope::kNone); fNewlineStack.push_back(true); } ~SkJSONWriter() { this->flush(); delete[] fBlock; SkASSERT(fScopeStack.count() == 1); SkASSERT(fNewlineStack.count() == 1); } /** * Force all buffered output to be flushed to the underlying stream. */ void flush() { if (fWrite != fBlock) { fStream->write(fBlock, fWrite - fBlock); fWrite = fBlock; } } /** * Append the name (key) portion of an object member. Must be called between beginObject() and * endObject(). If you have both the name and value of an object member, you can simply call * the two argument versions of the other append functions. */ void appendName(const char* name) { if (!name) { return; } SkASSERT(Scope::kObject == this->scope()); SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState); if (State::kObjectValue == fState) { this->write(",", 1); } this->separator(this->multiline()); this->write("\"", 1); this->write(name, strlen(name)); this->write("\":", 2); fState = State::kObjectName; } /** * Adds a new object. A name must be supplied when called between beginObject() and * endObject(). Calls to beginObject() must be balanced by corresponding calls to endObject(). * By default, objects are written out with one named value per line (when in kPretty mode). * This can be overridden for a particular object by passing false for multiline, this will * keep the entire object on a single line. This can help with readability in some situations. * In kFast mode, this parameter is ignored. */ void beginObject(const char* name = nullptr, bool multiline = true) { this->appendName(name); this->beginValue(true); this->write("{", 1); fScopeStack.push_back(Scope::kObject); fNewlineStack.push_back(multiline); fState = State::kObjectBegin; } /** * Ends an object that was previously started with beginObject(). */ void endObject() { SkASSERT(Scope::kObject == this->scope()); SkASSERT(State::kObjectBegin == fState || State::kObjectValue == fState); bool emptyObject = State::kObjectBegin == fState; bool wasMultiline = this->multiline(); this->popScope(); if (!emptyObject) { this->separator(wasMultiline); } this->write("}", 1); } /** * Adds a new array. A name must be supplied when called between beginObject() and * endObject(). Calls to beginArray() must be balanced by corresponding calls to endArray(). * By default, arrays are written out with one value per line (when in kPretty mode). * This can be overridden for a particular array by passing false for multiline, this will * keep the entire array on a single line. This can help with readability in some situations. * In kFast mode, this parameter is ignored. */ void beginArray(const char* name = nullptr, bool multiline = true) { this->appendName(name); this->beginValue(true); this->write("[", 1); fScopeStack.push_back(Scope::kArray); fNewlineStack.push_back(multiline); fState = State::kArrayBegin; } /** * Ends an array that was previous started with beginArray(). */ void endArray() { SkASSERT(Scope::kArray == this->scope()); SkASSERT(State::kArrayBegin == fState || State::kArrayValue == fState); bool emptyArray = State::kArrayBegin == fState; bool wasMultiline = this->multiline(); this->popScope(); if (!emptyArray) { this->separator(wasMultiline); } this->write("]", 1); } /** * Functions for adding values of various types. The single argument versions add un-named * values, so must be called either * - Between beginArray() and endArray() -or- * - Between beginObject() and endObject(), after calling appendName() */ void appendString(const char* value) { this->beginValue(); this->write("\"", 1); if (value) { while (*value) { switch (*value) { case '"': this->write("\\\"", 2); break; case '\\': this->write("\\\\", 2); break; case '\b': this->write("\\b", 2); break; case '\f': this->write("\\f", 2); break; case '\n': this->write("\\n", 2); break; case '\r': this->write("\\r", 2); break; case '\t': this->write("\\t", 2); break; default: this->write(value, 1); break; } value++; } } this->write("\"", 1); } void appendPointer(const void* value) { this->beginValue(); this->appendf("\"%p\"", value); } void appendBool(bool value) { this->beginValue(); if (value) { this->write("true", 4); } else { this->write("false", 5); } } void appendS32(int32_t value) { this->beginValue(); this->appendf("%d", value); } void appendS64(int64_t value); void appendU32(uint32_t value) { this->beginValue(); this->appendf("%u", value); } void appendU64(uint64_t value); void appendFloat(float value) { this->beginValue(); this->appendf("%g", value); } void appendDouble(double value) { this->beginValue(); this->appendf("%g", value); } void appendFloatDigits(float value, int digits) { this->beginValue(); this->appendf("%.*g", digits, value); } void appendDoubleDigits(double value, int digits) { this->beginValue(); this->appendf("%.*g", digits, value); } void appendHexU32(uint32_t value) { this->beginValue(); this->appendf("\"0x%x\"", value); } void appendHexU64(uint64_t value); #define DEFINE_NAMED_APPEND(function, type) \ void function(const char* name, type value) { this->appendName(name); this->function(value); } /** * Functions for adding named values of various types. These add a name field, so must be * called between beginObject() and endObject(). */ DEFINE_NAMED_APPEND(appendString, const char *) DEFINE_NAMED_APPEND(appendPointer, const void *) DEFINE_NAMED_APPEND(appendBool, bool) DEFINE_NAMED_APPEND(appendS32, int32_t) DEFINE_NAMED_APPEND(appendS64, int64_t) DEFINE_NAMED_APPEND(appendU32, uint32_t) DEFINE_NAMED_APPEND(appendU64, uint64_t) DEFINE_NAMED_APPEND(appendFloat, float) DEFINE_NAMED_APPEND(appendDouble, double) DEFINE_NAMED_APPEND(appendHexU32, uint32_t) DEFINE_NAMED_APPEND(appendHexU64, uint64_t) #undef DEFINE_NAMED_APPEND void appendFloatDigits(const char* name, float value, int digits) { this->appendName(name); this->appendFloatDigits(value, digits); } void appendDoubleDigits(const char* name, double value, int digits) { this->appendName(name); this->appendDoubleDigits(value, digits); } private: enum { // Using a 32k scratch block gives big performance wins, but we diminishing returns going // any larger. Even with a 1MB block, time to write a large (~300 MB) JSON file only drops // another ~10%. kBlockSize = 32 * 1024, }; enum class Scope { kNone, kObject, kArray }; enum class State { kStart, kEnd, kObjectBegin, kObjectName, kObjectValue, kArrayBegin, kArrayValue, }; void appendf(const char* fmt, ...); void beginValue(bool structure = false) { SkASSERT(State::kObjectName == fState || State::kArrayBegin == fState || State::kArrayValue == fState || (structure && State::kStart == fState)); if (State::kArrayValue == fState) { this->write(",", 1); } if (Scope::kArray == this->scope()) { this->separator(this->multiline()); } else if (Scope::kObject == this->scope() && Mode::kPretty == fMode) { this->write(" ", 1); } // We haven't added the value yet, but all (non-structure) callers emit something // immediately, so transition state, to simplify the calling code. if (!structure) { fState = Scope::kArray == this->scope() ? State::kArrayValue : State::kObjectValue; } } void separator(bool multiline) { if (Mode::kPretty == fMode) { if (multiline) { this->write("\n", 1); for (int i = 0; i < fScopeStack.count() - 1; ++i) { this->write(" ", 3); } } else { this->write(" ", 1); } } } void write(const char* buf, size_t length) { if (static_cast<size_t>(fBlockEnd - fWrite) < length) { // Don't worry about splitting writes that overflow our block. this->flush(); } if (length > kBlockSize) { // Send particularly large writes straight through to the stream (unbuffered). fStream->write(buf, length); } else { memcpy(fWrite, buf, length); fWrite += length; } } Scope scope() const { SkASSERT(!fScopeStack.empty()); return fScopeStack.back(); } bool multiline() const { SkASSERT(!fNewlineStack.empty()); return fNewlineStack.back(); } void popScope() { fScopeStack.pop_back(); fNewlineStack.pop_back(); switch (this->scope()) { case Scope::kNone: fState = State::kEnd; break; case Scope::kObject: fState = State::kObjectValue; break; case Scope::kArray: fState = State::kArrayValue; break; default: SkDEBUGFAIL("Invalid scope"); break; } } char* fBlock; char* fWrite; char* fBlockEnd; SkWStream* fStream; Mode fMode; State fState; SkSTArray<16, Scope, true> fScopeStack; SkSTArray<16, bool, true> fNewlineStack; }; #endif