xref: /trueos/contrib/jansson/doc/tutorial.rst (revision 47b2a07a74bf982bec730c936e92feeef05c7575)
1.. _tutorial:
2
3********
4Tutorial
5********
6
7.. highlight:: c
8
9In this tutorial, we create a program that fetches the latest commits
10of a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so
11the result can be parsed using Jansson.
12
13To stick to the the scope of this tutorial, we will only cover the the
14parts of the program related to handling JSON data. For the best user
15experience, the full source code is available:
16:download:`github_commits.c`. To compile it (on Unix-like systems with
17gcc), use the following command::
18
19    gcc -o github_commits github_commits.c -ljansson -lcurl
20
21libcurl_ is used to communicate over the web, so it is required to
22compile the program.
23
24The command line syntax is::
25
26    github_commits USER REPOSITORY
27
28``USER`` is a GitHub user ID and ``REPOSITORY`` is the repository
29name. Please note that the GitHub API is rate limited, so if you run
30the program too many times within a short period of time, the sever
31starts to respond with an error.
32
33.. _GitHub: https://github.com/
34.. _GitHub API: http://developer.github.com/
35.. _libcurl: http://curl.haxx.se/
36
37
38.. _tutorial-github-commits-api:
39
40The GitHub Repo Commits API
41===========================
42
43The `GitHub Repo Commits API`_ is used by sending HTTP requests to
44URLs like ``https://api.github.com/repos/USER/REPOSITORY/commits``,
45where ``USER`` and ``REPOSITORY`` are the GitHub user ID and the name
46of the repository whose commits are to be listed, respectively.
47
48GitHub responds with a JSON array of the following form:
49
50.. code-block:: none
51
52    [
53        {
54            "sha": "<the commit ID>",
55            "commit": {
56                "message": "<the commit message>",
57                <more fields, not important to this tutorial...>
58            },
59            <more fields...>
60        },
61        {
62            "sha": "<the commit ID>",
63            "commit": {
64                "message": "<the commit message>",
65                <more fields...>
66            },
67            <more fields...>
68        },
69        <more commits...>
70    ]
71
72In our program, the HTTP request is sent using the following
73function::
74
75    static char *request(const char *url);
76
77It takes the URL as a parameter, preforms a HTTP GET request, and
78returns a newly allocated string that contains the response body. If
79the request fails, an error message is printed to stderr and the
80return value is *NULL*. For full details, refer to :download:`the code
81<github_commits.c>`, as the actual implementation is not important
82here.
83
84.. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/
85
86.. _tutorial-the-program:
87
88The Program
89===========
90
91First the includes::
92
93    #include <string.h>
94    #include <jansson.h>
95
96Like all the programs using Jansson, we need to include
97:file:`jansson.h`.
98
99The following definitions are used to build the GitHub API request
100URL::
101
102   #define URL_FORMAT   "https://api.github.com/repos/%s/%s/commits"
103   #define URL_SIZE     256
104
105The following function is used when formatting the result to find the
106first newline in the commit message::
107
108    /* Return the offset of the first newline in text or the length of
109       text if there's no newline */
110    static int newline_offset(const char *text)
111    {
112        const char *newline = strchr(text, '\n');
113        if(!newline)
114            return strlen(text);
115        else
116            return (int)(newline - text);
117    }
118
119The main function follows. In the beginning, we first declare a bunch
120of variables and check the command line parameters::
121
122    int main(int argc, char *argv[])
123    {
124        size_t i;
125        char *text;
126        char url[URL_SIZE];
127
128        json_t *root;
129        json_error_t error;
130
131        if(argc != 3)
132        {
133            fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
134            fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
135            return 2;
136        }
137
138Then we build the request URL using the user and repository names
139given as command line parameters::
140
141    snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
142
143This uses the ``URL_SIZE`` and ``URL_FORMAT`` constants defined above.
144Now we're ready to actually request the JSON data over the web::
145
146    text = request(url);
147    if(!text)
148        return 1;
149
150If an error occurs, our function ``request`` prints the error and
151returns *NULL*, so it's enough to just return 1 from the main
152function.
153
154Next we'll call :func:`json_loads()` to decode the JSON text we got
155as a response::
156
157    root = json_loads(text, 0, &error);
158    free(text);
159
160    if(!root)
161    {
162        fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
163        return 1;
164    }
165
166We don't need the JSON text anymore, so we can free the ``text``
167variable right after decoding it. If :func:`json_loads()` fails, it
168returns *NULL* and sets error information to the :type:`json_error_t`
169structure given as the second parameter. In this case, our program
170prints the error information out and returns 1 from the main function.
171
172Now we're ready to extract the data out of the decoded JSON response.
173The structure of the response JSON was explained in section
174:ref:`tutorial-github-commits-api`.
175
176We check that the returned value really is an array::
177
178    if(!json_is_array(root))
179    {
180        fprintf(stderr, "error: root is not an array\n");
181        json_decref(root);
182        return 1;
183    }
184
185Then we proceed to loop over all the commits in the array::
186
187    for(i = 0; i < json_array_size(root); i++)
188    {
189        json_t *data, *sha, *commit, *message;
190        const char *message_text;
191
192        data = json_array_get(root, i);
193        if(!json_is_object(data))
194        {
195            fprintf(stderr, "error: commit data %d is not an object\n", i + 1);
196            json_decref(root);
197            return 1;
198        }
199    ...
200
201The function :func:`json_array_size()` returns the size of a JSON
202array. First, we again declare some variables and then extract the
203i'th element of the ``root`` array using :func:`json_array_get()`.
204We also check that the resulting value is a JSON object.
205
206Next we'll extract the commit ID (a hexadecimal SHA-1 sum),
207intermediate commit info object, and the commit message from that
208object. We also do proper type checks::
209
210        sha = json_object_get(data, "sha");
211        if(!json_is_string(sha))
212        {
213            fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1);
214            json_decref(root);
215            return 1;
216        }
217
218        commit = json_object_get(data, "commit");
219        if(!json_is_object(commit))
220        {
221            fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1);
222            json_decref(root);
223            return 1;
224        }
225
226        message = json_object_get(commit, "message");
227        if(!json_is_string(message))
228        {
229            fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
230            json_decref(root);
231            return 1;
232        }
233    ...
234
235And finally, we'll print the first 8 characters of the commit ID and
236the first line of the commit message. A C-style string is extracted
237from a JSON string using :func:`json_string_value()`::
238
239        message_text = json_string_value(message);
240        printf("%.8s %.*s\n",
241               json_string_value(id),
242               newline_offset(message_text),
243               message_text);
244    }
245
246After sending the HTTP request, we decoded the JSON text using
247:func:`json_loads()`, remember? It returns a *new reference* to the
248JSON value it decodes. When we're finished with the value, we'll need
249to decrease the reference count using :func:`json_decref()`. This way
250Jansson can release the resources::
251
252    json_decref(root);
253    return 0;
254
255For a detailed explanation of reference counting in Jansson, see
256:ref:`apiref-reference-count` in :ref:`apiref`.
257
258The program's ready, let's test it and view the latest commits in
259Jansson's repository::
260
261    $ ./github_commits akheron jansson
262    1581f26a Merge branch '2.3'
263    aabfd493 load: Change buffer_pos to be a size_t
264    bd72efbd load: Avoid unexpected behaviour in macro expansion
265    e8fd3e30 Document and tweak json_load_callback()
266    873eddaf Merge pull request #60 from rogerz/contrib
267    bd2c0c73 Ignore the binary test_load_callback
268    17a51a4b Merge branch '2.3'
269    09c39adc Add json_load_callback to the list of exported symbols
270    cbb80baf Merge pull request #57 from rogerz/contrib
271    040bd7b0 Add json_load_callback()
272    2637faa4 Make test stripping locale independent
273    <...>
274
275
276Conclusion
277==========
278
279In this tutorial, we implemented a program that fetches the latest
280commits of a GitHub repository using the GitHub Repo Commits API.
281Jansson was used to decode the JSON response and to extract the commit
282data.
283
284This tutorial only covered a small part of Jansson. For example, we
285did not create or manipulate JSON values at all. Proceed to
286:ref:`apiref` to explore all features of Jansson.
287