323 lines
7.8 KiB
C++
323 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));
|
|
}
|