To ease into things, let's start by creating a simple utility function to parse a JSON file; later we'll delve into how we can control the parsing in more detail. First, we consider how to read a JSON file into memory and print the file contents as a c string. For this example and many of the rest of the examples, I am going to use the JSON representation of a person taken from the aforementioned Wikipedia article, contact.json:
{
"firstName": "John",
"lastName": "Smith",
"isAlive": true,
"age": 27,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
}
],
"children": [],
"spouse": null
}
Now examine json-file00.c:
#include <stdio.h>
#include <json-c/json.h>
int
main(void)
{
json_object *root = json_object_from_file("contact.json");
if (!root)
return 1;
printf("The json file:\n\n%s\n", json_object_to_json_string(root));
json_object_put(root);
return 0;
}
Compile with:
gcc json-file00.c -ljson-c -o json-file00
Now run json-file00:
$ ./json-file00
The json file:
{ "firstName": "John", "lastName": "Smith", "isAlive": true, "age": 27, "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY", "postalCode": "10021-3100" }, "phoneNumbers": [ { "type": "home", "number": "212 555-1234" }, { "type": "office", "number": "646 555-4567" } ], "children": [ ], "spouse": null }
Admittedly, the output is not very pretty, all extraneous whitespace has been stripped from the document. That may or may not be what you want in your application. Fortunately, json-c offers a little more control over the printing of the JSON object.
Now lets look at our next program: json-file01.c. Compile with gcc json-file01.c -ljson-c -o json-file01:
#include <stdio.h>
#include <json-c/json.h>
int
main(void)
{
json_object *root = json_object_from_file("contact.json");
if (!root)
return 1;
printf("The json file:\n\n%s\n", json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
json_object_put(root);
return 0;
}
The output from running json-file01 is:
$ ./json-file01
The json file:
{
"firstName":"John",
"lastName":"Smith",
"isAlive":true,
"age":27,
"address":{
"streetAddress":"21 2nd Street",
"city":"New York",
"state":"NY",
"postalCode":"10021-3100"
},
"phoneNumbers":[
{
"type":"home",
"number":"212 555-1234"
},
{
"type":"office",
"number":"646 555-4567"
}
],
"children":[
],
"spouse":null
}
Here, the output is more readable having been reformatted somewhat, but it is certainly more suitable for human reading than before. Especially for more complex and long json files.
So let's examine what we have learned from the code above.
First and foremost, I have introduced a new 'type': json_object. As expected for a C program, json_object is really an opaque structure whose internal implementation is not available in application code outside of the json-c library. Your code will only ever deal with a pointer to a json_object, and all operations must be performed through the json-c API functions. Some programmers who wish to stress the idea json_object is a struct would write the line of code:
json_object *root = json_object_from_file("contact.json");
as
struct json_object *root = json_object_from_file("contact.json");
That is a personal choice and do whichever you prefer. I prefer to leave the struct part off and think of it more like an additional basic type the library offers much like int or float.
Next I introduce the function:
This function should be self-explanatory: It reads the file filename and returns the json_object representing the JSON data contained in the file. If there is a failure to read the file, this function returns NULL. Of course, as we will later see json-c can also create a json_object from a string (technically a NULL-terminated character array in C, not that I am pedantic enough to care).
Now note, right before our program exits we have the function:
We call this function on the json_object we created using json_object_from_file. You can think of this as freeing the memory allocated by creating the json_object. Technically though, the memory is only freed if the reference count of the object is zero. The official documentation states json_object_put
Decrements the reference count of json_object and frees if it reaches zero. You must have ownership of obj prior to doing this or you will cause an imbalance in the reference count.
You may be thinking, "What is meant by ownership of an object?" More on that latter, but for now, note most of sample programs will follow this template:
Pseudo-Code | Ownership |
---|---|
Create or initialize a root JSON_object | at this point we own the object root and roots reference count is 1 |
Process JSON, saving or displaying JSON data as needed | we usually create or initialize other json objects here and transfer ownership of them to root. As longs as roots ownership is not transfered or shared with another object, roots reference count is unchanged |
Dereference our JSON object | at this point we lose ownership of root and its reference count is decremented. If roots reference count is 0, roots memory and all objects owned by root with a reference count of 0 is freed |
Finally, I introduced 2 functions and a constant to convert the json_object to a string:
- const char* json_object_to_json_string(json_object *obj)
- const char* json_object_to_json_string_ext(json_object *obj, int flags)
- JSON_C_TO_STRING_PRETTY
First, the constant JSON_C_TO_STRING_PRETTY used in the function json_object_to_json_string_ext as a formatting flag. There are 6 such flags, the remaining ones are:
- JSON_C_TO_STRING_PLAIN
- JSON_C_TO_STRING_SPACED
- JSON_C_TO_STRING_PRETTY_TAB
- JSON_C_TO_STRING_NOZERO
- JSON_C_TO_STRING_NOSLASHESCAPE
For information of the remaining flags your json-c version supports consult the documentation.
These flags tell the function json_object_to_json_string_ext how to format the JSON in the string representation. We have already seen the usage and effect of JSON_C_TO_STRING_PLAIN: The function json_object_to_json_string(obj) is equivalent to json_object_to_json_string_ext(obj, JSON_C_TO_STRING_SPACED). Here all superfluous white space is removed from the string representation.
Now, to see the effect of using JSON_C_TO_STRING_SPACED edit the file json-file01.c and add it as the flag instead of JSON_C_TO_STRING_PRETTY. What do think it does?
- What happens if our json file contains invalid json and we run json-file01 ? Alter the program to print a message informing the user the json is invalid.