summaryrefslogtreecommitdiffstats
path: root/src/common/ceph_json.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/ceph_json.cc')
-rw-r--r--src/common/ceph_json.cc991
1 files changed, 991 insertions, 0 deletions
diff --git a/src/common/ceph_json.cc b/src/common/ceph_json.cc
new file mode 100644
index 000000000..b5f875671
--- /dev/null
+++ b/src/common/ceph_json.cc
@@ -0,0 +1,991 @@
+#include "common/ceph_json.h"
+#include "include/utime.h"
+
+// for testing DELETE ME
+#include <fstream>
+#include <include/types.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include "json_spirit/json_spirit_writer_template.h"
+
+using namespace json_spirit;
+
+using std::ifstream;
+using std::pair;
+using std::ostream;
+using std::string;
+using std::vector;
+
+using ceph::bufferlist;
+using ceph::Formatter;
+
+#define dout_subsys ceph_subsys_rgw
+
+static JSONFormattable default_formattable;
+
+void encode_json(const char *name, const JSONObj::data_val& v, Formatter *f)
+{
+ if (v.quoted) {
+ encode_json(name, v.str, f);
+ } else {
+ f->dump_format_unquoted(name, "%s", v.str.c_str());
+ }
+}
+
+JSONObjIter::JSONObjIter()
+{
+}
+
+JSONObjIter::~JSONObjIter()
+{
+}
+
+void JSONObjIter::set(const JSONObjIter::map_iter_t &_cur, const JSONObjIter::map_iter_t &_last)
+{
+ cur = _cur;
+ last = _last;
+}
+
+void JSONObjIter::operator++()
+{
+ if (cur != last)
+ ++cur;
+}
+
+JSONObj *JSONObjIter::operator*()
+{
+ return cur->second;
+}
+
+// does not work, FIXME
+ostream& operator<<(ostream &out, const JSONObj &obj) {
+ out << obj.name << ": " << obj.val;
+ return out;
+}
+
+JSONObj::~JSONObj()
+{
+ for (auto iter = children.begin(); iter != children.end(); ++iter) {
+ JSONObj *obj = iter->second;
+ delete obj;
+ }
+}
+
+
+void JSONObj::add_child(string el, JSONObj *obj)
+{
+ children.insert(pair<string, JSONObj *>(el, obj));
+}
+
+bool JSONObj::get_attr(string name, data_val& attr)
+{
+ auto iter = attr_map.find(name);
+ if (iter == attr_map.end())
+ return false;
+ attr = iter->second;
+ return true;
+}
+
+JSONObjIter JSONObj::find(const string& name)
+{
+ JSONObjIter iter;
+ auto first = children.find(name);
+ if (first != children.end()) {
+ auto last = children.upper_bound(name);
+ iter.set(first, last);
+ }
+ return iter;
+}
+
+JSONObjIter JSONObj::find_first()
+{
+ JSONObjIter iter;
+ iter.set(children.begin(), children.end());
+ return iter;
+}
+
+JSONObjIter JSONObj::find_first(const string& name)
+{
+ JSONObjIter iter;
+ auto first = children.find(name);
+ iter.set(first, children.end());
+ return iter;
+}
+
+JSONObj *JSONObj::find_obj(const string& name)
+{
+ JSONObjIter iter = find(name);
+ if (iter.end())
+ return NULL;
+
+ return *iter;
+}
+
+bool JSONObj::get_data(const string& key, data_val *dest)
+{
+ JSONObj *obj = find_obj(key);
+ if (!obj)
+ return false;
+
+ *dest = obj->get_data_val();
+
+ return true;
+}
+
+/* accepts a JSON Array or JSON Object contained in
+ * a JSON Spirit Value, v, and creates a JSONObj for each
+ * child contained in v
+ */
+void JSONObj::handle_value(Value v)
+{
+ if (v.type() == obj_type) {
+ Object temp_obj = v.get_obj();
+ for (Object::size_type i = 0; i < temp_obj.size(); i++) {
+ Pair temp_pair = temp_obj[i];
+ string temp_name = temp_pair.name_;
+ Value temp_value = temp_pair.value_;
+ JSONObj *child = new JSONObj;
+ child->init(this, temp_value, temp_name);
+ add_child(temp_name, child);
+ }
+ } else if (v.type() == array_type) {
+ Array temp_array = v.get_array();
+ Value value;
+
+ for (unsigned j = 0; j < temp_array.size(); j++) {
+ Value cur = temp_array[j];
+ string temp_name;
+
+ JSONObj *child = new JSONObj;
+ child->init(this, cur, temp_name);
+ add_child(child->get_name(), child);
+ }
+ }
+}
+
+void JSONObj::init(JSONObj *p, Value v, string n)
+{
+ name = n;
+ parent = p;
+ data = v;
+
+ handle_value(v);
+ if (v.type() == str_type) {
+ val.set(v.get_str(), true);
+ } else {
+ val.set(json_spirit::write_string(v), false);
+ }
+ attr_map.insert(pair<string,data_val>(name, val));
+}
+
+JSONObj *JSONObj::get_parent()
+{
+ return parent;
+}
+
+bool JSONObj::is_object()
+{
+ return (data.type() == obj_type);
+}
+
+bool JSONObj::is_array()
+{
+ return (data.type() == array_type);
+}
+
+vector<string> JSONObj::get_array_elements()
+{
+ vector<string> elements;
+ Array temp_array;
+
+ if (data.type() == array_type)
+ temp_array = data.get_array();
+
+ int array_size = temp_array.size();
+ if (array_size > 0)
+ for (int i = 0; i < array_size; i++) {
+ Value temp_value = temp_array[i];
+ string temp_string;
+ temp_string = write(temp_value, raw_utf8);
+ elements.push_back(temp_string);
+ }
+
+ return elements;
+}
+
+JSONParser::JSONParser() : buf_len(0), success(true)
+{
+}
+
+JSONParser::~JSONParser()
+{
+}
+
+
+
+void JSONParser::handle_data(const char *s, int len)
+{
+ json_buffer.append(s, len); // check for problems with null termination FIXME
+ buf_len += len;
+}
+
+// parse a supplied JSON fragment
+bool JSONParser::parse(const char *buf_, int len)
+{
+ if (!buf_) {
+ set_failure();
+ return false;
+ }
+
+ string json_string(buf_, len);
+ success = read(json_string, data);
+ if (success) {
+ handle_value(data);
+ if (data.type() != obj_type &&
+ data.type() != array_type) {
+ if (data.type() == str_type) {
+ val.set(data.get_str(), true);
+ } else {
+ val.set(json_spirit::write_string(data), false);
+ }
+ }
+ } else {
+ set_failure();
+ }
+
+ return success;
+}
+
+// parse the internal json_buffer up to len
+bool JSONParser::parse(int len)
+{
+ string json_string = json_buffer.substr(0, len);
+ success = read(json_string, data);
+ if (success)
+ handle_value(data);
+ else
+ set_failure();
+
+ return success;
+}
+
+// parse the complete internal json_buffer
+bool JSONParser::parse()
+{
+ success = read(json_buffer, data);
+ if (success)
+ handle_value(data);
+ else
+ set_failure();
+
+ return success;
+}
+
+// parse a supplied ifstream, for testing. DELETE ME
+bool JSONParser::parse(const char *file_name)
+{
+ ifstream is(file_name);
+ success = read(is, data);
+ if (success)
+ handle_value(data);
+ else
+ set_failure();
+
+ return success;
+}
+
+
+void decode_json_obj(long& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+ const char *start = s.c_str();
+ char *p;
+
+ errno = 0;
+ val = strtol(start, &p, 10);
+
+ /* Check for various possible errors */
+
+ if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) ||
+ (errno != 0 && val == 0)) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+
+ if (p == start) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+
+ while (*p != '\0') {
+ if (!isspace(*p)) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+ p++;
+ }
+}
+
+void decode_json_obj(unsigned long& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+ const char *start = s.c_str();
+ char *p;
+
+ errno = 0;
+ val = strtoul(start, &p, 10);
+
+ /* Check for various possible errors */
+
+ if ((errno == ERANGE && val == ULONG_MAX) ||
+ (errno != 0 && val == 0)) {
+ throw JSONDecoder::err("failed to number");
+ }
+
+ if (p == start) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+
+ while (*p != '\0') {
+ if (!isspace(*p)) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+ p++;
+ }
+}
+
+void decode_json_obj(long long& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+ const char *start = s.c_str();
+ char *p;
+
+ errno = 0;
+ val = strtoll(start, &p, 10);
+
+ /* Check for various possible errors */
+
+ if ((errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN)) ||
+ (errno != 0 && val == 0)) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+
+ if (p == start) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+
+ while (*p != '\0') {
+ if (!isspace(*p)) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+ p++;
+ }
+}
+
+void decode_json_obj(unsigned long long& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+ const char *start = s.c_str();
+ char *p;
+
+ errno = 0;
+ val = strtoull(start, &p, 10);
+
+ /* Check for various possible errors */
+
+ if ((errno == ERANGE && val == ULLONG_MAX) ||
+ (errno != 0 && val == 0)) {
+ throw JSONDecoder::err("failed to number");
+ }
+
+ if (p == start) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+
+ while (*p != '\0') {
+ if (!isspace(*p)) {
+ throw JSONDecoder::err("failed to parse number");
+ }
+ p++;
+ }
+}
+
+void decode_json_obj(int& val, JSONObj *obj)
+{
+ long l;
+ decode_json_obj(l, obj);
+#if LONG_MAX > INT_MAX
+ if (l > INT_MAX || l < INT_MIN) {
+ throw JSONDecoder::err("integer out of range");
+ }
+#endif
+
+ val = (int)l;
+}
+
+void decode_json_obj(unsigned& val, JSONObj *obj)
+{
+ unsigned long l;
+ decode_json_obj(l, obj);
+#if ULONG_MAX > UINT_MAX
+ if (l > UINT_MAX) {
+ throw JSONDecoder::err("unsigned integer out of range");
+ }
+#endif
+
+ val = (unsigned)l;
+}
+
+void decode_json_obj(bool& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+ if (strcasecmp(s.c_str(), "true") == 0) {
+ val = true;
+ return;
+ }
+ if (strcasecmp(s.c_str(), "false") == 0) {
+ val = false;
+ return;
+ }
+ int i;
+ decode_json_obj(i, obj);
+ val = (bool)i;
+}
+
+void decode_json_obj(bufferlist& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+
+ bufferlist bl;
+ bl.append(s.c_str(), s.size());
+ try {
+ val.decode_base64(bl);
+ } catch (ceph::buffer::error& err) {
+ throw JSONDecoder::err("failed to decode base64");
+ }
+}
+
+void decode_json_obj(utime_t& val, JSONObj *obj)
+{
+ string s = obj->get_data();
+ uint64_t epoch;
+ uint64_t nsec;
+ int r = utime_t::parse_date(s, &epoch, &nsec);
+ if (r == 0) {
+ val = utime_t(epoch, nsec);
+ } else {
+ throw JSONDecoder::err("failed to decode utime_t");
+ }
+}
+
+void decode_json_obj(ceph::real_time& val, JSONObj *obj)
+{
+ const std::string& s = obj->get_data();
+ uint64_t epoch;
+ uint64_t nsec;
+ int r = utime_t::parse_date(s, &epoch, &nsec);
+ if (r == 0) {
+ using namespace std::chrono;
+ val = real_time{seconds(epoch) + nanoseconds(nsec)};
+ } else {
+ throw JSONDecoder::err("failed to decode real_time");
+ }
+}
+
+void decode_json_obj(ceph::coarse_real_time& val, JSONObj *obj)
+{
+ const std::string& s = obj->get_data();
+ uint64_t epoch;
+ uint64_t nsec;
+ int r = utime_t::parse_date(s, &epoch, &nsec);
+ if (r == 0) {
+ using namespace std::chrono;
+ val = coarse_real_time{seconds(epoch) + nanoseconds(nsec)};
+ } else {
+ throw JSONDecoder::err("failed to decode coarse_real_time");
+ }
+}
+
+void decode_json_obj(ceph_dir_layout& i, JSONObj *obj){
+
+ unsigned tmp;
+ JSONDecoder::decode_json("dir_hash", tmp, obj, true);
+ i.dl_dir_hash = tmp;
+ JSONDecoder::decode_json("unused1", tmp, obj, true);
+ i.dl_unused1 = tmp;
+ JSONDecoder::decode_json("unused2", tmp, obj, true);
+ i.dl_unused2 = tmp;
+ JSONDecoder::decode_json("unused3", tmp, obj, true);
+ i.dl_unused3 = tmp;
+}
+
+void encode_json(const char *name, const string& val, Formatter *f)
+{
+ f->dump_string(name, val);
+}
+
+void encode_json(const char *name, const char *val, Formatter *f)
+{
+ f->dump_string(name, val);
+}
+
+void encode_json(const char *name, bool val, Formatter *f)
+{
+ string s;
+ if (val)
+ s = "true";
+ else
+ s = "false";
+
+ f->dump_string(name, s);
+}
+
+void encode_json(const char *name, int val, Formatter *f)
+{
+ f->dump_int(name, val);
+}
+
+void encode_json(const char *name, long val, Formatter *f)
+{
+ f->dump_int(name, val);
+}
+
+void encode_json(const char *name, unsigned val, Formatter *f)
+{
+ f->dump_unsigned(name, val);
+}
+
+void encode_json(const char *name, unsigned long val, Formatter *f)
+{
+ f->dump_unsigned(name, val);
+}
+
+void encode_json(const char *name, unsigned long long val, Formatter *f)
+{
+ f->dump_unsigned(name, val);
+}
+
+void encode_json(const char *name, long long val, Formatter *f)
+{
+ f->dump_int(name, val);
+}
+
+void encode_json(const char *name, const utime_t& val, Formatter *f)
+{
+ val.gmtime(f->dump_stream(name));
+}
+
+void encode_json(const char *name, const ceph::real_time& val, Formatter *f)
+{
+ encode_json(name, utime_t{val}, f);
+}
+
+void encode_json(const char *name, const ceph::coarse_real_time& val, Formatter *f)
+{
+ encode_json(name, utime_t{val}, f);
+}
+
+void encode_json(const char *name, const bufferlist& bl, Formatter *f)
+{
+ /* need to copy data from bl, as it is const bufferlist */
+ bufferlist src = bl;
+
+ bufferlist b64;
+ src.encode_base64(b64);
+
+ string s(b64.c_str(), b64.length());
+
+ encode_json(name, s, f);
+}
+
+
+
+/* JSONFormattable */
+
+const JSONFormattable& JSONFormattable::operator[](const string& name) const
+{
+ auto i = obj.find(name);
+ if (i == obj.end()) {
+ return default_formattable;
+ }
+ return i->second;
+}
+
+const JSONFormattable& JSONFormattable::operator[](size_t index) const
+{
+ if (index >= arr.size()) {
+ return default_formattable;
+ }
+ return arr[index];
+}
+
+JSONFormattable& JSONFormattable::operator[](const string& name)
+{
+ auto i = obj.find(name);
+ if (i == obj.end()) {
+ return default_formattable;
+ }
+ return i->second;
+}
+
+JSONFormattable& JSONFormattable::operator[](size_t index)
+{
+ if (index >= arr.size()) {
+ return default_formattable;
+ }
+ return arr[index];
+}
+
+bool JSONFormattable::exists(const string& name) const
+{
+ auto i = obj.find(name);
+ return (i != obj.end());
+}
+
+bool JSONFormattable::exists(size_t index) const
+{
+ return (index < arr.size());
+}
+
+bool JSONFormattable::find(const string& name, string *val) const
+{
+ auto i = obj.find(name);
+ if (i == obj.end()) {
+ return false;
+ }
+ *val = i->second.val();
+ return true;
+}
+
+int JSONFormattable::val_int() const {
+ return atoi(value.str.c_str());
+}
+
+long JSONFormattable::val_long() const {
+ return atol(value.str.c_str());
+}
+
+long long JSONFormattable::val_long_long() const {
+ return atoll(value.str.c_str());
+}
+
+bool JSONFormattable::val_bool() const {
+ return (boost::iequals(value.str, "true") ||
+ boost::iequals(value.str, "on") ||
+ boost::iequals(value.str, "yes") ||
+ boost::iequals(value.str, "1"));
+}
+
+string JSONFormattable::def(const string& def_val) const {
+ if (type == FMT_NONE) {
+ return def_val;
+ }
+ return val();
+}
+
+int JSONFormattable::def(int def_val) const {
+ if (type == FMT_NONE) {
+ return def_val;
+ }
+ return val_int();
+}
+
+bool JSONFormattable::def(bool def_val) const {
+ if (type == FMT_NONE) {
+ return def_val;
+ }
+ return val_bool();
+}
+
+string JSONFormattable::get(const string& name, const string& def_val) const
+{
+ return (*this)[name].def(def_val);
+}
+
+int JSONFormattable::get_int(const string& name, int def_val) const
+{
+ return (*this)[name].def(def_val);
+}
+
+bool JSONFormattable::get_bool(const string& name, bool def_val) const
+{
+ return (*this)[name].def(def_val);
+}
+
+struct field_entity {
+ bool is_obj{false}; /* either obj field or array entity */
+ string name; /* if obj */
+ int index{0}; /* if array */
+ bool append{false};
+
+ field_entity() {}
+ explicit field_entity(const string& n) : is_obj(true), name(n) {}
+ explicit field_entity(int i) : is_obj(false), index(i) {}
+};
+
+static int parse_entity(const string& s, vector<field_entity> *result)
+{
+ size_t ofs = 0;
+
+ while (ofs < s.size()) {
+ size_t next_arr = s.find('[', ofs);
+ if (next_arr == string::npos) {
+ if (ofs != 0) {
+ return -EINVAL;
+ }
+ result->push_back(field_entity(s));
+ return 0;
+ }
+ if (next_arr > ofs) {
+ string field = s.substr(ofs, next_arr - ofs);
+ result->push_back(field_entity(field));
+ ofs = next_arr;
+ }
+ size_t end_arr = s.find(']', next_arr + 1);
+ if (end_arr == string::npos) {
+ return -EINVAL;
+ }
+
+ string index_str = s.substr(next_arr + 1, end_arr - next_arr - 1);
+
+ ofs = end_arr + 1;
+
+ if (!index_str.empty()) {
+ result->push_back(field_entity(atoi(index_str.c_str())));
+ } else {
+ field_entity f;
+ f.append = true;
+ result->push_back(f);
+ }
+ }
+ return 0;
+}
+
+static bool is_numeric(const string& val)
+{
+ try {
+ boost::lexical_cast<double>(val);
+ } catch (const boost::bad_lexical_cast& e) {
+ return false;
+ }
+ return true;
+}
+
+int JSONFormattable::set(const string& name, const string& val)
+{
+ boost::escaped_list_separator<char> els('\\', '.', '"');
+ boost::tokenizer<boost::escaped_list_separator<char> > tok(name, els);
+
+ JSONFormattable *f = this;
+
+ JSONParser jp;
+
+ bool is_valid_json = jp.parse(val.c_str(), val.size());
+
+ for (const auto& i : tok) {
+ vector<field_entity> v;
+ int ret = parse_entity(i, &v);
+ if (ret < 0) {
+ return ret;
+ }
+ for (const auto& vi : v) {
+ if (f->type == FMT_NONE) {
+ if (vi.is_obj) {
+ f->type = FMT_OBJ;
+ } else {
+ f->type = FMT_ARRAY;
+ }
+ }
+
+ if (f->type == FMT_OBJ) {
+ if (!vi.is_obj) {
+ return -EINVAL;
+ }
+ f = &f->obj[vi.name];
+ } else if (f->type == FMT_ARRAY) {
+ if (vi.is_obj) {
+ return -EINVAL;
+ }
+ int index = vi.index;
+ if (vi.append) {
+ index = f->arr.size();
+ } else if (index < 0) {
+ index = f->arr.size() + index;
+ if (index < 0) {
+ return -EINVAL; /* out of bounds */
+ }
+ }
+ if ((size_t)index >= f->arr.size()) {
+ f->arr.resize(index + 1);
+ }
+ f = &f->arr[index];
+ }
+ }
+ }
+
+ if (is_valid_json) {
+ f->decode_json(&jp);
+ } else {
+ f->type = FMT_VALUE;
+ f->value.set(val, !is_numeric(val));
+ }
+
+ return 0;
+}
+
+int JSONFormattable::erase(const string& name)
+{
+ boost::escaped_list_separator<char> els('\\', '.', '"');
+ boost::tokenizer<boost::escaped_list_separator<char> > tok(name, els);
+
+ JSONFormattable *f = this;
+ JSONFormattable *parent = nullptr;
+ field_entity last_entity;
+
+ for (auto& i : tok) {
+ vector<field_entity> v;
+ int ret = parse_entity(i, &v);
+ if (ret < 0) {
+ return ret;
+ }
+ for (const auto& vi : v) {
+ if (f->type == FMT_NONE ||
+ f->type == FMT_VALUE) {
+ if (vi.is_obj) {
+ f->type = FMT_OBJ;
+ } else {
+ f->type = FMT_ARRAY;
+ }
+ }
+
+ parent = f;
+
+ if (f->type == FMT_OBJ) {
+ if (!vi.is_obj) {
+ return -EINVAL;
+ }
+ auto iter = f->obj.find(vi.name);
+ if (iter == f->obj.end()) {
+ return 0; /* nothing to erase */
+ }
+ f = &iter->second;
+ } else if (f->type == FMT_ARRAY) {
+ if (vi.is_obj) {
+ return -EINVAL;
+ }
+ int index = vi.index;
+ if (index < 0) {
+ index = f->arr.size() + index;
+ if (index < 0) { /* out of bounds, nothing to remove */
+ return 0;
+ }
+ }
+ if ((size_t)index >= f->arr.size()) {
+ return 0; /* index beyond array boundaries */
+ }
+ f = &f->arr[index];
+ }
+ last_entity = vi;
+ }
+ }
+
+ if (!parent) {
+ *this = JSONFormattable(); /* erase everything */
+ } else {
+ if (last_entity.is_obj) {
+ parent->obj.erase(last_entity.name);
+ } else {
+ int index = (last_entity.index >= 0 ? last_entity.index : parent->arr.size() + last_entity.index);
+ if (index < 0 || (size_t)index >= parent->arr.size()) {
+ return 0;
+ }
+ parent->arr.erase(parent->arr.begin() + index);
+ }
+ }
+
+ return 0;
+}
+
+void JSONFormattable::derive_from(const JSONFormattable& parent)
+{
+ for (auto& o : parent.obj) {
+ if (obj.find(o.first) == obj.end()) {
+ obj[o.first] = o.second;
+ }
+ }
+}
+
+void encode_json(const char *name, const JSONFormattable& v, Formatter *f)
+{
+ v.encode_json(name, f);
+}
+
+void JSONFormattable::encode_json(const char *name, Formatter *f) const
+{
+ switch (type) {
+ case JSONFormattable::FMT_VALUE:
+ ::encode_json(name, value, f);
+ break;
+ case JSONFormattable::FMT_ARRAY:
+ ::encode_json(name, arr, f);
+ break;
+ case JSONFormattable::FMT_OBJ:
+ f->open_object_section(name);
+ for (auto iter : obj) {
+ ::encode_json(iter.first.c_str(), iter.second, f);
+ }
+ f->close_section();
+ break;
+ case JSONFormattable::FMT_NONE:
+ break;
+ }
+}
+
+bool JSONFormattable::handle_value(std::string_view name, std::string_view s, bool quoted) {
+ JSONFormattable *new_val;
+ if (cur_enc->is_array()) {
+ cur_enc->arr.push_back(JSONFormattable());
+ new_val = &cur_enc->arr.back();
+ } else {
+ cur_enc->set_type(JSONFormattable::FMT_OBJ);
+ new_val = &cur_enc->obj[string{name}];
+ }
+ new_val->set_type(JSONFormattable::FMT_VALUE);
+ new_val->value.set(s, quoted);
+
+ return false;
+}
+
+bool JSONFormattable::handle_open_section(std::string_view name,
+ const char *ns,
+ bool section_is_array) {
+ if (cur_enc->is_array()) {
+ cur_enc->arr.push_back(JSONFormattable());
+ cur_enc = &cur_enc->arr.back();
+ } else if (enc_stack.size() > 1) {
+ /* only open a new section if already nested,
+ * otherwise root is the container
+ */
+ cur_enc = &cur_enc->obj[string{name}];
+ }
+ enc_stack.push_back(cur_enc);
+
+ if (section_is_array) {
+ cur_enc->set_type(JSONFormattable::FMT_ARRAY);
+ } else {
+ cur_enc->set_type(JSONFormattable::FMT_OBJ);
+ }
+
+ return false; /* continue processing */
+}
+
+bool JSONFormattable::handle_close_section() {
+ if (enc_stack.size() <= 1) {
+ return false;
+ }
+
+ enc_stack.pop_back();
+ cur_enc = enc_stack.back();
+ return false; /* continue processing */
+}
+