78 using Ptr = shared_ptr<JSONNode>;
81 cJSON *cjson =
nullptr;
84 bool is_owner =
false;
87 JSONNode::Ptr parent =
nullptr;
93 JSONNode(cJSON *cjson,
bool is_owner,
const JSONNode::Ptr &parent)
94 : cjson(cjson), is_owner(is_owner), parent(parent)
97 const int max_length = 64;
98 string msg = string(cJSON_GetErrorPtr()).substr(0, max_length);
99 JSON_ERROR(
"Invalid syntax right before: '%s'", msg.c_str());
100 throw std::runtime_error(msg);
116 *
this = std::move(node);
134 is_owner = node.is_owner;
135 parent = node.parent;
137 cjson = cJSON_Duplicate(node.cjson,
true);
156 swap(lhs.cjson, rhs.cjson);
157 swap(lhs.is_owner, rhs.is_owner);
158 swap(lhs.parent, rhs.parent);
164 loadFromFile(
const string &filepath)
166 std::ifstream file(filepath);
167 if (!file.is_open()) {
168 JSON_ERROR(
"Unable to open file %s", filepath.c_str());
172 std::stringstream stream{};
173 stream << file.rdbuf();
174 string content = stream.str();
180 saveToFile(
const string &filepath)
const
182 string contents = toString(
false);
183 std::ofstream file(filepath);
185 if (!file.is_open()) {
186 JSON_ERROR(
"Unable to open file %s", filepath.c_str());
195 operator[](
const string &key)
const
197 const char *name = key.c_str();
198 JSON_ASSERTW(isObject(),
"Trying to retrieve field '%s' from non-object %s", name, toString().c_str());
200 cJSON *value = cJSON_GetObjectItemCaseSensitive(cjson, name);
201 JSON_ASSERTW(value !=
nullptr,
"Unable to retrieve field '%s' from %s", name, toString().c_str());
203 return JSONNode{value,
false,
nullptr};
207 operator[](
int i)
const
209 JSON_ASSERTW(isArray(),
"Trying to retrieve index '%d' from non-array %s", i, toString().c_str());
211 cJSON *value = cJSON_GetArrayItem(cjson, i);
212 JSON_ASSERTW(value !=
nullptr,
"Unable to retrieve index %d from %s", i, toString().c_str());
214 return JSONNode{value,
false,
nullptr};
218 bool isObject()
const {
return isValid() && cJSON_IsObject(cjson); }
219 bool isArray()
const {
return isValid() && cJSON_IsArray(cjson); }
220 bool isString()
const {
return isValid() && cJSON_IsString(cjson); }
221 bool isNumber()
const {
return isValid() && cJSON_IsNumber(cjson); }
222 bool isInt()
const {
return isNumber() && cjson->valuedouble == cjson->valueint; }
223 bool isDouble()
const {
return isNumber(); }
224 bool isNull()
const {
return isValid() && cJSON_IsNull(cjson); }
225 bool isBool()
const {
return isValid() && cJSON_IsBool(cjson); }
226 bool isInvalid()
const {
return cjson ==
nullptr || cJSON_IsInvalid(cjson); }
227 bool isValid()
const {
return !isInvalid(); }
229 bool canBool()
const {
return isBool() || (isInt() && (cjson->valueint == 0 || cjson->valueint == 1)); }
232 map<string, JSONNode>
233 asObject(
const map<string, JSONNode> &otherwise = map<string, JSONNode>())
const
235 JSON_ASSERTW(isObject(),
"Invalid object: %s, defaults", toString().c_str());
237 map<string, JSONNode>
object{};
240 cJSON_ArrayForEach(item, cjson)
242 const char *key = item->string;
243 JSON_ASSERTF(key,
"Unexpected unnamed pair in json: %s", toString().c_str());
244 JSON_ASSERTW(
object.count(key) == 0,
"Duplicated key '%s'", key);
245 object.insert({key,
JSONNode{item,
false,
nullptr}});
254 asArray(
const vector<JSONNode> &otherwise = vector<JSONNode>())
const
256 JSON_ASSERTW(isArray(),
"Invalid array: %s, defaults", toString().c_str());
258 vector<JSONNode> array{};
261 cJSON_ArrayForEach(item, cjson)
263 array.push_back(
JSONNode{item,
false,
nullptr});
272 asString(
const string &otherwise =
"")
const
274 JSON_ASSERTW(isString(),
"Invalid string: %s, defaults %s", toString().c_str(), otherwise.c_str());
275 return isString() ? cjson->valuestring : otherwise;
279 asInt(
int otherwise = 0)
const
281 JSON_ASSERTW(isInt(),
"Invalid int: %s, defaults %d", toString().c_str(), otherwise);
282 return isInt() ? cjson->valueint : otherwise;
286 asDouble(
double otherwise = 0.0)
const
288 JSON_ASSERTW(isDouble(),
"Invalid double: %s, defaults %lf", toString().c_str(), otherwise);
289 return isDouble() ? cjson->valuedouble : otherwise;
293 asNull(
void *otherwise =
nullptr)
const
295 JSON_ASSERTW(isNull(),
"Invalid null: %s, defaults %p", toString().c_str(), otherwise);
296 return isNull() ? nullptr : otherwise;
300 asBool(
bool otherwise =
false)
const
302 JSON_ASSERTW(canBool(),
"Invalid bool: %s, defaults %d", toString().c_str(), otherwise);
303 return isBool() ? cJSON_IsTrue(cjson) : (canBool() ? cjson->valueint : otherwise);
307 hasKey(
const string &key)
const
309 return asObject().count(key) == 1;
313 toString(
bool show_field =
true)
const
315 char *cstr = cJSON_Print(cjson);
321 str +=
"\nFrom field named: " + getName();
330 return string(cjson->string ? cjson->string :
"");
351 enum class StackAlphabet
368 enum class InputAlphabet
378 using G = StackAlphabet;
379 using S = InputAlphabet;
382 std::stack<StackAlphabet> stack{{G::Base}};
383 State state{Q::Empty};
384 JSONNode::Ptr node =
nullptr;
386 using JSONValue = variant<string, const char *, int, double, bool>;
390 valueToString(
const JSONValue &value)
392 string s =
"JSONValue<invalid>()";
393 if (
const string *v = get_if<string>(&value)) {
394 s =
string{
"JSONValue<string>("} + *v +
")";
395 }
else if (
const char *
const *v = get_if<const char *>(&value)) {
396 s =
string{
"JSONValue<const char*>("} + *v +
")";
397 }
else if (
const int *v = get_if<int>(&value)) {
398 s =
string{
"JSONValue<int>("} + to_string(*v) +
")";
399 }
else if (
const double *v = get_if<double>(&value)) {
400 s =
string{
"JSONValue<double>("} + to_string(*v) +
")";
401 }
else if (
const bool *v = get_if<bool>(&value)) {
402 s =
string{
"JSONValue<bool>("} + to_string(*v) +
")";
404 JSON_ASSERTF(
false,
"Unsupported variant type");
405 s =
"[Invalid JSONValue]";
412 makeCJSONValue(
const JSONValue &value)
414 cJSON *ret =
nullptr;
415 if (holds_alternative<string>(value)) {
416 ret = cJSON_CreateString(get<string>(value).c_str());
417 }
else if (holds_alternative<const char *>(value)) {
418 ret = cJSON_CreateString(get<const char *>(value));
419 }
else if (holds_alternative<int>(value)) {
420 ret = cJSON_CreateNumber(get<int>(value));
421 }
else if (holds_alternative<double>(value)) {
422 ret = cJSON_CreateNumber(get<double>(value));
423 }
else if (holds_alternative<bool>(value)) {
424 ret = cJSON_CreateBool(get<bool>(value));
426 JSON_ASSERTF(
false,
"Unexpected value");
456 transition(InputAlphabet symbol,
const JSONValue &value)
458 StackAlphabet top = stack.top();
459 JSON_DEBUG(
"stacksz=%zu top=%d state=%d symbol=%d value=%s", stack.size(),
static_cast<int>(top),
460 static_cast<int>(state),
static_cast<int>(symbol), valueToString(value).c_str());
463 if (top == G::Base && state == Q::Empty && symbol == S::PushValue) {
465 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
466 cJSON *cjson_value = makeCJSONValue(value);
467 node = make_shared<JSONNode>(cjson_value,
true,
nullptr);
471 }
else if (top == G::Base && state == Q::Empty && symbol == S::StartObject) {
473 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
474 cJSON *cjson_object = cJSON_CreateObject();
475 node = make_shared<JSONNode>(cjson_object,
true,
nullptr);
477 state = Q::BuildObjectKey;
478 stack.push(G::Object);
480 }
else if (top == G::Base && state == Q::Empty && symbol == S::StartArray) {
482 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
483 cJSON *cjson_array = cJSON_CreateArray();
484 node = make_shared<JSONNode>(cjson_array,
true,
nullptr);
486 state = Q::BuildArray;
487 stack.push(G::Array);
489 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::PushValue) {
491 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
492 cJSON *cjson_value = makeCJSONValue(value);
493 cJSON_AddItemToArray(node->cjson, cjson_value);
496 state = Q::BuildArray;
498 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::StartArray) {
500 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
501 cJSON *cjson_array = cJSON_CreateArray();
502 cJSON_AddItemToArray(node->cjson, cjson_array);
503 node = make_shared<JSONNode>(cjson_array,
false, node);
505 state = Q::BuildArray;
506 stack.push(G::Array);
508 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::EndArray) {
511 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
512 state = m[stack.top()];
514 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
518 JSON_ASSERTF(stack.top() == G::Base,
"Unexpected non-root node without");
521 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::StartObject) {
523 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
524 cJSON *cjson_object = cJSON_CreateObject();
525 cJSON_AddItemToArray(node->cjson, cjson_object);
526 node = make_shared<JSONNode>(cjson_object,
false, node);
528 state = Q::BuildObjectKey;
529 stack.push(G::Object);
531 }
else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::PushKey) {
533 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
534 JSON_ASSERTF(holds_alternative<string>(value),
"Non-string key not allowed");
535 cJSON *cjson_null = cJSON_CreateNull();
536 cJSON_AddItemToObject(node->cjson, get<string>(value).c_str(), cjson_null);
537 node = make_shared<JSONNode>(cjson_null,
false, node);
539 state = Q::BuildObjectValue;
541 }
else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::EndObject) {
544 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
545 state = m[stack.top()];
547 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
551 JSON_ASSERTF(stack.top() == G::Base,
"Unexpected non-root node without")
554 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::PushValue) {
556 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
557 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
558 cJSON *cjson_value = makeCJSONValue(value);
559 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_value);
560 node->cjson = cjson_value;
563 state = Q::BuildObjectKey;
565 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartObject) {
567 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
568 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
569 cJSON *cjson_object = cJSON_CreateObject();
570 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_object);
571 node->cjson = cjson_object;
573 state = Q::BuildObjectKey;
574 stack.push(G::Object);
576 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartArray) {
578 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
579 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
580 cJSON *cjson_array = cJSON_CreateArray();
581 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_array);
582 node->cjson = cjson_array;
584 state = Q::BuildArray;
585 stack.push(G::Array);
589 JSON_ASSERTF(
false,
"Invalid construction transition: top=%d state=%d symbol=%d value=%s",
590 static_cast<int>(top),
static_cast<int>(state),
static_cast<int>(symbol),
591 valueToString(value).c_str());
592 node = make_shared<JSONNode>();
597 JSON_DEBUG(
"After transition: node=%p parent=%p\n", (
void *)node.get(),
598 (
void *)(node ? node->parent.get() :
nullptr));
610 bool is_string = holds_alternative<string>(value) || holds_alternative<const char *>(value);
612 transition(S::PushValue, value);
616 string as_string = holds_alternative<string>(value) ? get<string>(value) : get<const char *>(value);
617 if (as_string ==
"[") {
618 transition(S::StartArray, as_string);
619 }
else if (as_string ==
"]") {
620 transition(S::EndArray, as_string);
621 }
else if (as_string ==
"{") {
622 transition(S::StartObject, as_string);
623 }
else if (as_string ==
"}") {
624 transition(S::EndObject, as_string);
625 }
else if (state == Q::BuildObjectKey) {
626 transition(S::PushKey, as_string);
627 }
else if (state == Q::BuildObjectValue) {
628 transition(S::PushValue, as_string);
630 JSON_ASSERTF(
false,
"Invalid state=%d value=%s",
static_cast<int>(state), as_string.c_str());
639 JSON_ASSERTF(state == Q::Finish,
"Trying to getBuiltNode but the construction has not ended");