14#include "cjson/cJSON.h"
27#define JSON_TRACE(...) U_LOG_IFL_T(debug_get_log_option_json_log(), __VA_ARGS__)
28#define JSON_DEBUG(...) U_LOG_IFL_D(debug_get_log_option_json_log(), __VA_ARGS__)
29#define JSON_INFO(...) U_LOG_IFL_I(debug_get_log_option_json_log(), __VA_ARGS__)
30#define JSON_WARN(...) U_LOG_IFL_W(debug_get_log_option_json_log(), __VA_ARGS__)
31#define JSON_ERROR(...) U_LOG_IFL_E(debug_get_log_option_json_log(), __VA_ARGS__)
32#define JSON_ASSERT(fatal, predicate, ...) \
36 JSON_ERROR(__VA_ARGS__); \
38 assert(false && "Assertion failed: " #predicate); \
45#define JSON_ASSERTF(predicate, ...) JSON_ASSERT(true, predicate, __VA_ARGS__)
46#define JSON_ASSERTF_(predicate) JSON_ASSERT(true, predicate, "Assertion failed " #predicate)
49#define JSON_ASSERTW(predicate, ...) JSON_ASSERT(false, predicate, __VA_ARGS__)
51namespace xrt::auxiliary::util::json {
55using std::holds_alternative;
56using std::make_shared;
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)
106 cjson = cJSON_Parse(content.c_str());
107 if (cjson ==
nullptr) {
108 const int max_length = 64;
109 string msg = string(cJSON_GetErrorPtr()).substr(0, max_length);
110 JSON_ERROR(
"Invalid syntax right before: '%s'", msg.c_str());
121 is_owner = node.is_owner;
122 parent = node.parent;
124 cjson = cJSON_Duplicate(node.cjson,
true);
151 swap(lhs.cjson, rhs.cjson);
152 swap(lhs.is_owner, rhs.is_owner);
153 swap(lhs.parent, rhs.parent);
159 loadFromFile(
const string &filepath)
161 std::ifstream file(filepath);
162 if (!file.is_open()) {
163 JSON_ERROR(
"Unable to open file %s", filepath.c_str());
167 std::stringstream stream{};
168 stream << file.rdbuf();
169 string content = stream.str();
175 saveToFile(
const string &filepath)
const
177 string contents = toString(
false);
178 std::ofstream file(filepath);
180 if (!file.is_open()) {
181 JSON_ERROR(
"Unable to open file %s", filepath.c_str());
190 operator[](
const string &key)
const
192 const char *name = key.c_str();
193 JSON_ASSERTW(isObject(),
"Trying to retrieve field '%s' from non-object %s", name, toString().c_str());
195 cJSON *value = cJSON_GetObjectItemCaseSensitive(cjson, name);
196 JSON_ASSERTW(value !=
nullptr,
"Unable to retrieve field '%s' from %s", name, toString().c_str());
198 return JSONNode{value,
false,
nullptr};
202 operator[](
int i)
const
204 JSON_ASSERTW(isArray(),
"Trying to retrieve index '%d' from non-array %s", i, toString().c_str());
206 cJSON *value = cJSON_GetArrayItem(cjson, i);
207 JSON_ASSERTW(value !=
nullptr,
"Unable to retrieve index %d from %s", i, toString().c_str());
209 return JSONNode{value,
false,
nullptr};
213 bool isObject()
const {
return cJSON_IsObject(cjson); }
214 bool isArray()
const {
return cJSON_IsArray(cjson); }
215 bool isString()
const {
return cJSON_IsString(cjson); }
216 bool isNumber()
const {
return cJSON_IsNumber(cjson); }
217 bool isInt()
const {
return isNumber() && cjson->valuedouble == cjson->valueint; }
218 bool isDouble()
const {
return isNumber(); }
219 bool isNull()
const {
return cJSON_IsNull(cjson); }
220 bool isBool()
const {
return cJSON_IsBool(cjson); }
221 bool isInvalid()
const {
return cjson ==
nullptr || cJSON_IsInvalid(cjson); }
222 bool isValid()
const {
return !isInvalid(); }
224 bool canBool()
const {
return isBool() || (isInt() && (cjson->valueint == 0 || cjson->valueint == 1)); }
227 map<string, JSONNode>
228 asObject(
const map<string, JSONNode> &otherwise = map<string, JSONNode>())
const
230 JSON_ASSERTW(isObject(),
"Invalid object: %s, defaults", toString().c_str());
232 map<string, JSONNode>
object{};
235 cJSON_ArrayForEach(item, cjson)
237 const char *key = item->string;
238 JSON_ASSERTF(key,
"Unexpected unnamed pair in json: %s", toString().c_str());
239 JSON_ASSERTW(
object.count(key) == 0,
"Duplicated key '%s'", key);
240 object.insert({key,
JSONNode{item,
false,
nullptr}});
249 asArray(
const vector<JSONNode> &otherwise = vector<JSONNode>())
const
251 JSON_ASSERTW(isArray(),
"Invalid array: %s, defaults", toString().c_str());
253 vector<JSONNode> array{};
256 cJSON_ArrayForEach(item, cjson)
258 array.push_back(
JSONNode{item,
false,
nullptr});
267 asString(
const string &otherwise =
"")
const
269 JSON_ASSERTW(isString(),
"Invalid string: %s, defaults %s", toString().c_str(), otherwise.c_str());
270 return isString() ? cjson->valuestring : otherwise;
274 asInt(
int otherwise = 0)
const
276 JSON_ASSERTW(isInt(),
"Invalid int: %s, defaults %d", toString().c_str(), otherwise);
277 return isInt() ? cjson->valueint : otherwise;
281 asDouble(
double otherwise = 0.0)
const
283 JSON_ASSERTW(isDouble(),
"Invalid double: %s, defaults %lf", toString().c_str(), otherwise);
284 return isDouble() ? cjson->valuedouble : otherwise;
288 asNull(
void *otherwise =
nullptr)
const
290 JSON_ASSERTW(isNull(),
"Invalid null: %s, defaults %p", toString().c_str(), otherwise);
291 return isNull() ? nullptr : otherwise;
295 asBool(
bool otherwise =
false)
const
297 JSON_ASSERTW(canBool(),
"Invalid bool: %s, defaults %d", toString().c_str(), otherwise);
298 return isBool() ? cJSON_IsTrue(cjson) : (canBool() ? cjson->valueint : otherwise);
302 hasKey(
const string &key)
const
304 return asObject().count(key) == 1;
308 toString(
bool show_field =
true)
const
310 char *cstr = cJSON_Print(cjson);
316 str +=
"\nFrom field named: " + getName();
325 return string(cjson->string ? cjson->string :
"");
346 enum class StackAlphabet
363 enum class InputAlphabet
373 using G = StackAlphabet;
374 using S = InputAlphabet;
377 std::stack<StackAlphabet> stack{{G::Base}};
378 State state{Q::Empty};
379 JSONNode::Ptr node =
nullptr;
381 using JSONValue = variant<string, const char *, int, double, bool>;
385 valueToString(
const JSONValue &value)
387 string s =
"JSONValue<invalid>()";
388 if (
const string *v = get_if<string>(&value)) {
389 s =
string{
"JSONValue<string>("} + *v +
")";
390 }
else if (
const char *
const *v = get_if<const char *>(&value)) {
391 s =
string{
"JSONValue<const char*>("} + *v +
")";
392 }
else if (
const int *v = get_if<int>(&value)) {
393 s =
string{
"JSONValue<int>("} + to_string(*v) +
")";
394 }
else if (
const double *v = get_if<double>(&value)) {
395 s =
string{
"JSONValue<double>("} + to_string(*v) +
")";
396 }
else if (
const bool *v = get_if<bool>(&value)) {
397 s =
string{
"JSONValue<bool>("} + to_string(*v) +
")";
399 JSON_ASSERTF(
false,
"Unsupported variant type");
400 s =
"[Invalid JSONValue]";
407 makeCJSONValue(
const JSONValue &value)
409 cJSON *ret =
nullptr;
410 if (holds_alternative<string>(value)) {
411 ret = cJSON_CreateString(get<string>(value).c_str());
412 }
else if (holds_alternative<const char *>(value)) {
413 ret = cJSON_CreateString(get<const char *>(value));
414 }
else if (holds_alternative<int>(value)) {
415 ret = cJSON_CreateNumber(get<int>(value));
416 }
else if (holds_alternative<double>(value)) {
417 ret = cJSON_CreateNumber(get<double>(value));
418 }
else if (holds_alternative<bool>(value)) {
419 ret = cJSON_CreateBool(get<bool>(value));
421 JSON_ASSERTF(
false,
"Unexpected value");
451 transition(InputAlphabet symbol,
const JSONValue &value)
453 StackAlphabet top = stack.top();
454 JSON_DEBUG(
"stacksz=%zu top=%d state=%d symbol=%d value=%s", stack.size(),
static_cast<int>(top),
455 static_cast<int>(state),
static_cast<int>(symbol), valueToString(value).c_str());
458 if (top == G::Base && state == Q::Empty && symbol == S::PushValue) {
460 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
461 cJSON *cjson_value = makeCJSONValue(value);
462 node = make_shared<JSONNode>(cjson_value,
true,
nullptr);
466 }
else if (top == G::Base && state == Q::Empty && symbol == S::StartObject) {
468 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
469 cJSON *cjson_object = cJSON_CreateObject();
470 node = make_shared<JSONNode>(cjson_object,
true,
nullptr);
472 state = Q::BuildObjectKey;
473 stack.push(G::Object);
475 }
else if (top == G::Base && state == Q::Empty && symbol == S::StartArray) {
477 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
478 cJSON *cjson_array = cJSON_CreateArray();
479 node = make_shared<JSONNode>(cjson_array,
true,
nullptr);
481 state = Q::BuildArray;
482 stack.push(G::Array);
484 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::PushValue) {
486 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
487 cJSON *cjson_value = makeCJSONValue(value);
488 cJSON_AddItemToArray(node->cjson, cjson_value);
491 state = Q::BuildArray;
493 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::StartArray) {
495 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
496 cJSON *cjson_array = cJSON_CreateArray();
497 cJSON_AddItemToArray(node->cjson, cjson_array);
498 node = make_shared<JSONNode>(cjson_array,
false, node);
500 state = Q::BuildArray;
501 stack.push(G::Array);
503 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::EndArray) {
506 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
507 state = m[stack.top()];
509 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
513 JSON_ASSERTF(stack.top() == G::Base,
"Unexpected non-root node without");
516 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::StartObject) {
518 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
519 cJSON *cjson_object = cJSON_CreateObject();
520 cJSON_AddItemToArray(node->cjson, cjson_object);
521 node = make_shared<JSONNode>(cjson_object,
false, node);
523 state = Q::BuildObjectKey;
524 stack.push(G::Object);
526 }
else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::PushKey) {
528 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
529 JSON_ASSERTF(holds_alternative<string>(value),
"Non-string key not allowed");
530 cJSON *cjson_null = cJSON_CreateNull();
531 cJSON_AddItemToObject(node->cjson, get<string>(value).c_str(), cjson_null);
532 node = make_shared<JSONNode>(cjson_null,
false, node);
534 state = Q::BuildObjectValue;
536 }
else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::EndObject) {
539 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
540 state = m[stack.top()];
542 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
546 JSON_ASSERTF(stack.top() == G::Base,
"Unexpected non-root node without")
549 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::PushValue) {
551 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
552 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
553 cJSON *cjson_value = makeCJSONValue(value);
554 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_value);
555 node->cjson = cjson_value;
558 state = Q::BuildObjectKey;
560 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartObject) {
562 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
563 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
564 cJSON *cjson_object = cJSON_CreateObject();
565 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_object);
566 node->cjson = cjson_object;
568 state = Q::BuildObjectKey;
569 stack.push(G::Object);
571 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartArray) {
573 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
574 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
575 cJSON *cjson_array = cJSON_CreateArray();
576 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_array);
577 node->cjson = cjson_array;
579 state = Q::BuildArray;
580 stack.push(G::Array);
584 JSON_ASSERTF(
false,
"Invalid construction transition: top=%d state=%d symbol=%d value=%s",
585 static_cast<int>(top),
static_cast<int>(state),
static_cast<int>(symbol),
586 valueToString(value).c_str());
587 node = make_shared<JSONNode>();
592 JSON_DEBUG(
"After transition: node=%p parent=%p\n", (
void *)node.get(),
593 (
void *)(node ? node->parent.get() :
nullptr));
605 bool is_string = holds_alternative<string>(value) || holds_alternative<const char *>(value);
607 transition(S::PushValue, value);
611 string as_string = holds_alternative<string>(value) ? get<string>(value) : get<const char *>(value);
612 if (as_string ==
"[") {
613 transition(S::StartArray, as_string);
614 }
else if (as_string ==
"]") {
615 transition(S::EndArray, as_string);
616 }
else if (as_string ==
"{") {
617 transition(S::StartObject, as_string);
618 }
else if (as_string ==
"}") {
619 transition(S::EndObject, as_string);
620 }
else if (state == Q::BuildObjectKey) {
621 transition(S::PushKey, as_string);
622 }
else if (state == Q::BuildObjectValue) {
623 transition(S::PushValue, as_string);
625 JSON_ASSERTF(
false,
"Invalid state=%d value=%s",
static_cast<int>(state), as_string.c_str());
634 JSON_ASSERTF(state == Q::Finish,
"Trying to getBuiltNode but the construction has not ended");
Helper class for building cJSON trees through operator<<
Definition: u_json.hpp:344
JSONBuilder & operator<<(const JSONValue &value)
Receives "[", "]", "{", "}", or any of string, const char*, double, int, bool as inputs.
Definition: u_json.hpp:603
JSONNode::Ptr getBuiltNode()
Gets the built JSONNode or crash if the construction has not finished.
Definition: u_json.hpp:632
A JSONNode wraps a cJSON object and presents useful functions for accessing the different properties ...
Definition: u_json.hpp:75
JSONNode(cJSON *cjson)
Wrap cJSON object for easy manipulation, does not take ownership.
Definition: u_json.hpp:98
JSONNode()
Makes a null object; isInvalid() on it returns true.
Definition: u_json.hpp:101
JSONNode(cJSON *cjson, bool is_owner, const JSONNode::Ptr &parent)
This is public so that make_shared works; do not use outside of this file.
Definition: u_json.hpp:93
JSONNode(const string &content)
Receives a json string and constructs a wrapped cJSON object out of it.
Definition: u_json.hpp:104
@ U_LOGGING_WARN
Warning messages: indicating a potential problem.
Definition: u_logging.h:47
Very simple file opening functions.
static const cJSON * get(const cJSON *json, const char *f)
Less typing.
Definition: u_json.c:36