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