jansson/doc/tutorial.rst

289 lines
8.7 KiB
ReStructuredText
Raw Permalink Normal View History

2009-10-17 19:18:40 +08:00
.. _tutorial:
********
Tutorial
********
.. highlight:: c
In this tutorial, we create a program that fetches the latest commits
of a repository in GitHub_ over the web. `GitHub API`_ uses JSON, so
the result can be parsed using Jansson.
2009-10-17 19:18:40 +08:00
2017-07-16 18:16:34 +08:00
To stick to the scope of this tutorial, we will only cover the
2009-10-17 19:18:40 +08:00
parts of the program related to handling JSON data. For the best user
experience, the full source code is available:
:download:`github_commits.c`. To compile it (on Unix-like systems with
gcc), use the following command::
gcc -o github_commits github_commits.c -ljansson -lcurl
libcurl_ is used to communicate over the web, so it is required to
compile the program.
The command line syntax is::
github_commits USER REPOSITORY
``USER`` is a GitHub user ID and ``REPOSITORY`` is the repository
name. Please note that the GitHub API is rate limited, so if you run
the program too many times within a short period of time, the sever
starts to respond with an error.
.. _GitHub: https://github.com/
.. _GitHub API: http://developer.github.com/
2009-10-17 19:18:40 +08:00
.. _libcurl: http://curl.haxx.se/
.. _tutorial-github-commits-api:
The GitHub Repo Commits API
===========================
2009-10-17 19:18:40 +08:00
The `GitHub Repo Commits API`_ is used by sending HTTP requests to
URLs like ``https://api.github.com/repos/USER/REPOSITORY/commits``,
where ``USER`` and ``REPOSITORY`` are the GitHub user ID and the name
of the repository whose commits are to be listed, respectively.
2009-10-17 19:18:40 +08:00
GitHub responds with a JSON array of the following form:
2009-10-17 19:18:40 +08:00
.. code-block:: none
[
{
"sha": "<the commit ID>",
"commit": {
2009-10-17 19:18:40 +08:00
"message": "<the commit message>",
<more fields, not important to this tutorial...>
2009-10-17 19:18:40 +08:00
},
<more fields...>
},
{
"sha": "<the commit ID>",
"commit": {
2009-10-17 19:18:40 +08:00
"message": "<the commit message>",
<more fields...>
2009-10-17 19:18:40 +08:00
},
<more fields...>
},
<more commits...>
]
2009-10-17 19:18:40 +08:00
In our program, the HTTP request is sent using the following
function::
static char *request(const char *url);
It takes the URL as a parameter, performs a HTTP GET request, and
returns a newly allocated string that contains the response body. If
the request fails, an error message is printed to stderr and the
return value is *NULL*. For full details, refer to :download:`the code
<github_commits.c>`, as the actual implementation is not important
here.
2009-10-17 19:18:40 +08:00
.. _GitHub Repo Commits API: http://developer.github.com/v3/repos/commits/
2009-10-17 19:18:40 +08:00
.. _tutorial-the-program:
The Program
===========
First the includes::
#include <string.h>
#include <jansson.h>
Like all the programs using Jansson, we need to include
:file:`jansson.h`.
The following definitions are used to build the GitHub API request
URL::
#define URL_FORMAT "https://api.github.com/repos/%s/%s/commits"
#define URL_SIZE 256
2009-10-17 19:18:40 +08:00
The following function is used when formatting the result to find the
first newline in the commit message::
/* Return the offset of the first newline in text or the length of
text if there's no newline */
static int newline_offset(const char *text)
{
const char *newline = strchr(text, '\n');
if(!newline)
return strlen(text);
else
return (int)(newline - text);
}
The main function follows. In the beginning, we first declare a bunch
of variables and check the command line parameters::
int main(int argc, char *argv[])
{
size_t i;
char *text;
char url[URL_SIZE];
2009-10-17 19:18:40 +08:00
json_t *root;
json_error_t error;
2009-10-17 19:18:40 +08:00
if(argc != 3)
{
fprintf(stderr, "usage: %s USER REPOSITORY\n\n", argv[0]);
fprintf(stderr, "List commits at USER's REPOSITORY.\n\n");
return 2;
}
2009-10-17 19:18:40 +08:00
Then we build the request URL using the user and repository names
given as command line parameters::
snprintf(url, URL_SIZE, URL_FORMAT, argv[1], argv[2]);
This uses the ``URL_SIZE`` and ``URL_FORMAT`` constants defined above.
Now we're ready to actually request the JSON data over the web::
text = request(url);
if(!text)
return 1;
If an error occurs, our function ``request`` prints the error and
returns *NULL*, so it's enough to just return 1 from the main
function.
2010-09-06 03:00:47 +08:00
Next we'll call :func:`json_loads()` to decode the JSON text we got
2009-10-17 19:18:40 +08:00
as a response::
root = json_loads(text, 0, &error);
2009-10-17 19:18:40 +08:00
free(text);
if(!root)
{
fprintf(stderr, "error: on line %d: %s\n", error.line, error.text);
return 1;
}
We don't need the JSON text anymore, so we can free the ``text``
2010-09-06 03:00:47 +08:00
variable right after decoding it. If :func:`json_loads()` fails, it
returns *NULL* and sets error information to the :type:`json_error_t`
2009-10-17 19:18:40 +08:00
structure given as the second parameter. In this case, our program
prints the error information out and returns 1 from the main function.
Now we're ready to extract the data out of the decoded JSON response.
The structure of the response JSON was explained in section
:ref:`tutorial-github-commits-api`.
We check that the returned value really is an array::
2009-10-17 19:18:40 +08:00
if(!json_is_array(root))
2009-10-17 19:18:40 +08:00
{
fprintf(stderr, "error: root is not an array\n");
2013-03-10 03:15:09 +08:00
json_decref(root);
2009-10-17 19:18:40 +08:00
return 1;
}
Then we proceed to loop over all the commits in the array::
for(i = 0; i < json_array_size(root); i++)
2009-10-17 19:18:40 +08:00
{
json_t *data, *sha, *commit, *message;
2009-10-17 19:18:40 +08:00
const char *message_text;
data = json_array_get(root, i);
if(!json_is_object(data))
2009-10-17 19:18:40 +08:00
{
fprintf(stderr, "error: commit data %d is not an object\n", i + 1);
2013-03-10 03:15:09 +08:00
json_decref(root);
2009-10-17 19:18:40 +08:00
return 1;
}
...
2010-09-06 03:00:47 +08:00
The function :func:`json_array_size()` returns the size of a JSON
2009-10-17 19:18:40 +08:00
array. First, we again declare some variables and then extract the
i'th element of the ``root`` array using :func:`json_array_get()`.
We also check that the resulting value is a JSON object.
2009-10-17 19:18:40 +08:00
Next we'll extract the commit ID (a hexadecimal SHA-1 sum),
intermediate commit info object, and the commit message from that
object. We also do proper type checks::
sha = json_object_get(data, "sha");
if(!json_is_string(sha))
{
fprintf(stderr, "error: commit %d: sha is not a string\n", i + 1);
2013-03-10 03:15:09 +08:00
json_decref(root);
return 1;
}
2009-10-17 19:18:40 +08:00
commit = json_object_get(data, "commit");
if(!json_is_object(commit))
2009-10-17 19:18:40 +08:00
{
fprintf(stderr, "error: commit %d: commit is not an object\n", i + 1);
2013-03-10 03:15:09 +08:00
json_decref(root);
2009-10-17 19:18:40 +08:00
return 1;
}
message = json_object_get(commit, "message");
if(!json_is_string(message))
2009-10-17 19:18:40 +08:00
{
fprintf(stderr, "error: commit %d: message is not a string\n", i + 1);
2013-03-10 03:15:09 +08:00
json_decref(root);
2009-10-17 19:18:40 +08:00
return 1;
}
...
And finally, we'll print the first 8 characters of the commit ID and
the first line of the commit message. A C-style string is extracted
2010-09-06 03:00:47 +08:00
from a JSON string using :func:`json_string_value()`::
2009-10-17 19:18:40 +08:00
message_text = json_string_value(message);
printf("%.8s %.*s\n",
2017-07-16 18:16:34 +08:00
json_string_value(sha),
2009-10-17 19:18:40 +08:00
newline_offset(message_text),
message_text);
}
After sending the HTTP request, we decoded the JSON text using
2010-09-06 03:00:47 +08:00
:func:`json_loads()`, remember? It returns a *new reference* to the
2009-10-17 19:18:40 +08:00
JSON value it decodes. When we're finished with the value, we'll need
2010-09-06 03:00:47 +08:00
to decrease the reference count using :func:`json_decref()`. This way
2009-10-17 19:18:40 +08:00
Jansson can release the resources::
json_decref(root);
return 0;
For a detailed explanation of reference counting in Jansson, see
:ref:`apiref-reference-count` in :ref:`apiref`.
The program's ready, let's test it and view the latest commits in
2016-08-31 02:09:28 +08:00
Jansson's repository:
.. code-block:: shell
2009-10-17 19:18:40 +08:00
$ ./github_commits akheron jansson
1581f26a Merge branch '2.3'
aabfd493 load: Change buffer_pos to be a size_t
bd72efbd load: Avoid unexpected behaviour in macro expansion
e8fd3e30 Document and tweak json_load_callback()
873eddaf Merge pull request #60 from rogerz/contrib
bd2c0c73 Ignore the binary test_load_callback
17a51a4b Merge branch '2.3'
09c39adc Add json_load_callback to the list of exported symbols
cbb80baf Merge pull request #57 from rogerz/contrib
040bd7b0 Add json_load_callback()
2637faa4 Make test stripping locale independent
<...>
2009-10-17 19:18:40 +08:00
Conclusion
==========
In this tutorial, we implemented a program that fetches the latest
commits of a GitHub repository using the GitHub Repo Commits API.
Jansson was used to decode the JSON response and to extract the commit
data.
2009-10-17 19:18:40 +08:00
This tutorial only covered a small part of Jansson. For example, we
did not create or manipulate JSON values at all. Proceed to
:ref:`apiref` to explore all features of Jansson.