1 2.. highlight:: cpp 3 4JSON 5==== 6 7The JSON part of orcus consists of a low-level parser class that handles 8parsing of JSON strings, and a high-level document class that stores parsed 9JSON structures as a node tree. 10 11There are two approaches to processing JSON strings using the orcus library. 12One approach is to utilize the :cpp:class:`~orcus::json::document_tree` class 13to load and populate the JSON structure tree via its 14:cpp:func:`~orcus::json::document_tree::load()` method and traverse the tree 15through its :cpp:func:`~orcus::json::document_tree::get_document_root()` method. 16This approach is ideal if you want a quick way to parse and access the content 17of a JSON document with minimal effort. 18 19Another approach is to use the low-level :cpp:class:`~orcus::json_parser` 20class directly by providing your own handler class to receive callbacks from 21the parser. This method requires a bit more effort on your part to provide 22and populate your own data structure, but if you already have a data structure 23to store the content of JSON, then this approach is ideal. The 24:cpp:class:`~orcus::json::document_tree` class internally uses 25:cpp:class:`~orcus::json_parser` to parse JSON contents. 26 27 28Populating a document tree from JSON string 29------------------------------------------- 30 31The following code snippet shows an example of how to populate an instance of 32:cpp:class:`~orcus::json::document_tree` from a JSON string, and navigate its 33content tree afterward. 34 35:: 36 37 #include <orcus/json_document_tree.hpp> 38 #include <orcus/config.hpp> 39 #include <orcus/pstring.hpp> 40 41 #include <cstdlib> 42 #include <iostream> 43 44 using namespace std; 45 46 const char* json_string = "{" 47 " \"name\": \"John Doe\"," 48 " \"occupation\": \"Software Engineer\"," 49 " \"score\": [89, 67, 90]" 50 "}"; 51 52 int main() 53 { 54 using node = orcus::json::node; 55 56 orcus::json_config config; // Use default configuration. 57 58 orcus::json::document_tree doc; 59 doc.load(json_string, config); 60 61 // Root is an object containing three key-value pairs. 62 node root = doc.get_document_root(); 63 64 for (const orcus::pstring& key : root.keys()) 65 { 66 node value = root.child(key); 67 switch (value.type()) 68 { 69 case orcus::json::node_t::string: 70 // string value 71 cout << key << ": " << value.string_value() << endl; 72 break; 73 case orcus::json::node_t::array: 74 { 75 // array value 76 cout << key << ":" << endl; 77 78 for (size_t i = 0; i < value.child_count(); ++i) 79 { 80 node array_element = value.child(i); 81 cout << " - " << array_element.numeric_value() << endl; 82 } 83 } 84 break; 85 default: 86 ; 87 } 88 } 89 90 return EXIT_SUCCESS; 91 } 92 93You'll see the following output when executing this code: 94 95.. code-block:: text 96 97 name: John Doe 98 occupation: Software Engineer 99 score: 100 - 89 101 - 67 102 - 90 103 104 105Using the low-level parser 106-------------------------- 107 108The following code snippet shows how to use the low-level :cpp:class:`~orcus::json_parser` 109class by providing an own handler class and passing it as a template argument:: 110 111 #include <orcus/json_parser.hpp> 112 #include <orcus/pstring.hpp> 113 #include <cstring> 114 #include <iostream> 115 116 using namespace std; 117 118 class json_parser_handler : public orcus::json_handler 119 { 120 public: 121 void object_key(const char* p, size_t len, bool transient) 122 { 123 cout << "object key: " << orcus::pstring(p, len) << endl; 124 } 125 126 void string(const char* p, size_t len, bool transient) 127 { 128 cout << "string: " << orcus::pstring(p, len) << endl; 129 } 130 131 void number(double val) 132 { 133 cout << "number: " << val << endl; 134 } 135 }; 136 137 int main() 138 { 139 const char* test_code = "{\"key1\": [1,2,3,4,5], \"key2\": 12.3}"; 140 size_t n = strlen(test_code); 141 142 cout << "JSON string: " << test_code << endl; 143 144 // Instantiate the parser with an own handler. 145 json_parser_handler hdl; 146 orcus::json_parser<json_parser_handler> parser(test_code, n, hdl); 147 148 // Parse the string. 149 parser.parse(); 150 151 return EXIT_SUCCESS; 152 } 153 154The parser constructor expects the char array, its length, and the handler 155instance. The base handler class :cpp:class:`~orcus::json_handler` implements 156all required handler methods. By inheriting from it, you only need to 157implement the handler methods you need. In this example, we are only 158implementing the :cpp:func:`~orcus::json_handler::object_key`, 159:cpp:func:`~orcus::json_handler::string`, and :cpp:func:`~orcus::json_handler::number` 160methods to process object key values, string values and numeric values, 161respectively. Refer to the :cpp:class:`~orcus::json_handler` class definition 162for all available handler methods. 163 164Executing this code will generate the following output: 165 166.. code-block:: text 167 168 JSON string: {"key1": [1,2,3,4,5], "key2": 12.3} 169 object key: key1 170 number: 1 171 number: 2 172 number: 3 173 number: 4 174 number: 5 175 object key: key2 176 number: 12.3 177 178 179Building a document tree directly 180--------------------------------- 181 182You can also create and populate a JSON document tree directly without needing 183to parse a JSON string. This approach is ideal if you want to create a JSON 184tree from scratch and export it as a string. The following series of code 185snippets demonstrate how to exactly build JSON document trees directly and 186export their contents as JSON strings. 187 188The first example shows how to initialize the tree with a simple array:: 189 190 orcus::json::document_tree doc = { 191 1.0, 2.0, "string value", false, nullptr 192 }; 193 194 std::cout << doc.dump() << std::endl; 195 196You can simply specify the content of the array via initialization list and 197assign it to the document. The :cpp:func:`~orcus::json::document_tree::dump()` 198method then turns the content into a single string instance, which looks like 199the following: 200 201.. code-block:: text 202 203 [ 204 1, 205 2, 206 "string value", 207 false, 208 null 209 ] 210 211If you need to build a array of arrays, do like the following:: 212 213 orcus::json::document_tree doc = { 214 { true, false, nullptr }, 215 { 1.1, 2.2, "text" } 216 }; 217 218 std::cout << doc.dump() << std::endl; 219 220This will create an array of two nested child arrays with three values each. 221Dumping the content of the tree as a JSON string will produce something like 222the following: 223 224.. code-block:: text 225 226 [ 227 [ 228 true, 229 false, 230 null 231 ], 232 [ 233 1.1, 234 2.2, 235 "text" 236 ] 237 ] 238 239Creating an object can be done by nesting one of more key-value pairs, each of 240which is surrounded by a pair of curly braces, inside another pair of curly 241braces. For example, the following code:: 242 243 orcus::json::document_tree doc = { 244 { "key1", 1.2 }, 245 { "key2", "some text" }, 246 }; 247 248 std::cout << doc.dump() << std::endl; 249 250produces the following output: 251 252.. code-block:: text 253 254 { 255 "key1": 1.2, 256 "key2": "some text" 257 } 258 259indicating that the tree consists of a single object having two key-value 260pairs. 261 262You may notice that this syntax is identical to the syntax for 263creating an array of arrays as shown above. In fact, in order for this to be 264an object, each of the inner sequences must have exactly two values, and its 265first value must be a string value. Failing that, it will be interpreted as 266an array of arrays. 267 268As with arrays, nesting of objects is also supported. The following code:: 269 270 orcus::json::document_tree doc = { 271 { "parent1", { 272 { "child1", true }, 273 { "child2", false }, 274 { "child3", 123.4 }, 275 } 276 }, 277 { "parent2", "not-nested" }, 278 }; 279 280 std::cout << doc.dump() << std::endl; 281 282creates a root object having two key-value pairs one of which contains 283another object having three key-value pairs, as evident in the following output 284generated by this code: 285 286.. code-block:: text 287 288 { 289 "parent1": { 290 "child1": true, 291 "child2": false, 292 "child3": 123.4 293 }, 294 "parent2": "not-nested" 295 } 296 297There is one caveat that you need to be aware of because of this special 298object creation syntax. When you have a nested array that exactly contains 299two values and the first value is a string value, you must explicitly declare 300that as an array by using an :cpp:class:`~orcus::json::array` class instance. 301For instance, this code:: 302 303 orcus::json::document_tree doc = { 304 { "array", { "one", 987.0 } } 305 }; 306 307is intended to be an object containing an array. However, because the supposed 308inner array contains exactly two values and the first value is a string 309value, which could be interpreted as a key-value pair for the outer object, it 310ends up being too ambiguous and a :cpp:class:`~orcus::json::key_value_error` 311exception gets thrown as a result. 312 313To work around this ambiguity, you need to declare the inner array to be 314explicit by using an :cpp:class:`~orcus::json::array` instance:: 315 316 using namespace orcus; 317 318 json::document_tree doc = { 319 { "array", json::array({ "one", 987.0 }) } 320 }; 321 322This code now correctly generates a root object containing one key-value pair 323whose value is an array: 324 325.. code-block:: text 326 327 { 328 "array": [ 329 "one", 330 987 331 ] 332 } 333 334Similar ambiguity issue arises when you want to construct a tree consisting 335only of an empty root object. You may be tempted to write something like 336this:: 337 338 using namespace orcus; 339 340 json::document_tree doc = {}; 341 342However, this will result in leaving the tree entirely unpopulated i.e. the 343tree will not even have a root node! If you continue on and try to get a root 344node from this tree, you'll get a :cpp:class:`~orcus::json::document_error` 345thrown as a result. If you inspect the error message stored in the exception:: 346 347 try 348 { 349 auto root = doc.get_document_root(); 350 } 351 catch (const json::document_error& e) 352 { 353 std::cout << e.what() << std::endl; 354 } 355 356you will get 357 358.. code-block:: text 359 360 json::document_error: document tree is empty 361 362giving you further proof that the tree is indeed empty! The solution here is 363to directly assign an instance of :cpp:class:`~orcus::json::object` to the 364document tree, which will initialize the tree with an empty root object. The 365following code:: 366 367 using namespace orcus; 368 369 json::document_tree doc = json::object(); 370 371 std::cout << doc.dump() << std::endl; 372 373will therefore generate 374 375.. code-block:: text 376 377 { 378 } 379 380You can also use the :cpp:class:`~orcus::json::object` class instances to 381indicate empty objects anythere in the tree. For instance, this code:: 382 383 using namespace orcus; 384 385 json::document_tree doc = { 386 json::object(), 387 json::object(), 388 json::object() 389 }; 390 391is intended to create an array containing three empty objects as its elements, 392and that's exactly what it does: 393 394.. code-block:: text 395 396 [ 397 { 398 }, 399 { 400 }, 401 { 402 } 403 ] 404 405So far all the examples have shown how to initialize the document tree as the 406tree itself is being constructed. But our next example shows how to create 407new key-value pairs to existing objects after the document tree instance has 408been initialized. 409 410:: 411 412 using namespace orcus; 413 414 // Initialize the tree with an empty object. 415 json::document_tree doc = json::object(); 416 417 // Get the root object, and assign three key-value pairs. 418 json::node root = doc.get_document_root(); 419 root["child1"] = 1.0; 420 root["child2"] = "string"; 421 root["child3"] = { true, false }; // implicit array 422 423 // You can also create a key-value pair whose value is another object. 424 root["child object"] = { 425 { "key1", 100.0 }, 426 { "key2", 200.0 } 427 }; 428 429 root["child array"] = json::array({ 1.1, 1.2, true }); // explicit array 430 431This code first initializes the tree with an empty object, then retrieves the 432root empty object and assigns several key-value pairs to it. When converting 433the tree content to a string and inspecting it you'll see something like the 434following: 435 436.. code-block:: text 437 438 { 439 "child array": [ 440 1.1, 441 1.2, 442 true 443 ], 444 "child1": 1, 445 "child3": [ 446 true, 447 false 448 ], 449 "child2": "string", 450 "child object": { 451 "key1": 100, 452 "key2": 200 453 } 454 } 455 456The next example shows how to append values to an existing array after the 457tree has been constructed. Let's take a look at the code:: 458 459 using namespace orcus; 460 461 // Initialize the tree with an empty array root. 462 json::document_tree doc = json::array(); 463 464 // Get the root array. 465 json::node root = doc.get_document_root(); 466 467 // Append values to the array. 468 root.push_back(-1.2); 469 root.push_back("string"); 470 root.push_back(true); 471 root.push_back(nullptr); 472 473 // You can append an object to the array via push_back() as well. 474 root.push_back({{"key1", 1.1}, {"key2", 1.2}}); 475 476Like the previous example, this code first initializes the tree but this time 477with an empty array as its root, retrieves the root array, then appends 478several values to it via its :cpp:func:`~orcus::json::node::push_back` method. 479 480When you dump the content of this tree as a JSON string you'll get something 481like this: 482 483.. code-block:: text 484 485 [ 486 -1.2, 487 "string", 488 true, 489 null, 490 { 491 "key1": 1.1, 492 "key2": 1.2 493 } 494 ] 495 496