Monado OpenXR Runtime
u_json.hpp
Go to the documentation of this file.
1// Copyright 2021, Collabora, Ltd.
2// SPDX-License-Identifier: BSL-1.0
3/*!
4 * @file
5 * @brief C++ wrapper for cJSON.
6 * @author Mateo de Mayo <mateo.demayo@collabora.com>
7 * @ingroup aux_util
8 */
9
10#pragma once
11
12#include "util/u_file.h"
13#include "util/u_debug.h"
14#include "cjson/cJSON.h"
15
16#include <cassert>
17#include <stack>
18#include <vector>
19#include <map>
20#include <memory>
21#include <variant>
22#include <fstream>
23#include <sstream>
24
25DEBUG_GET_ONCE_LOG_OPTION(json_log, "JSON_LOG", U_LOGGING_WARN)
26
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, ...) \
33 do { \
34 bool p = predicate; \
35 if (!p) { \
36 JSON_ERROR(__VA_ARGS__); \
37 if (fatal) { \
38 assert(false && "Assertion failed: " #predicate); \
39 exit(EXIT_FAILURE); \
40 } \
41 } \
42 } while (false);
43
44// Fatal assertion
45#define JSON_ASSERTF(predicate, ...) JSON_ASSERT(true, predicate, __VA_ARGS__)
46#define JSON_ASSERTF_(predicate) JSON_ASSERT(true, predicate, "Assertion failed " #predicate)
47
48// Warn-only assertion
49#define JSON_ASSERTW(predicate, ...) JSON_ASSERT(false, predicate, __VA_ARGS__)
50
51namespace xrt::auxiliary::util::json {
52
53using std::get;
54using std::get_if;
55using std::holds_alternative;
56using std::make_shared;
57using std::map;
58using std::shared_ptr;
59using std::string;
60using std::to_string;
61using std::variant;
62using std::vector;
63
64class JSONBuilder;
65
66/*!
67 * @brief A JSONNode wraps a cJSON object and presents useful functions for
68 * accessing the different properties of the json structure like `operator[]`,
69 * `isType()` and `asType()` methods.
70 *
71 * The main ways a user can build a JSONNode is from a json string, from a
72 * json file with `loadFromFile` or with the @ref JSONBuilder.
73 */
75{
76private:
77 friend class JSONBuilder;
78 using Ptr = shared_ptr<JSONNode>;
79
80 //! Wrapped cJSON object
81 cJSON *cjson = nullptr;
82
83 //! Whether this node is responsible for deleting the cjson object
84 bool is_owner = false;
85
86 //! Parent of this node, used only by @ref JSONBuilder.
87 JSONNode::Ptr parent = nullptr;
88
89public:
90 // Class resource management
91
92 //! This is public so that make_shared works; do not use outside of this file.
93 JSONNode(cJSON *cjson, bool is_owner, const JSONNode::Ptr &parent)
94 : cjson(cjson), is_owner(is_owner), parent(parent)
95 {}
96
97 //! Wrap cJSON object for easy manipulation, does not take ownership
98 JSONNode(cJSON *cjson) : JSONNode(cjson, false, nullptr) {}
99
100 //! Makes a null object; `isInvalid()` on it returns true.
102
103 //! Receives a json string and constructs a wrapped cJSON object out of it.
104 JSONNode(const string &content)
105 {
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());
111 return;
112 }
113 is_owner = true;
114 parent = nullptr;
115 }
116
117 JSONNode(JSONNode &&) = default;
118
119 JSONNode(const JSONNode &node)
120 {
121 is_owner = node.is_owner;
122 parent = node.parent;
123 if (node.is_owner) {
124 cjson = cJSON_Duplicate(node.cjson, true); // Deep copy
125 } else {
126 cjson = node.cjson; // Shallow copy
127 }
128 };
129
130 JSONNode &
131 operator=(JSONNode &&) = default;
132
133 JSONNode &
134 operator=(JSONNode rhs)
135 {
136 swap(*this, rhs);
137 return *this;
138 };
139
140 ~JSONNode()
141 {
142 if (is_owner) {
143 cJSON_Delete(cjson);
144 }
145 }
146
147 friend void
148 swap(JSONNode &lhs, JSONNode &rhs) noexcept
149 {
150 using std::swap;
151 swap(lhs.cjson, rhs.cjson);
152 swap(lhs.is_owner, rhs.is_owner);
153 swap(lhs.parent, rhs.parent);
154 }
155
156 // Methods for explicit usage by the user of this class
157
158 static JSONNode
159 loadFromFile(const string &filepath)
160 {
161 std::ifstream file(filepath);
162 if (!file.is_open()) {
163 JSON_ERROR("Unable to open file %s", filepath.c_str());
164 return JSONNode{};
165 }
166
167 std::stringstream stream{};
168 stream << file.rdbuf();
169 string content = stream.str();
170
171 return JSONNode{content};
172 }
173
174 bool
175 saveToFile(const string &filepath) const
176 {
177 string contents = toString(false);
178 std::ofstream file(filepath);
179
180 if (!file.is_open()) {
181 JSON_ERROR("Unable to open file %s", filepath.c_str());
182 return false;
183 }
184
185 file << contents;
186 return true;
187 }
188
190 operator[](const string &key) const
191 {
192 const char *name = key.c_str();
193 JSON_ASSERTW(isObject(), "Trying to retrieve field '%s' from non-object %s", name, toString().c_str());
194
195 cJSON *value = cJSON_GetObjectItemCaseSensitive(cjson, name);
196 JSON_ASSERTW(value != nullptr, "Unable to retrieve field '%s' from %s", name, toString().c_str());
197
198 return JSONNode{value, false, nullptr};
199 }
200
202 operator[](int i) const
203 {
204 JSON_ASSERTW(isArray(), "Trying to retrieve index '%d' from non-array %s", i, toString().c_str());
205
206 cJSON *value = cJSON_GetArrayItem(cjson, i);
207 JSON_ASSERTW(value != nullptr, "Unable to retrieve index %d from %s", i, toString().c_str());
208
209 return JSONNode{value, false, nullptr};
210 }
211
212 // clang-format off
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(); }
223
224 bool canBool() const { return isBool() || (isInt() && (cjson->valueint == 0 || cjson->valueint == 1)); }
225 // clang-format on
226
227 map<string, JSONNode>
228 asObject(const map<string, JSONNode> &otherwise = map<string, JSONNode>()) const
229 {
230 JSON_ASSERTW(isObject(), "Invalid object: %s, defaults", toString().c_str());
231 if (isObject()) {
232 map<string, JSONNode> object{};
233
234 cJSON *item = NULL;
235 cJSON_ArrayForEach(item, cjson)
236 {
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}});
241 }
242
243 return object;
244 }
245 return otherwise;
246 }
247
248 vector<JSONNode>
249 asArray(const vector<JSONNode> &otherwise = vector<JSONNode>()) const
250 {
251 JSON_ASSERTW(isArray(), "Invalid array: %s, defaults", toString().c_str());
252 if (isArray()) {
253 vector<JSONNode> array{};
254
255 cJSON *item = NULL;
256 cJSON_ArrayForEach(item, cjson)
257 {
258 array.push_back(JSONNode{item, false, nullptr});
259 }
260
261 return array;
262 }
263 return otherwise;
264 }
265
266 string
267 asString(const string &otherwise = "") const
268 {
269 JSON_ASSERTW(isString(), "Invalid string: %s, defaults %s", toString().c_str(), otherwise.c_str());
270 return isString() ? cjson->valuestring : otherwise;
271 }
272
273 int
274 asInt(int otherwise = 0) const
275 {
276 JSON_ASSERTW(isInt(), "Invalid int: %s, defaults %d", toString().c_str(), otherwise);
277 return isInt() ? cjson->valueint : otherwise;
278 }
279
280 double
281 asDouble(double otherwise = 0.0) const
282 {
283 JSON_ASSERTW(isDouble(), "Invalid double: %s, defaults %lf", toString().c_str(), otherwise);
284 return isDouble() ? cjson->valuedouble : otherwise;
285 }
286
287 void *
288 asNull(void *otherwise = nullptr) const
289 {
290 JSON_ASSERTW(isNull(), "Invalid null: %s, defaults %p", toString().c_str(), otherwise);
291 return isNull() ? nullptr : otherwise;
292 }
293
294 bool
295 asBool(bool otherwise = false) const
296 {
297 JSON_ASSERTW(canBool(), "Invalid bool: %s, defaults %d", toString().c_str(), otherwise);
298 return isBool() ? cJSON_IsTrue(cjson) : (canBool() ? cjson->valueint : otherwise);
299 }
300
301 bool
302 hasKey(const string &key) const
303 {
304 return asObject().count(key) == 1;
305 }
306
307 string
308 toString(bool show_field = true) const
309 {
310 char *cstr = cJSON_Print(cjson);
311 string str{cstr};
312 free(cstr);
313
314 // Show the named field this comes from if any
315 if (show_field) {
316 str += "\nFrom field named: " + getName();
317 }
318
319 return str;
320 }
321
322 string
323 getName() const
324 {
325 return string(cjson->string ? cjson->string : "");
326 }
327
328 cJSON *
329 getCJSON()
330 {
331 return cjson;
332 }
333};
334
335
336/*!
337 * @brief Helper class for building cJSON trees through `operator<<`
338 *
339 * JSONBuild is implemented with a pushdown automata to keep track of the JSON
340 * construction state.
341 *
342 */
344{
345private:
346 enum class StackAlphabet
347 {
348 Base, // Unique occurrence as the base of the stack
349 Array,
350 Object
351 };
352
353 enum class State
354 {
355 Empty,
356 BuildArray,
357 BuildObjectKey,
358 BuildObjectValue,
359 Finish,
360 Invalid
361 };
362
363 enum class InputAlphabet
364 {
365 StartArray,
366 EndArray,
367 StartObject,
368 EndObject,
369 PushKey,
370 PushValue,
371 };
372
373 using G = StackAlphabet;
374 using S = InputAlphabet;
375 using Q = State;
376
377 std::stack<StackAlphabet> stack{{G::Base}};
378 State state{Q::Empty};
379 JSONNode::Ptr node = nullptr; //!< Current node we are pointing to in the tree.
380
381 using JSONValue = variant<string, const char *, int, double, bool>;
382
383 //! String representation of @p value.
384 static string
385 valueToString(const JSONValue &value)
386 {
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) + ")";
398 } else {
399 JSON_ASSERTF(false, "Unsupported variant type");
400 s = "[Invalid JSONValue]";
401 }
402 return s;
403 }
404
405 //! Construct a cJSON object out of native types.
406 static cJSON *
407 makeCJSONValue(const JSONValue &value)
408 {
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));
420 } else {
421 JSON_ASSERTF(false, "Unexpected value");
422 }
423 return ret;
424 }
425
426 /*!
427 * @brief Receives inputs and transitions the automata from state to state.
428 *
429 * This is the table of transitions. Can be thought of as three regular FSM
430 * that get switched based on the stack's [top] value. The function is
431 * the implementation of the table.
432 *
433 * [top], [state], [symbol] -> [new-state], [stack-action]
434 * Base, Empty, PushValue -> Finish, -
435 * Base, Empty, StartObject -> BuildObjectKey, push(Object)
436 * Base, Empty, StartArray -> BuildArray, push(Array)
437 * Array, BuildArray, PushValue -> BuildArray, -
438 * Array, BuildArray, StartArray -> BuildArray, push(Array)
439 * Array, BuildArray, EndArray -> [1], pop
440 * Array, BuildArray, StartObject -> BuildObjectKey, push(Object)
441 * Object, BuildObjectKey, PushKey -> BuildObjectValue, -
442 * Object, BuildObjectKey, EndObject -> [1], pop
443 * Object, BuildObjectValue, PushValue -> BuildObjectKey, -
444 * Object, BuildObjectValue, StartObject -> BuildObjectKey, push(Object)
445 * Object, BuildObjectValue, StartArray -> BuildArray, push(Array)
446 * _, _, _, -> Invalid, -
447 *
448 * [1]: Empty or BuildArray or BuildObjectKey depending on new stack.top
449 */
450 void
451 transition(InputAlphabet symbol, const JSONValue &value)
452 {
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());
456
457 // This is basically an if-defined transition function for a pushdown automata
458 if (top == G::Base && state == Q::Empty && symbol == S::PushValue) {
459
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);
463
464 state = Q::Finish;
465
466 } else if (top == G::Base && state == Q::Empty && symbol == S::StartObject) {
467
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);
471
472 state = Q::BuildObjectKey;
473 stack.push(G::Object);
474
475 } else if (top == G::Base && state == Q::Empty && symbol == S::StartArray) {
476
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);
480
481 state = Q::BuildArray;
482 stack.push(G::Array);
483
484 } else if (top == G::Array && state == Q::BuildArray && symbol == S::PushValue) {
485
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);
489 // node = node; // The current node does not change, it is still the array
490
491 state = Q::BuildArray;
492
493 } else if (top == G::Array && state == Q::BuildArray && symbol == S::StartArray) {
494
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);
499
500 state = Q::BuildArray;
501 stack.push(G::Array);
502
503 } else if (top == G::Array && state == Q::BuildArray && symbol == S::EndArray) {
504
505 stack.pop();
506 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
507 state = m[stack.top()];
508
509 JSON_ASSERTF(node->cjson != nullptr, "Failed with %s", valueToString(value).c_str());
510 if (node->parent) {
511 node = node->parent;
512 } else {
513 JSON_ASSERTF(stack.top() == G::Base, "Unexpected non-root node without");
514 }
515
516 } else if (top == G::Array && state == Q::BuildArray && symbol == S::StartObject) {
517
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);
522
523 state = Q::BuildObjectKey;
524 stack.push(G::Object);
525
526 } else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::PushKey) {
527
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);
533
534 state = Q::BuildObjectValue;
535
536 } else if (top == G::Object && state == Q::BuildObjectKey && symbol == S::EndObject) {
537
538 stack.pop();
539 map<G, Q> m{{G::Object, Q::BuildObjectKey}, {G::Array, Q::BuildArray}, {G::Base, Q::Finish}};
540 state = m[stack.top()];
541
542 JSON_ASSERTF(node->cjson != nullptr, "Failed with %s", valueToString(value).c_str());
543 if (node->parent) {
544 node = node->parent;
545 } else {
546 JSON_ASSERTF(stack.top() == G::Base, "Unexpected non-root node without")
547 }
548
549 } else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::PushValue) {
550
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;
556 node = node->parent;
557
558 state = Q::BuildObjectKey;
559
560 } else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartObject) {
561
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;
567
568 state = Q::BuildObjectKey;
569 stack.push(G::Object);
570
571 } else if (top == G::Object && state == Q::BuildObjectValue && symbol == S::StartArray) {
572
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;
578
579 state = Q::BuildArray;
580 stack.push(G::Array);
581
582 } else {
583
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>();
588
589 state = Q::Invalid;
590 }
591
592 JSON_DEBUG("After transition: node=%p parent=%p\n", (void *)node.get(),
593 (void *)(node ? node->parent.get() : nullptr));
594 }
595
596public:
597 JSONBuilder() {}
598
599 //! Receives "[", "]", "{", "}", or any of string, const char*, double, int,
600 //! bool as inputs. Updates the JSONBuilder state with it, after finishing the
601 //! JSON tree, obtain the result with @ref getBuiltNode.
603 operator<<(const JSONValue &value)
604 {
605 bool is_string = holds_alternative<string>(value) || holds_alternative<const char *>(value);
606 if (!is_string) {
607 transition(S::PushValue, value);
608 return *this;
609 }
610
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);
624 } else {
625 JSON_ASSERTF(false, "Invalid state=%d value=%s", static_cast<int>(state), as_string.c_str());
626 }
627 return *this;
628 }
629
630 //! Gets the built JSONNode or crash if the construction has not finished
631 JSONNode::Ptr
633 {
634 JSON_ASSERTF(state == Q::Finish, "Trying to getBuiltNode but the construction has not ended");
635 return node;
636 }
637};
638
639} // namespace xrt::auxiliary::util::json
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:44
Small debug helpers.
Very simple file opening functions.
static const cJSON * get(const cJSON *json, const char *f)
Less typing.
Definition: u_json.c:36