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