#pragma once #include #include #include #include "flow/flow.h" #include "flow/Trace.h" #include "fdbclient/JSONDoc.h" class JsonBuilder; class JsonBuilderObject; class JsonBuilderArray; typedef JsonBuilder JsonString; template class JsonBuilderObjectSetter; // Class for building JSON string values. // Default value is null, as in the JSON type class JsonBuilder { protected: enum EType { NULLVALUE, OBJECT, ARRAY }; typedef VectorRef VString; public: // Default value is null, which will be considered "empty" JsonBuilder() : type(NULLVALUE), elements(0), bytes(0) { jsonText.resize(arena, 1); } int getFinalLength() const { return bytes + strlen(getEnd()); } // TODO: Remove the need for this by changing usages to steal this's content std::string getJson() const { std::string result; result.reserve(bytes + 1); for (auto& it : jsonText) { result.append(it.begin(), it.end()); } result.append(getEnd()); return result; } int size() const { return elements; } bool empty() const { return elements == 0; } static JsonBuilderObject makeMessage(const char* name, const char* description); static int coerceAsciiNumberToJSON(const char* s, int len, char* dst); protected: EType type; Arena arena; mutable VectorRef jsonText; int elements; int bytes; // 'raw' write methods inline void write(const char* s, int len) { bytes += len; jsonText.back().append(arena, s, len); } inline void write(const char* s) { write(s, strlen(s)); } inline void write(const StringRef& s) { write((char*)s.begin(), s.size()); } inline void write(char s) { ++bytes; jsonText.back().push_back(arena, s); } // writeValue() methods write JSON form of the value void writeValue(const json_spirit::mValue& val) { switch (val.type()) { case json_spirit::int_type: return writeValue(val.get_int64()); case json_spirit::bool_type: return writeValue(val.get_bool()); case json_spirit::real_type: return writeValue(val.get_real()); case json_spirit::str_type: return writeValue(val.get_str()); default: // Catch-all for objects/arrays return write(json_spirit::write_string(val)); }; } void writeValue(const bool& val) { write(val ? "true" : "false"); } template inline void writeFormat(const char* fmt, const T& val) { VString& dst = jsonText.back(); const int limit = 30; dst.reserve(arena, dst.size() + limit); int len = snprintf(dst.end(), limit, fmt, val); if (len > 0 && len < limit) { dst.extendUnsafeNoReallocNoInit(len); } else { write(format(fmt, val)); } } void writeValue(const int64_t& val) { writeFormat("%lld", val); } void writeValue(const uint64_t& val) { writeFormat("%llu", val); } void writeValue(const int& val) { writeFormat("%d", val); } void writeValue(const double& val) { if (std::isfinite(val)) { writeFormat("%g", val); } else if (std::isnan(val)) { write("-999"); } else { write("1e99"); } } bool shouldEscape(char c) { switch (c) { case '"': case '\\': case '\b': case '\f': case '\n': case '\r': case '\t': return true; default: return false; } } void writeValue(const char* val, int len) { write('"'); int beginCopy = 0; VString& dst = jsonText.back(); for (int i = 0; i < len; i++) { if (shouldEscape(val[i])) { dst.append(arena, val + beginCopy, i - beginCopy); beginCopy = i + 1; write('\\'); write(val[i]); } } if (beginCopy < len) { dst.append(arena, val + beginCopy, len - beginCopy); } write('"'); } inline void writeValue(const std::string& val) { writeValue(val.data(), val.size()); } inline void writeValue(const char* val) { writeValue(val, strlen(val)); } inline void writeValue(const StringRef& s) { writeValue((const char*)s.begin(), s.size()); } // Write the finalized (closed) form of val void writeValue(const JsonBuilder& val) { bytes += val.bytes; jsonText.append(arena, val.jsonText.begin(), val.jsonText.size()); val.jsonText.push_back(arena, VString()); arena.dependsOn(val.arena); write(val.getEnd()); } void writeCoercedAsciiNumber(const char* s, int len) { VString& val = jsonText.back(); val.reserve(arena, val.size() + len + 3); int written = coerceAsciiNumberToJSON(s, len, val.end()); if (written > 0) { val.extendUnsafeNoReallocNoInit(written); } else { write("-999"); } } inline void writeCoercedAsciiNumber(const StringRef& s) { writeCoercedAsciiNumber((const char*)s.begin(), s.size()); } inline void writeCoercedAsciiNumber(const std::string& s) { writeCoercedAsciiNumber(s.data(), s.size()); } // Helper function to add contents of another JsonBuilder to this one. // This is only used by the subclasses to combine like-typed (at compile time) objects, // so it can be assumed that the other object has been initialized with an opening character. void _addContents(const JsonBuilder& other) { if (other.empty()) { return; } if (elements > 0) { write(','); } // Add everything but the first byte of the first string in arr bytes += other.bytes - 1; const VString& front = other.jsonText.front(); jsonText.push_back(arena, front.slice(1, front.size())); jsonText.append(arena, other.jsonText.begin() + 1, other.jsonText.size() - 1); // Both JsonBuilders would now want to write to the same additional VString capacity memory // if they were modified, so force the other (likely to not be modified again) to start a new one. other.jsonText.push_back(arena, VString()); arena.dependsOn(other.arena); elements += other.elements; } // Get the text necessary to finish the JSON string const char* getEnd() const { switch (type) { case NULLVALUE: return "null"; case OBJECT: return "}"; case ARRAY: return "]"; default: return ""; }; } }; class JsonBuilderArray : public JsonBuilder { public: JsonBuilderArray() { type = ARRAY; write('['); } template inline JsonBuilderArray& push_back(const VT& val) { if (elements++ > 0) { write(','); } writeValue(val); return *this; } JsonBuilderArray& addContents(const json_spirit::mArray& arr) { for (auto& v : arr) { push_back(v); } return *this; } JsonBuilderArray& addContents(const JsonBuilderArray& arr) { _addContents(arr); return *this; } }; class JsonBuilderObject : public JsonBuilder { public: JsonBuilderObject() { type = OBJECT; write('{'); } template inline JsonBuilderObject& setKey(const KT& name, const VT& val) { if (elements++ > 0) { write(','); } write('"'); write(name); write("\":"); writeValue(val); return *this; } template inline JsonBuilderObject& setKeyRawNumber(const KT& name, const VT& val) { if (elements++ > 0) { write(','); } write('"'); write(name); write("\":"); writeCoercedAsciiNumber(val); return *this; } template inline JsonBuilderObjectSetter operator[](T&& name); JsonBuilderObject& addContents(const json_spirit::mObject& obj) { for (auto& kv : obj) { setKey(kv.first, kv.second); } return *this; } JsonBuilderObject& addContents(const JsonBuilderObject& obj) { _addContents(obj); return *this; } }; // Template is the key name, accepted as an r-value if possible to avoid copying if it's a string template class JsonBuilderObjectSetter { public: JsonBuilderObjectSetter(JsonBuilderObject& dest, KT&& name) : dest(dest), name(std::forward(name)) {} // Value is accepted as an rvalue if possible template inline void operator=(const VT& value) { dest.setKey(name, value); } protected: JsonBuilderObject& dest; KT name; }; template inline JsonBuilderObjectSetter JsonBuilderObject::operator[](T&& name) { return JsonBuilderObjectSetter(*this, std::forward(name)); }