foundationdb/fdbclient/JsonBuilder.h

348 lines
7.8 KiB
C++

#pragma once
#include <string>
#include <vector>
#include <cmath>
#include "flow/flow.h"
#include "flow/Trace.h"
#include "fdbclient/JSONDoc.h"
class JsonBuilder;
class JsonBuilderObject;
class JsonBuilderArray;
typedef JsonBuilder JsonString;
template <typename T> 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<char> 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<VString> 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<typename T> 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<typename VT> 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<typename KT, typename VT> inline JsonBuilderObject & setKey(const KT &name, const VT &val) {
if(elements++ > 0) {
write(',');
}
write('"');
write(name);
write("\":");
writeValue(val);
return *this;
}
template<typename KT, typename VT> inline JsonBuilderObject & setKeyRawNumber(const KT &name, const VT &val) {
if(elements++ > 0) {
write(',');
}
write('"');
write(name);
write("\":");
writeCoercedAsciiNumber(val);
return *this;
}
template<typename T> inline JsonBuilderObjectSetter<T> 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<typename KT>
class JsonBuilderObjectSetter {
public:
JsonBuilderObjectSetter(JsonBuilderObject &dest, KT &&name) : dest(dest), name(std::forward<KT>(name)) {}
// Value is accepted as an rvalue if possible
template <class VT> inline void operator=(const VT &value) {
dest.setKey(name, value);
}
protected:
JsonBuilderObject& dest;
KT name;
};
template<typename T> inline JsonBuilderObjectSetter<T> JsonBuilderObject::operator[](T &&name) {
return JsonBuilderObjectSetter<T>(*this, std::forward<T>(name));
}