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