Home | History | Annotate | Download | only in utils
      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)
     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 
     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      */
     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      */
     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      */
    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      */
    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      */
    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 
    188     void appendPointer(const void* value) { this->beginValue(); this->appendf("\"%p\"", 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     }
    197     void appendS32(int32_t value) { this->beginValue(); this->appendf("%d", value); }
    198     void appendS64(int64_t value);
    199     void appendU32(uint32_t value) { this->beginValue(); this->appendf("%u", value); }
    200     void appendU64(uint64_t value);
    201     void appendFloat(float value) { this->beginValue(); this->appendf("%f", value); }
    202     void appendDouble(double value) { this->beginValue(); this->appendf("%f", value); }
    203     void appendFloatDigits(float value, int digits) {
    204         this->beginValue();
    205         this->appendf("%.*f", digits, value);
    206     }
    207     void appendDoubleDigits(double value, int digits) {
    208         this->beginValue();
    209         this->appendf("%.*f", digits, value);
    210     }
    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      */
    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     }
    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 
    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 
    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 
    317     Scope scope() const {
    318         SkASSERT(!fScopeStack.empty());
    319         return fScopeStack.back();
    320     }
    321 
    322     bool multiline() const {
    323         SkASSERT(!fNewlineStack.empty());
    324         return fNewlineStack.back();
    325     }
    326 
    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