A lot of web services use the JSON format. If you are working a GLib based project and need to access a service like this there are two great libraries to help you - libsoup and JSON-Glib.
For my example, I'm going to grab some review data from Ubuntu (API) which looks something like this:
[
{
"ratings_total": 229,
"ratings_average": "3.84",
"app_name": "",
"package_name": "simple-scan",
"histogram": "[35, 13, 22, 42, 117]"
},
{
"ratings_total": 546,
"ratings_average": "4.66",
"app_name": "",
"package_name": "audacity",
"histogram": "[17, 7, 17, 63, 442]"
},
...
]
The data is a single array of objects that contain the statistics for each package. For this example I'll print out the number of ratings for each package by getting the package_name and ratings_total members from each object.
Firstly, I need to download the data. The data is retrieved using a HTTP GET request; in libsoup you can do this with:
SoupSession *session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "test-json", NULL);
SoupMessage *message = soup_message_new (SOUP_METHOD_GET, "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any");
soup_session_send_message (session, message);
Now I have the server text in message->response_body->data but it needs to be decoded. JSON-GLib can parse it with:
JsonParser *parser = json_parser_new ();
json_parser_load_from_data (parser, message->response_body->data, -1, NULL);
JsonNode *root = json_parser_get_root (parser);
Now I have an in-memory tree of the JSON data which can be traversed. After checking the root node is an array as expected I'll iterate over each object:
g_assert (JSON_NODE_HOLDS_ARRAY (root));
array = json_node_get_array (root);
for (i = 0; i < json_array_get_length (array); i++)
{
JsonNode *node = json_array_get_element (array, i);
/* do stuff... */
}
For each object, I extract the required data:
g_assert (JSON_NODE_HOLDS_OBJECT (node));
JsonObject *object = json_node_get_object (node);
const gchar *package_name = json_object_get_string_member (object, "package_name");
gint64 ratings_total = json_object_get_int_member (object, "ratings_total");
if (package_name)
g_print ("%s: %" G_GUINT64_FORMAT "\n", package_name,
Combined into a program, I can print out the number of reviews for each package:
simple-scan: 229
audacity: 546
...
The full program:
// gcc -g -Wall example-json.c -o example-json `pkg-config --cflags --libs libsoup-2.4 json-glib-1.0`
#include
#include
int main (int argc, char **argv)
{
SoupSession *session;
SoupMessage *message;
guint status_code;
JsonParser *parser;
gboolean result;
JsonNode *root;
JsonArray *array;
gint i;
/* Get the data using a HTTP GET */
session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "test-json", NULL);
message = soup_message_new (SOUP_METHOD_GET, "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any");
g_assert (message != NULL);
status_code = soup_session_send_message (session, message);
g_assert (status_code == SOUP_STATUS_OK);
/* Parse the data in JSON format */
parser = json_parser_new ();
result = json_parser_load_from_data (parser, message->response_body->data, -1, NULL);
g_assert (result);
/* The data should contain an array of JSON objects */
root = json_parser_get_root (parser);
g_assert (JSON_NODE_HOLDS_ARRAY (root));
array = json_node_get_array (root);
for (i = 0; i < json_array_get_length (array); i++)
{
JsonNode *node;
JsonObject *object;
const gchar *package_name;
gint64 ratings_total;
/* Get the nth object, skipping unexpected elements */
node = json_array_get_element (array, i);
if (!JSON_NODE_HOLDS_OBJECT (node))
continue;
/* Get the package name and number of ratings from the object - skip if has no name */
object = json_node_get_object (node);
package_name = json_object_get_string_member (object, "package_name");
ratings_total = json_object_get_int_member (object, "ratings_total");
if (package_name)
g_print ("%s: %" G_GINT64_FORMAT "\n", package_name, ratings_total);
}
/* Clean up */
g_object_unref (session);
g_object_unref (message);
g_object_unref (parser);
return 0;
}
And to show you can do the same thing with GIR bindings, here's the same in Vala:
// valac example-json.vala --pkg soup-2.4 --pkg json-glib-1.0
public int main (string[] args)
{
/* Get the data using a HTTP GET */
var session = new Soup.Session.with_options (Soup.SESSION_USER_AGENT, "test-json");
var message = new Soup.Message ("GET", "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any");
assert (message != null);
var status_code = session.send_message (message);
assert (status_code == Soup.Status.OK);
/* Parse the data in JSON format */
var parser = new Json.Parser ();
try
{
parser.load_from_data ((string) message.response_body.data);
}
catch (Error e)
{
}
/* The data should contain an array of JSON objects */
var root = parser.get_root ();
assert (root.get_node_type () == Json.NodeType.ARRAY);
var array = root.get_array ();
for (var i = 0; i
{
/* Get the nth object, skipping unexpected elements */
var node = array.get_element (i);
if (node.get_node_type () != Json.NodeType.OBJECT)
continue;
/* Get the package name and number of ratings from the object - skip if has no name */
var object = node.get_object ();
var package_name = object.get_string_member ("package_name");
var ratings_total = object.get_int_member ("ratings_total");
if (package_name != null)
stdout.printf ("%s: %" + int64.FORMAT + "\n", package_name, ratings_total);
}
return 0;
}
and Python:
#!/usr/bin/python
from gi.repository import Soup
from gi.repository import Json
# Get the data using a HTTP GET
session = Soup.Session.new ()
session.set_property (Soup.SESSION_USER_AGENT, "test-json")
message = Soup.Message.new ("GET", "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any")
assert (message != None)
status_code = session.send_message (message)
assert (status_code == Soup.Status.OK)
# Parse the data in JSON format
parser = Json.Parser ()
parser.load_from_data (message.response_body.data, -1)
# The data should contain an array of JSON objects
root = parser.get_root ()
assert (root.get_node_type () == Json.NodeType.ARRAY)
array = root.get_array ()
for i in xrange (array.get_length ()):
# Get the nth object, skipping unexpected elements
node = array.get_element (i)
if node.get_node_type () != Json.NodeType.OBJECT:
continue
# Get the package name and number of ratings from the object - skip if has no name
object = node.get_object ()
package_name = object.get_string_member ("package_name")
ratings_total = object.get_int_member ("ratings_total")
if package_name != None:
print ("%s: %d" % (package_name, ratings_total))
For my example, I'm going to grab some review data from Ubuntu (API) which looks something like this:
[
{
"ratings_total": 229,
"ratings_average": "3.84",
"app_name": "",
"package_name": "simple-scan",
"histogram": "[35, 13, 22, 42, 117]"
},
{
"ratings_total": 546,
"ratings_average": "4.66",
"app_name": "",
"package_name": "audacity",
"histogram": "[17, 7, 17, 63, 442]"
},
...
]
The data is a single array of objects that contain the statistics for each package. For this example I'll print out the number of ratings for each package by getting the package_name and ratings_total members from each object.
Firstly, I need to download the data. The data is retrieved using a HTTP GET request; in libsoup you can do this with:
SoupSession *session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "test-json", NULL);
SoupMessage *message = soup_message_new (SOUP_METHOD_GET, "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any");
soup_session_send_message (session, message);
Now I have the server text in message->response_body->data but it needs to be decoded. JSON-GLib can parse it with:
JsonParser *parser = json_parser_new ();
json_parser_load_from_data (parser, message->response_body->data, -1, NULL);
JsonNode *root = json_parser_get_root (parser);
Now I have an in-memory tree of the JSON data which can be traversed. After checking the root node is an array as expected I'll iterate over each object:
g_assert (JSON_NODE_HOLDS_ARRAY (root));
array = json_node_get_array (root);
for (i = 0; i < json_array_get_length (array); i++)
{
JsonNode *node = json_array_get_element (array, i);
/* do stuff... */
}
For each object, I extract the required data:
g_assert (JSON_NODE_HOLDS_OBJECT (node));
JsonObject *object = json_node_get_object (node);
const gchar *package_name = json_object_get_string_member (object, "package_name");
gint64 ratings_total = json_object_get_int_member (object, "ratings_total");
if (package_name)
g_print ("%s: %" G_GUINT64_FORMAT "\n", package_name,
Combined into a program, I can print out the number of reviews for each package:
simple-scan: 229
audacity: 546
...
The full program:
// gcc -g -Wall example-json.c -o example-json `pkg-config --cflags --libs libsoup-2.4 json-glib-1.0`
#include
#include
int main (int argc, char **argv)
{
SoupSession *session;
SoupMessage *message;
guint status_code;
JsonParser *parser;
gboolean result;
JsonNode *root;
JsonArray *array;
gint i;
/* Get the data using a HTTP GET */
session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, "test-json", NULL);
message = soup_message_new (SOUP_METHOD_GET, "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any");
g_assert (message != NULL);
status_code = soup_session_send_message (session, message);
g_assert (status_code == SOUP_STATUS_OK);
/* Parse the data in JSON format */
parser = json_parser_new ();
result = json_parser_load_from_data (parser, message->response_body->data, -1, NULL);
g_assert (result);
/* The data should contain an array of JSON objects */
root = json_parser_get_root (parser);
g_assert (JSON_NODE_HOLDS_ARRAY (root));
array = json_node_get_array (root);
for (i = 0; i < json_array_get_length (array); i++)
{
JsonNode *node;
JsonObject *object;
const gchar *package_name;
gint64 ratings_total;
/* Get the nth object, skipping unexpected elements */
node = json_array_get_element (array, i);
if (!JSON_NODE_HOLDS_OBJECT (node))
continue;
/* Get the package name and number of ratings from the object - skip if has no name */
object = json_node_get_object (node);
package_name = json_object_get_string_member (object, "package_name");
ratings_total = json_object_get_int_member (object, "ratings_total");
if (package_name)
g_print ("%s: %" G_GINT64_FORMAT "\n", package_name, ratings_total);
}
/* Clean up */
g_object_unref (session);
g_object_unref (message);
g_object_unref (parser);
return 0;
}
And to show you can do the same thing with GIR bindings, here's the same in Vala:
// valac example-json.vala --pkg soup-2.4 --pkg json-glib-1.0
public int main (string[] args)
{
/* Get the data using a HTTP GET */
var session = new Soup.Session.with_options (Soup.SESSION_USER_AGENT, "test-json");
var message = new Soup.Message ("GET", "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any");
assert (message != null);
var status_code = session.send_message (message);
assert (status_code == Soup.Status.OK);
/* Parse the data in JSON format */
var parser = new Json.Parser ();
try
{
parser.load_from_data ((string) message.response_body.data);
}
catch (Error e)
{
}
/* The data should contain an array of JSON objects */
var root = parser.get_root ();
assert (root.get_node_type () == Json.NodeType.ARRAY);
var array = root.get_array ();
for (var i = 0; i
{
/* Get the nth object, skipping unexpected elements */
var node = array.get_element (i);
if (node.get_node_type () != Json.NodeType.OBJECT)
continue;
/* Get the package name and number of ratings from the object - skip if has no name */
var object = node.get_object ();
var package_name = object.get_string_member ("package_name");
var ratings_total = object.get_int_member ("ratings_total");
if (package_name != null)
stdout.printf ("%s: %" + int64.FORMAT + "\n", package_name, ratings_total);
}
return 0;
}
and Python:
#!/usr/bin/python
from gi.repository import Soup
from gi.repository import Json
# Get the data using a HTTP GET
session = Soup.Session.new ()
session.set_property (Soup.SESSION_USER_AGENT, "test-json")
message = Soup.Message.new ("GET", "https://reviews.ubuntu.com/reviews/api/1.0/review-stats/any/any")
assert (message != None)
status_code = session.send_message (message)
assert (status_code == Soup.Status.OK)
# Parse the data in JSON format
parser = Json.Parser ()
parser.load_from_data (message.response_body.data, -1)
# The data should contain an array of JSON objects
root = parser.get_root ()
assert (root.get_node_type () == Json.NodeType.ARRAY)
array = root.get_array ()
for i in xrange (array.get_length ()):
# Get the nth object, skipping unexpected elements
node = array.get_element (i)
if node.get_node_type () != Json.NodeType.OBJECT:
continue
# Get the package name and number of ratings from the object - skip if has no name
object = node.get_object ()
package_name = object.get_string_member ("package_name")
ratings_total = object.get_int_member ("ratings_total")
if package_name != None:
print ("%s: %d" % (package_name, ratings_total))
No comments:
Post a Comment