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());
119 *
this = std::move(node);
137 is_owner = node.is_owner;
138 parent = node.parent;
140 cjson = cJSON_Duplicate(node.cjson,
true);
159 swap(lhs.cjson, rhs.cjson);
160 swap(lhs.is_owner, rhs.is_owner);
161 swap(lhs.parent, rhs.parent);
167 loadFromFile(
const string &filepath)
169 std::ifstream file(filepath);
170 if (!file.is_open()) {
171 JSON_ERROR(
"Unable to open file %s", filepath.c_str());
175 std::stringstream stream{};
176 stream << file.rdbuf();
177 string content = stream.str();
183 saveToFile(
const string &filepath)
const
185 string contents = toString(
false);
186 std::ofstream file(filepath);
188 if (!file.is_open()) {
189 JSON_ERROR(
"Unable to open file %s", filepath.c_str());
198 operator[](
const string &key)
const
200 const char *name = key.c_str();
201 JSON_ASSERTW(isObject(),
"Trying to retrieve field '%s' from non-object %s", name, toString().c_str());
203 cJSON *value = cJSON_GetObjectItemCaseSensitive(cjson, name);
204 JSON_ASSERTW(value !=
nullptr,
"Unable to retrieve field '%s' from %s", name, toString().c_str());
206 return JSONNode{value,
false,
nullptr};
210 operator[](
int i)
const
212 JSON_ASSERTW(isArray(),
"Trying to retrieve index '%d' from non-array %s", i, toString().c_str());
214 cJSON *value = cJSON_GetArrayItem(cjson, i);
215 JSON_ASSERTW(value !=
nullptr,
"Unable to retrieve index %d from %s", i, toString().c_str());
217 return JSONNode{value,
false,
nullptr};
221 bool isObject()
const {
return cJSON_IsObject(cjson); }
222 bool isArray()
const {
return cJSON_IsArray(cjson); }
223 bool isString()
const {
return cJSON_IsString(cjson); }
224 bool isNumber()
const {
return cJSON_IsNumber(cjson); }
225 bool isInt()
const {
return isNumber() && cjson->valuedouble == cjson->valueint; }
226 bool isDouble()
const {
return isNumber(); }
227 bool isNull()
const {
return cJSON_IsNull(cjson); }
228 bool isBool()
const {
return cJSON_IsBool(cjson); }
229 bool isInvalid()
const {
return cjson ==
nullptr || cJSON_IsInvalid(cjson); }
230 bool isValid()
const {
return !isInvalid(); }
232 bool canBool()
const {
return isBool() || (isInt() && (cjson->valueint == 0 || cjson->valueint == 1)); }
235 map<string, JSONNode>
236 asObject(
const map<string, JSONNode> &otherwise = map<string, JSONNode>())
const
238 JSON_ASSERTW(isObject(),
"Invalid object: %s, defaults", toString().c_str());
240 map<string, JSONNode>
object{};
243 cJSON_ArrayForEach(item, cjson)
245 const char *key = item->string;
246 JSON_ASSERTF(key,
"Unexpected unnamed pair in json: %s", toString().c_str());
247 JSON_ASSERTW(
object.count(key) == 0,
"Duplicated key '%s'", key);
248 object.insert({key,
JSONNode{item,
false,
nullptr}});
257 asArray(
const vector<JSONNode> &otherwise = vector<JSONNode>())
const
259 JSON_ASSERTW(isArray(),
"Invalid array: %s, defaults", toString().c_str());
261 vector<JSONNode> array{};
264 cJSON_ArrayForEach(item, cjson)
266 array.push_back(
JSONNode{item,
false,
nullptr});
275 asString(
const string &otherwise =
"")
const
277 JSON_ASSERTW(isString(),
"Invalid string: %s, defaults %s", toString().c_str(), otherwise.c_str());
278 return isString() ? cjson->valuestring : otherwise;
282 asInt(
int otherwise = 0)
const
284 JSON_ASSERTW(isInt(),
"Invalid int: %s, defaults %d", toString().c_str(), otherwise);
285 return isInt() ? cjson->valueint : otherwise;
289 asDouble(
double otherwise = 0.0)
const
291 JSON_ASSERTW(isDouble(),
"Invalid double: %s, defaults %lf", toString().c_str(), otherwise);
292 return isDouble() ? cjson->valuedouble : otherwise;
296 asNull(
void *otherwise =
nullptr)
const
298 JSON_ASSERTW(isNull(),
"Invalid null: %s, defaults %p", toString().c_str(), otherwise);
299 return isNull() ? nullptr : otherwise;
303 asBool(
bool otherwise =
false)
const
305 JSON_ASSERTW(canBool(),
"Invalid bool: %s, defaults %d", toString().c_str(), otherwise);
306 return isBool() ? cJSON_IsTrue(cjson) : (canBool() ? cjson->valueint : otherwise);
310 hasKey(
const string &key)
const
312 return asObject().count(key) == 1;
316 toString(
bool show_field =
true)
const
318 char *cstr = cJSON_Print(cjson);
324 str +=
"\nFrom field named: " + getName();
333 return string(cjson->string ? cjson->string :
"");
354 enum class StackAlphabet
371 enum class InputAlphabet
381 using G = StackAlphabet;
382 using S = InputAlphabet;
385 std::stack<StackAlphabet> stack{{G::Base}};
386 State state{Q::Empty};
387 JSONNode::Ptr node =
nullptr;
389 using JSONValue = variant<string, const char *, int, double, bool>;
393 valueToString(
const JSONValue &value)
395 string s =
"JSONValue<invalid>()";
396 if (
const string *v = get_if<string>(&value)) {
397 s =
string{
"JSONValue<string>("} + *v +
")";
398 }
else if (
const char *
const *v = get_if<const char *>(&value)) {
399 s =
string{
"JSONValue<const char*>("} + *v +
")";
400 }
else if (
const int *v = get_if<int>(&value)) {
401 s =
string{
"JSONValue<int>("} + to_string(*v) +
")";
402 }
else if (
const double *v = get_if<double>(&value)) {
403 s =
string{
"JSONValue<double>("} + to_string(*v) +
")";
404 }
else if (
const bool *v = get_if<bool>(&value)) {
405 s =
string{
"JSONValue<bool>("} + to_string(*v) +
")";
407 JSON_ASSERTF(
false,
"Unsupported variant type");
408 s =
"[Invalid JSONValue]";
415 makeCJSONValue(
const JSONValue &value)
417 cJSON *ret =
nullptr;
418 if (holds_alternative<string>(value)) {
419 ret = cJSON_CreateString(get<string>(value).c_str());
420 }
else if (holds_alternative<const char *>(value)) {
421 ret = cJSON_CreateString(get<const char *>(value));
422 }
else if (holds_alternative<int>(value)) {
423 ret = cJSON_CreateNumber(get<int>(value));
424 }
else if (holds_alternative<double>(value)) {
425 ret = cJSON_CreateNumber(get<double>(value));
426 }
else if (holds_alternative<bool>(value)) {
427 ret = cJSON_CreateBool(get<bool>(value));
429 JSON_ASSERTF(
false,
"Unexpected value");
459 transition(InputAlphabet symbol,
const JSONValue &value)
461 StackAlphabet top = stack.top();
462 JSON_DEBUG(
"stacksz=%zu top=%d state=%d symbol=%d value=%s", stack.size(),
static_cast<int>(top),
463 static_cast<int>(state),
static_cast<int>(symbol), valueToString(value).c_str());
466 if (top == G::Base && state == Q::Empty && symbol == S::PushValue) {
468 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
469 cJSON *cjson_value = makeCJSONValue(value);
470 node = make_shared<JSONNode>(cjson_value,
true,
nullptr);
474 }
else if (top == G::Base && state == Q::Empty && symbol == S::StartObject) {
476 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
477 cJSON *cjson_object = cJSON_CreateObject();
478 node = make_shared<JSONNode>(cjson_object,
true,
nullptr);
480 state = Q::BuildObjectKey;
481 stack.push(G::Object);
483 }
else if (top == G::Base && state == Q::Empty && symbol == S::StartArray) {
485 JSON_ASSERTF(node ==
nullptr,
"Failed with %s", valueToString(value).c_str());
486 cJSON *cjson_array = cJSON_CreateArray();
487 node = make_shared<JSONNode>(cjson_array,
true,
nullptr);
489 state = Q::BuildArray;
490 stack.push(G::Array);
492 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::PushValue) {
494 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
495 cJSON *cjson_value = makeCJSONValue(value);
496 cJSON_AddItemToArray(node->cjson, cjson_value);
499 state = Q::BuildArray;
501 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::StartArray) {
503 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
504 cJSON *cjson_array = cJSON_CreateArray();
505 cJSON_AddItemToArray(node->cjson, cjson_array);
506 node = make_shared<JSONNode>(cjson_array,
false, node);
508 state = Q::BuildArray;
509 stack.push(G::Array);
511 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::EndArray) {
514 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
515 state = m[stack.top()];
517 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
521 JSON_ASSERTF(stack.top() == G::Base,
"Unexpected non-root node without");
524 }
else if (top == G::Array && state == Q::BuildArray && symbol == S::StartObject) {
526 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
527 cJSON *cjson_object = cJSON_CreateObject();
528 cJSON_AddItemToArray(node->cjson, cjson_object);
529 node = make_shared<JSONNode>(cjson_object,
false, node);
531 state = Q::BuildObjectKey;
532 stack.push(G::Object);
534 }
else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::PushKey) {
536 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
537 JSON_ASSERTF(holds_alternative<string>(value),
"Non-string key not allowed");
538 cJSON *cjson_null = cJSON_CreateNull();
539 cJSON_AddItemToObject(node->cjson, get<string>(value).c_str(), cjson_null);
540 node = make_shared<JSONNode>(cjson_null,
false, node);
542 state = Q::BuildObjectValue;
544 }
else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::EndObject) {
547 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
548 state = m[stack.top()];
550 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
554 JSON_ASSERTF(stack.top() == G::Base,
"Unexpected non-root node without")
557 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::PushValue) {
559 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
560 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
561 cJSON *cjson_value = makeCJSONValue(value);
562 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_value);
563 node->cjson = cjson_value;
566 state = Q::BuildObjectKey;
568 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartObject) {
570 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
571 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
572 cJSON *cjson_object = cJSON_CreateObject();
573 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_object);
574 node->cjson = cjson_object;
576 state = Q::BuildObjectKey;
577 stack.push(G::Object);
579 }
else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartArray) {
581 JSON_ASSERTF(node->cjson !=
nullptr,
"Failed with %s", valueToString(value).c_str());
582 JSON_ASSERTF(cJSON_IsNull(node->cjson),
"Partial pair value is not null");
583 cJSON *cjson_array = cJSON_CreateArray();
584 cJSON_ReplaceItemInObject(node->parent->cjson, node->cjson->string, cjson_array);
585 node->cjson = cjson_array;
587 state = Q::BuildArray;
588 stack.push(G::Array);
592 JSON_ASSERTF(
false,
"Invalid construction transition: top=%d state=%d symbol=%d value=%s",
593 static_cast<int>(top),
static_cast<int>(state),
static_cast<int>(symbol),
594 valueToString(value).c_str());
595 node = make_shared<JSONNode>();
600 JSON_DEBUG(
"After transition: node=%p parent=%p\n", (
void *)node.get(),
601 (
void *)(node ? node->parent.get() :
nullptr));
613 bool is_string = holds_alternative<string>(value) || holds_alternative<const char *>(value);
615 transition(S::PushValue, value);
619 string as_string = holds_alternative<string>(value) ? get<string>(value) : get<const char *>(value);
620 if (as_string ==
"[") {
621 transition(S::StartArray, as_string);
622 }
else if (as_string ==
"]") {
623 transition(S::EndArray, as_string);
624 }
else if (as_string ==
"{") {
625 transition(S::StartObject, as_string);
626 }
else if (as_string ==
"}") {
627 transition(S::EndObject, as_string);
628 }
else if (state == Q::BuildObjectKey) {
629 transition(S::PushKey, as_string);
630 }
else if (state == Q::BuildObjectValue) {
631 transition(S::PushValue, as_string);
633 JSON_ASSERTF(
false,
"Invalid state=%d value=%s",
static_cast<int>(state), as_string.c_str());
642 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:352
JSONBuilder & operator<<(const JSONValue &value)
Receives "[", "]", "{", "}", or any of string, const char*, double, int, bool as inputs.
Definition: u_json.hpp:611
JSONNode::Ptr getBuiltNode()
Gets the built JSONNode or crash if the construction has not finished.
Definition: u_json.hpp:640
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:48
Very simple file opening functions.
static const cJSON * get(const cJSON *json, const char *f)
Less typing.
Definition: u_json.c:36