diff --git a/ext/native/json/json_writer.cpp b/ext/native/json/json_writer.cpp index ec9019c50d..ec43a4fae8 100644 --- a/ext/native/json/json_writer.cpp +++ b/ext/native/json/json_writer.cpp @@ -1,6 +1,9 @@ +#include #include "json/json_writer.h" -JsonWriter::JsonWriter() { +JsonWriter::JsonWriter(int flags) { + pretty_ = (flags & PRETTY) != 0; + str_.imbue(std::locale::classic()); } JsonWriter::~JsonWriter() { @@ -13,15 +16,24 @@ void JsonWriter::begin() { void JsonWriter::end() { pop(); - str_ << "\n"; + if (pretty_) + str_ << "\n"; } const char *JsonWriter::indent(int n) const { - static const char * const whitespace = " "; + if (!pretty_) + return ""; + static const char * const whitespace = " "; + if (n > 32) { + // Avoid crash. + return whitespace; + } return whitespace + (32 - n); } const char *JsonWriter::indent() const { + if (!pretty_) + return ""; int amount = (int)stack_.size() + 1; amount *= 2; // 2-space indent. return indent(amount); @@ -37,7 +49,7 @@ const char *JsonWriter::comma() const { if (stack_.back().first) { return ""; } else { - return ","; + return ",\n"; } } @@ -45,17 +57,21 @@ const char *JsonWriter::arrayComma() const { if (stack_.back().first) { return "\n"; } else { - return ", "; + return pretty_ ? ", " : ","; } } void JsonWriter::pushDict(const char *name) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": {"; + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << "\": {"; stack_.push_back(StackEntry(DICT)); } void JsonWriter::pushArray(const char *name) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": ["; + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << "\": ["; stack_.push_back(StackEntry(ARRAY)); } @@ -65,7 +81,9 @@ void JsonWriter::writeBool(bool value) { } void JsonWriter::writeBool(const char *name, bool value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": " << (value ? "true" : "false"); + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << "\": " << (value ? "true" : "false"); stack_.back().first = false; } @@ -75,7 +93,9 @@ void JsonWriter::writeInt(int value) { } void JsonWriter::writeInt(const char *name, int value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": " << value; + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << "\": " << value; stack_.back().first = false; } @@ -85,31 +105,78 @@ void JsonWriter::writeFloat(double value) { } void JsonWriter::writeFloat(const char *name, double value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": " << value; + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << "\": " << value; stack_.back().first = false; } void JsonWriter::writeString(const char *value) { - str_ << arrayComma() << arrayIndent() << "\"" << value << "\""; + str_ << arrayComma() << arrayIndent() << "\""; + writeEscapedString(value); + str_ << "\""; stack_.back().first = false; } void JsonWriter::writeString(const char *name, const char *value) { - str_ << comma() << "\n" << indent() << "\"" << name << "\": \"" << value << "\""; + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << "\": \""; + writeEscapedString(value); + str_ << "\""; stack_.back().first = false; } void JsonWriter::pop() { BlockType type = stack_.back().type; stack_.pop_back(); + if (pretty_) + str_ << "\n" << indent(); switch (type) { case ARRAY: - str_ << "\n" << indent() << "]"; + str_ << "]"; break; case DICT: - str_ << "\n" << indent() << "}"; + str_ << "}"; break; } if (stack_.size() > 0) stack_.back().first = false; } + +void JsonWriter::writeEscapedString(const char *str) { + size_t pos = 0; + size_t len = strlen(str); + + auto update = [&](size_t current, size_t skip = 0) { + size_t end = current; + if (pos < end) + str_ << std::string(str + pos, end - pos); + pos = end + skip; + }; + + for (size_t i = 0; i < len; ++i) { + if (str[i] == '\\' || str[i] == '"' || str[i] == '/') { + update(i); + str_ << '\\'; + } else if (str[i] == '\r') { + update(i, 1); + str_ << "\\r"; + } else if (str[i] == '\n') { + update(i, 1); + str_ << "\\n"; + } else if (str[i] == '\t') { + update(i, 1); + str_ << "\\t"; + } else if (str[i] < 32) { + update(i, 1); + str_ << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)str[i] << std::dec << std::setw(0); + } + } + + if (pos != 0) { + update(len); + } else { + str_ << str; + } +} diff --git a/ext/native/json/json_writer.h b/ext/native/json/json_writer.h index b58ca37f6d..378a09f5b9 100644 --- a/ext/native/json/json_writer.h +++ b/ext/native/json/json_writer.h @@ -16,7 +16,7 @@ class JsonWriter { public: - JsonWriter(); + JsonWriter(int flags = NORMAL); ~JsonWriter(); void begin(); void end(); @@ -31,17 +31,33 @@ public: void writeFloat(const char *name, double value); void writeString(const char *value); void writeString(const char *name, const char *value); + void writeString(const std::string &value) { + writeString(value.c_str()); + } + void writeString(const char *name, const std::string &value) { + writeString(name, value.c_str()); + } + void writeString(const std::string &name, const std::string &value) { + writeString(name.c_str(), value.c_str()); + } std::string str() const { return str_.str(); } + enum { + NORMAL = 0, + PRETTY = 1, + }; + private: const char *indent(int n) const; const char *comma() const; const char *arrayComma() const; const char *indent() const; const char *arrayIndent() const; + void writeEscapedString(const char *s); + enum BlockType { ARRAY, DICT, @@ -53,4 +69,5 @@ private: }; std::vector stack_; std::ostringstream str_; + bool pretty_; };