Commit 9771af32 authored by Pēteris Krišjānis's avatar Pēteris Krišjānis Committed by Philip Withnall

core: Add support for JSON to GDataParsable, GDataEntry and GDataFeed

This also includes initial code for detection of the Content-Type of received
messages, and parsing JSON or XML depending on that.

This breaks ABI (but not API), and adds a dependency on json-glib ≥ 0.15.

Complete unit tests are included. Further work is expected for integrating
JSON support into GDataService, ready for use with the Tasks service.

This work is originally by Pēteris Krišjānis <pecisk@gmail.com>, with
additions by Philip Withnall <philip@tecnocode.co.uk>.

Helps: https://bugzilla.gnome.org/show_bug.cgi?id=657539
parent ce7bc546
......@@ -487,7 +487,7 @@ EXTRA_DIST += m4/introspection.m4
if HAVE_INTROSPECTION
gdata/GData-@GDATA_API_VERSION_MAJOR@.@GDATA_API_VERSION_MINOR@.gir: gdata/libgdata.la
gdata_GData_@GDATA_API_VERSION_MAJOR@_@GDATA_API_VERSION_MINOR@_gir_INCLUDES = GObject-2.0 libxml2-2.0 Soup-2.4
gdata_GData_@GDATA_API_VERSION_MAJOR@_@GDATA_API_VERSION_MINOR@_gir_INCLUDES = GObject-2.0 libxml2-2.0 Soup-2.4 Json-1.0
if ENABLE_GOA
gdata_GData_@GDATA_API_VERSION_MAJOR@_@GDATA_API_VERSION_MINOR@_gir_INCLUDES += Goa-1.0
endif
......@@ -521,7 +521,7 @@ gdata/libgdata.vapi: gdata/GData-@GDATA_API_VERSION_MAJOR@.@GDATA_API_VERSION_MI
VAPIGEN_VAPIS = gdata/libgdata.vapi
gdata_libgdata_vapi_DEPS = libxml-2.0 libsoup-2.4
gdata_libgdata_vapi_DEPS = libxml-2.0 libsoup-2.4 json-glib-1.0
gdata_libgdata_vapi_METADATADIRS = $(srcdir)/gdata
gdata_libgdata_vapi_FILES = gdata/GData-@GDATA_API_VERSION_MAJOR@.@GDATA_API_VERSION_MINOR@.gir
......
......@@ -18,6 +18,7 @@ Dependencies
• gio-2.0 ≥ 2.17.3
• libsoup-2.4 ≥ 2.37.91
• liboauth ≥ 0.9.4
• json-glib ≥ 0.15.0
If compiling with --enable-gnome (for GNOME support):
• libsoup-gnome-2.4
......
......@@ -39,6 +39,7 @@ SOUP_REQS=2.37.91
OAUTH_REQS=0.9.4
GTK_REQS=2.91.2
GOA_REQS=3.2
JSON_GLIB_REQS=0.15
# Before making a release, the GDATA_LT_VERSION string should be modified. The string is of the form c:r:a. Follow these instructions sequentially:
#
......@@ -64,7 +65,7 @@ AC_SUBST(GDATA_API_VERSION)
AC_SUBST(GDATA_API_VERSION_MAJOR)
AC_SUBST(GDATA_API_VERSION_MINOR)
GDATA_PACKAGES_PUBLIC="gobject-2.0 glib-2.0 >= $GLIB_REQS gio-2.0 >= $GIO_REQS libxml-2.0 libsoup-2.4 >= $SOUP_REQS"
GDATA_PACKAGES_PUBLIC="gobject-2.0 glib-2.0 >= $GLIB_REQS gio-2.0 >= $GIO_REQS libxml-2.0 libsoup-2.4 >= $SOUP_REQS json-glib-1.0 >= $JSON_GLIB_REQS"
GDATA_PACKAGES_PRIVATE="gthread-2.0 oauth >= $OAUTH_REQS"
GDATA_PACKAGES="$GDATA_PACKAGES_PUBLIC $GDATA_PACKAGES_PRIVATE"
AC_SUBST([GDATA_PACKAGES_PUBLIC])
......
......@@ -708,6 +708,8 @@ GDataParsable
GDataParsableClass
gdata_parsable_new_from_xml
gdata_parsable_get_xml
gdata_parsable_new_from_json
gdata_parsable_get_json
<SUBSECTION Standard>
gdata_parsable_get_type
GDATA_IS_PARSABLE
......
Color struct
Parsable.get_xml#method skip
Parsable.get_json#method skip
Query.get_query_uri#method skip
DOCUMENTS_PRESENTATION_* name="DOCUMENTS_PRESENTATION_(.+)" parent="GData.DocumentsPresentationFormat"
DOCUMENTS_TEXT_* name="DOCUMENTS_TEXT_(.+)" parent="GData.DocumentsTextFormat"
......@@ -8,4 +9,4 @@ DOCUMENTS_SPREADSHEET_* name="DOCUMENTS_SPREADSHEET_(.+)" parent="GData.Document
CONTACTS_GENDER_* name="CONTACTS_GENDER_(.+)" parent="GData.ContactsGender"
CONTACTS_GROUP_* name="CONTACTS_GROUP_(.+)" parent="GData.ContactsGroupType"
CONTACTS_PRIORITY_* name="CONTACTS_PRIORITY_(.+)" parent="GData.ContactsPriority"
CONTACTS_SENSITIVITY_* name="CONTACTS_SENSITIVITY_(.+)" parent="GData.ContactsSensitivity"
\ No newline at end of file
CONTACTS_SENSITIVITY_* name="CONTACTS_SENSITIVITY_(.+)" parent="GData.ContactsSensitivity"
......@@ -33,6 +33,7 @@
#include <glib/gi18n-lib.h>
#include <libxml/parser.h>
#include <string.h>
#include <json-glib/json-glib.h>
#include "gdata-entry.h"
#include "gdata-types.h"
......@@ -55,6 +56,8 @@ static void pre_get_xml (GDataParsable *parsable, GString *xml_string);
static void get_xml (GDataParsable *parsable, GString *xml_string);
static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
static gchar *get_entry_uri (const gchar *id) G_GNUC_WARN_UNUSED_RESULT;
static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
static void get_json (GDataParsable *parsable, JsonBuilder *builder);
struct _GDataEntryPrivate {
gchar *title;
......@@ -112,6 +115,9 @@ gdata_entry_class_init (GDataEntryClass *klass)
parsable_class->get_namespaces = get_namespaces;
parsable_class->element_name = "entry";
parsable_class->parse_json = parse_json;
parsable_class->get_json = get_json;
klass->get_entry_uri = get_entry_uri;
/**
......@@ -584,6 +590,98 @@ get_entry_uri (const gchar *id)
return g_strdup (id);
}
static gboolean
parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
{
gboolean success;
GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
if (gdata_parser_string_from_json_member (reader, "title", P_DEFAULT | P_NO_DUPES, &(priv->title), &success, error) == TRUE ||
gdata_parser_string_from_json_member (reader, "id", P_NON_EMPTY | P_NO_DUPES, &(priv->id), &success, error) == TRUE ||
gdata_parser_int64_time_from_json_member (reader, "updated", P_REQUIRED | P_NO_DUPES, &(priv->updated), &success, error) == TRUE ||
gdata_parser_string_from_json_member (reader, "etag", P_NON_EMPTY | P_NO_DUPES, &(priv->etag), &success, error) == TRUE) {
return success;
} else if (g_strcmp0 (json_reader_get_member_name (reader), "selfLink") == 0) {
GDataLink *_link;
const gchar *uri;
/* Empty URI? */
uri = json_reader_get_string_value (reader);
if (uri == NULL || *uri == '\0') {
return gdata_parser_error_required_json_content_missing (reader, error);
}
_link = gdata_link_new (uri, GDATA_LINK_SELF);
gdata_entry_add_link (GDATA_ENTRY (parsable), _link);
g_object_unref (_link);
return TRUE;
} else if (g_strcmp0 (json_reader_get_member_name (reader), "kind") == 0) {
GDataCategory *category;
const gchar *kind;
/* Empty kind? */
kind = json_reader_get_string_value (reader);
if (kind == NULL || *kind == '\0') {
return gdata_parser_error_required_json_content_missing (reader, error);
}
category = gdata_category_new (kind, "http://schemas.google.com/g/2005#kind", NULL);
gdata_entry_add_category (GDATA_ENTRY (parsable), category);
g_object_unref (category);
return TRUE;
}
return GDATA_PARSABLE_CLASS (gdata_entry_parent_class)->parse_json (parsable, reader, user_data, error);
}
static void
get_json (GDataParsable *parsable, JsonBuilder *builder)
{
GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
GList *i;
GDataLink *_link;
json_builder_set_member_name (builder, "title");
json_builder_add_string_value (builder, priv->title);
if (priv->id != NULL) {
json_builder_set_member_name (builder, "id");
json_builder_add_string_value (builder, priv->id);
}
if (priv->updated != -1) {
gchar *updated = gdata_parser_int64_to_iso8601 (priv->updated);
json_builder_set_member_name (builder, "updated");
json_builder_add_string_value (builder, updated);
g_free (updated);
}
/* If we have a "kind" category, add that. */
for (i = priv->categories; i != NULL; i = i->next) {
GDataCategory *category = GDATA_CATEGORY (i->data);
if (g_strcmp0 (gdata_category_get_scheme (category), "http://schemas.google.com/g/2005#kind") == 0) {
json_builder_set_member_name (builder, "kind");
json_builder_add_string_value (builder, gdata_category_get_term (category));
}
}
/* Add the ETag, if available. */
if (gdata_entry_get_etag (GDATA_ENTRY (parsable)) != NULL) {
json_builder_set_member_name (builder, "etag");
json_builder_add_string_value (builder, priv->etag);
}
/* Add the self-link. */
_link = gdata_entry_look_up_link (GDATA_ENTRY (parsable), GDATA_LINK_SELF);
if (_link != NULL) {
json_builder_set_member_name (builder, "selfLink");
json_builder_add_string_value (builder, gdata_link_get_uri (_link));
}
}
/**
* gdata_entry_new:
* @id: (allow-none): the entry's ID, or %NULL
......
......@@ -36,6 +36,7 @@
#include <glib/gi18n-lib.h>
#include <libxml/parser.h>
#include <string.h>
#include <json-glib/json-glib.h>
#include "gdata-feed.h"
#include "gdata-entry.h"
......@@ -57,6 +58,9 @@ static void _gdata_feed_add_category (GDataFeed *self, GDataCategory *category);
static void _gdata_feed_add_link (GDataFeed *self, GDataLink *link);
static void _gdata_feed_add_author (GDataFeed *self, GDataAuthor *author);
static gboolean parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
static gboolean post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error);
struct _GDataFeedPrivate {
GList *entries;
gchar *title;
......@@ -112,6 +116,9 @@ gdata_feed_class_init (GDataFeedClass *klass)
parsable_class->get_namespaces = get_namespaces;
parsable_class->element_name = "feed";
parsable_class->parse_json = parse_json;
parsable_class->post_parse_json = post_parse_json;
/**
* GDataFeed:title:
*
......@@ -584,6 +591,57 @@ get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
GDATA_PARSABLE_GET_CLASS (i->data)->get_namespaces (GDATA_PARSABLE (i->data), namespaces);
}
static gboolean
parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
{
GDataFeed *self = GDATA_FEED (parsable);
ParseData *data = user_data;
if (g_strcmp0 (json_reader_get_member_name (reader), "items") == 0) {
gint i, elements;
/* Loop through the elements array. */
for (i = 0, elements = json_reader_count_elements (reader); i < elements; i++) {
GDataEntry *entry;
GType entry_type;
json_reader_read_element (reader, i);
/* Allow @data to be %NULL, and assume we're parsing a vanilla feed, so that we can test #GDataFeed in tests/general.c.
* A little hacky, but not too much so, and valuable for testing. */
entry_type = (data != NULL) ? data->entry_type : GDATA_TYPE_ENTRY;
/* Parse the node, passing it the reader cursor. */
entry = GDATA_ENTRY (_gdata_parsable_new_from_json_node (entry_type, reader, NULL, error));
if (entry == NULL)
return FALSE;
/* Calls the callbacks in the main thread */
if (data != NULL)
_gdata_feed_call_progress_callback (self, data, entry);
_gdata_feed_add_entry (self, entry);
g_object_unref (entry);
json_reader_end_element (reader);
}
} else {
return GDATA_PARSABLE_CLASS (gdata_feed_parent_class)->parse_json (parsable, reader, user_data, error);
}
return TRUE;
}
static gboolean
post_parse_json (GDataParsable *parsable, gpointer user_data, GError **error)
{
GDataFeedPrivate *priv = GDATA_FEED (parsable)->priv;
/* Reverse our lists of stuff. */
priv->entries = g_list_reverse (priv->entries);
return TRUE;
}
/*
* _gdata_feed_new:
* @title: the feed's title
......@@ -632,6 +690,25 @@ _gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType
return feed;
}
GDataFeed *
_gdata_feed_new_from_json (GType feed_type, const gchar *json, gint length, GType entry_type,
GDataQueryProgressCallback progress_callback, gpointer progress_user_data, gboolean is_async, GError **error)
{
ParseData *data;
GDataFeed *feed;
g_return_val_if_fail (g_type_is_a (feed_type, GDATA_TYPE_FEED), NULL);
g_return_val_if_fail (json != NULL, NULL);
g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
data = _gdata_feed_parse_data_new (entry_type, progress_callback, progress_user_data, is_async);
feed = GDATA_FEED (_gdata_parsable_new_from_json (feed_type, json, length, data, error));
_gdata_feed_parse_data_free (data);
return feed;
}
/**
* gdata_feed_get_entries:
* @self: a #GDataFeed
......
......@@ -36,6 +36,7 @@
#include <glib/gi18n-lib.h>
#include <string.h>
#include <libxml/parser.h>
#include <json-glib/json-glib.h>
#include "gdata-parsable.h"
#include "gdata-private.h"
......@@ -51,10 +52,16 @@ static void gdata_parsable_get_property (GObject *object, guint property_id, GVa
static void gdata_parsable_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
static void gdata_parsable_finalize (GObject *object);
static gboolean real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
static gboolean real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
struct _GDataParsablePrivate {
/* XML stuff. */
GString *extra_xml;
GHashTable *extra_namespaces;
/* JSON stuff. */
GHashTable/*<gchar*, owned JsonNode*>*/ *extra_json;
gboolean constructed_from_xml;
};
......@@ -75,6 +82,7 @@ gdata_parsable_class_init (GDataParsableClass *klass)
gobject_class->set_property = gdata_parsable_set_property;
gobject_class->finalize = gdata_parsable_finalize;
klass->parse_xml = real_parse_xml;
klass->parse_json = real_parse_json;
/**
* GDataParsable:constructed-from-xml:
......@@ -98,6 +106,9 @@ gdata_parsable_init (GDataParsable *self)
self->priv->extra_xml = g_string_new ("");
self->priv->extra_namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
self->priv->extra_json = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) json_node_free);
self->priv->constructed_from_xml = FALSE;
}
......@@ -142,6 +153,8 @@ gdata_parsable_finalize (GObject *object)
g_string_free (priv->extra_xml, TRUE);
g_hash_table_destroy (priv->extra_namespaces);
g_hash_table_destroy (priv->extra_json);
/* Chain up to the parent class */
G_OBJECT_CLASS (gdata_parsable_parent_class)->finalize (object);
}
......@@ -178,6 +191,90 @@ real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer us
return TRUE;
}
/* Extract the member node. This would be a lot easier if JsonReader had an API to return
* the current node (regardless of whether it's a value, object or array). FIXME: bgo#707100. */
static JsonNode * /* transfer full */
_json_reader_dup_current_node (JsonReader *reader)
{
JsonNode *value;
if (json_reader_is_value (reader) == TRUE) {
/* Value nodes are easy. Well, ignoring the complication of nulls. */
if (json_reader_get_null_value (reader) == TRUE) {
value = json_node_new (JSON_NODE_NULL);
} else {
value = json_node_copy (json_reader_get_value (reader));
}
} else if (json_reader_is_object (reader) == TRUE) {
/* Object nodes require deep copies. */
gint i, members;
JsonObject *obj;
obj = json_object_new ();
for (i = 0, members = json_reader_count_members (reader); i < members; i++) {
json_reader_read_element (reader, i);
json_object_set_member (obj, json_reader_get_member_name (reader), _json_reader_dup_current_node (reader));
json_reader_end_element (reader);
}
value = json_node_new (JSON_NODE_OBJECT);
json_node_take_object (value, obj);
} else if (json_reader_is_array (reader) == TRUE) {
/* Array nodes require deep copies. */
gint i, elements;
JsonArray *arr;
arr = json_array_new ();
for (i = 0, elements = json_reader_count_elements (reader); i < elements; i++) {
json_reader_read_element (reader, i);
json_array_add_element (arr, _json_reader_dup_current_node (reader));
json_reader_end_element (reader);
}
value = json_node_new (JSON_NODE_ARRAY);
json_node_take_array (value, arr);
} else {
/* Uh-oh. */
g_assert_not_reached ();
}
return value;
}
static gboolean
real_parse_json (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error)
{
gchar *json, *member_name;
JsonGenerator *generator;
JsonNode *value;
/* Unhandled JSON member. Save it and its value to ->extra_xml so that it's not lost if we
* re-upload this Parsable to the server. */
member_name = g_strdup (json_reader_get_member_name (reader));
g_assert (member_name != NULL);
/* Extract a copy of the current node. */
value = _json_reader_dup_current_node (reader);
g_assert (value != NULL);
/* Serialise the value for debugging. */
generator = json_generator_new ();
json_generator_set_root (generator, value);
json = json_generator_to_data (generator, NULL);
g_debug ("Unhandled JSON member ‘%s’ in %s: %s", member_name, G_OBJECT_TYPE_NAME (parsable), json);
g_free (json);
g_object_unref (generator);
/* Save the value. Transfer ownership of the member_name and value. */
g_hash_table_replace (parsable->priv->extra_json, (gpointer) member_name, (gpointer) value);
return TRUE;
}
/**
* gdata_parsable_new_from_xml:
* @parsable_type: the type of the class represented by the XML
......@@ -320,6 +417,125 @@ _gdata_parsable_new_from_xml_node (GType parsable_type, xmlDoc *doc, xmlNode *no
return parsable;
}
/**
* gdata_parsable_new_from_json:
* @parsable_type: the type of the class represented by the JSON
* @json: the JSON for just the parsable object
* @length: the length of @json, or -1
* @error: a #GError, or %NULL
*
* Creates a new #GDataParsable subclass (of the given @parsable_type) from the given @json.
*
* An object of the given @parsable_type is created, and its <function>parse_json</function> and
* <function>post_parse_json</function> class functions called on the JSON node obtained from @json.
* <function>post_parse_json</function> is called once on the root node, while <function>parse_json</function> is called for
* each of the node's members.
*
* If @length is -1, @json will be assumed to be nul-terminated.
*
* If an error occurs during parsing, a suitable error from #GDataParserError will be returned.
*
* Return value: a new #GDataParsable, or %NULL; unref with g_object_unref()
*
* Since: UNRELEASED
*/
GDataParsable *
gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, GError **error)
{
g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
g_return_val_if_fail (json != NULL && *json != '\0', NULL);
g_return_val_if_fail (length >= -1, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
return _gdata_parsable_new_from_json (parsable_type, json, length, NULL, error);
}
GDataParsable *
_gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length, gpointer user_data, GError **error)
{
JsonParser *parser;
JsonReader *reader;
GDataParsable *parsable;
GError *child_error = NULL;
g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
g_return_val_if_fail (json != NULL && *json != '\0', NULL);
g_return_val_if_fail (length >= -1, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (length == -1)
length = strlen (json);
parser = json_parser_new ();
if (!json_parser_load_from_data (parser, json, length, &child_error)) {
g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
/* Translators: the parameter is an error message */
_("Error parsing JSON: %s"), child_error->message);
g_error_free (child_error);
g_object_unref (parser);
return NULL;
}
reader = json_reader_new (json_parser_get_root (parser));
parsable = _gdata_parsable_new_from_json_node (parsable_type, reader, user_data, error);
g_object_unref (reader);
g_object_unref (parser);
return parsable;
}
GDataParsable *
_gdata_parsable_new_from_json_node (GType parsable_type, JsonReader *reader, gpointer user_data, GError **error)
{
GDataParsable *parsable;
GDataParsableClass *klass;
gint i;
g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE), NULL);
g_return_val_if_fail (reader != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
/* Indicator property which allows distinguishing between locally created and server based objects
* as it is used for non-XML tasks, and adding another one for JSON would be a bit pointless. */
parsable = g_object_new (parsable_type, "constructed-from-xml", TRUE, NULL);
klass = GDATA_PARSABLE_GET_CLASS (parsable);
g_assert (klass->parse_json != NULL);
/* Check that the outermost node is an object. */
if (json_reader_is_object (reader) == FALSE) {
g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
/* Translators: the parameter is an error message */
_("Error parsing JSON: %s"),
_("Outermost JSON node is not an object."));
g_object_unref (parsable);
return NULL;
}
/* Parse each child member. This assumes the outermost node is an object. */
for (i = 0; i < json_reader_count_members (reader); i++) {
g_return_val_if_fail (json_reader_read_element (reader, i), NULL);
if (klass->parse_json (parsable, reader, user_data, error) == FALSE) {
g_object_unref (parsable);
return NULL;
}
json_reader_end_element (reader);
}
/* Call the post-parse function */
if (klass->post_parse_json != NULL &&
klass->post_parse_json (parsable, user_data, error) == FALSE) {
g_object_unref (parsable);
return NULL;
}
return parsable;
}
static void
build_namespaces_cb (gchar *prefix, gchar *href, GString *output)
{
......@@ -437,6 +653,82 @@ _gdata_parsable_get_xml (GDataParsable *self, GString *xml_string, gboolean decl
g_string_append_printf (xml_string, "</%s>", klass->element_name);
}
/**
* gdata_parsable_get_json:
* @self: a #GDataParsable
*
* Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted on the server. The JSON
* is valid for stand-alone use.
*
* Return value: the object's JSON; free with g_free()
*
* Since: UNRELEASED
*/
gchar *
gdata_parsable_get_json (GDataParsable *self)
{
JsonGenerator *generator;
JsonBuilder *builder;
JsonNode *root;
gchar *output;
g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
/* Build the JSON tree. */
builder = json_builder_new ();
_gdata_parsable_get_json (self, builder);
root = json_builder_get_root (builder);
g_object_unref (builder);
/* Serialise it to a string. */
generator = json_generator_new ();
json_generator_set_root (generator, root);
output = json_generator_to_data (generator, NULL);
g_object_unref (generator);
json_node_free (root);
return output;
}
/*
* _gdata_parsable_get_json:
* @self: a #GDataParsable
* @builder: a #JsonBuilder to build the JSON in
*
* Builds a JSON representation of the #GDataParsable in its current state, such that it could be inserted on the server.
*
* Since: UNRELEASED
*/
void
_gdata_parsable_get_json (GDataParsable *self, JsonBuilder *builder)
{
GDataParsableClass *klass;
GHashTableIter iter;
gchar *member_name;
JsonNode *value;
g_return_if_fail (GDATA_IS_PARSABLE (self));
g_return_if_fail (JSON_IS_BUILDER (builder));
klass = GDATA_PARSABLE_GET_CLASS (self);
json_builder_begin_object (builder);
/* Add the JSON. */
if (klass->get_json != NULL)
klass->get_json (self, builder);
/* Any extra JSON which we couldn't parse before? */
g_hash_table_iter_init (&iter, self->priv->extra_json);
while (g_hash_table_iter_next (&iter, (gpointer *) &member_name, (gpointer *) &value) == TRUE) {
json_builder_set_member_name (builder, member_name);
json_builder_add_value (builder, json_node_copy (value)); /* transfers ownership */
}
json_builder_end_object (builder);
}
/*
* _gdata_parsable_is_constructed_from_xml:
* @self: a #GDataParsable
......
......@@ -23,6 +23,7 @@
#include <glib.h>
#include <glib-object.h>
#include <libxml/parser.h>
#include <json-glib/json-glib.h>
G_BEGIN_DECLS
......@@ -73,10 +74,14 @@ typedef struct {
* XML node to be added to @xml_string
* @get_xml: a function to build an XML representation of the #GDataParsable in its current state, appending it to the provided #GString
* @get_namespaces: a function to return a string containing the namespace declarations used by the @parsable when represented in XML form
* @parse_json: a function to parse a JSON representation of the #GDataParsable to set the properties of the @parsable
* @post_parse_json: a function called after parsing a JSON object, to allow the @parsable to validate the parsed properties
* @get_json: a function to build a JSON representation of the #GDataParsable in its current state, appending it to the provided #JsonBuilder
* @element_name: the name of the XML element which represents this parsable
* @element_namespace: the prefix of the XML namespace used for the parsable
*
* The class structure for the #GDataParsable class.
* The class structure for the #GDataParsable class. Note that JSON and XML functions are mutually exclusive:
* a given implementation of #GDataParsable is represented as exactly one of JSON and XML.
*
* Since: 0.3.0
**/
......@@ -91,6 +96,10 @@ typedef struct {
void (*get_xml) (GDataParsable *parsable, GString *xml_string);
void (*get_namespaces) (GDataParsable *parsable, GHashTable *namespaces);
gboolean (*parse_json) (GDataParsable *parsable, JsonReader *reader, gpointer user_data, GError **error);
gboolean (*post_parse_json) (GDataParsable *parsable, gpointer user_data, GError **error);
void (*get_json) (GDataParsable *parsable, JsonBuilder *builder);
const gchar *element_name;
const gchar *element_namespace;
} GDataParsableClass;
......@@ -101,6 +110,10 @@ GDataParsable *gdata_parsable_new_from_xml (GType parsable_type, const gchar *xm
GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
gchar *gdata_parsable_get_xml (GDataParsable *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
GDataParsable *gdata_parsable_new_from_json (GType parsable_type, const gchar *json, gint length,
GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
gchar *gdata_parsable_get_json (GDataParsable *self) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
G_END_DECLS
#endif /* !GDATA_PARSABLE_H */
......@@ -23,7 +23,9 @@
#include <glib/gi18n-lib.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <libxml/parser.h>
#include <json-glib/json-glib.h>
#include "gdata-parser.h"
#include "gdata-service.h"
......@@ -259,6 +261,60 @@ gdata_parser_int64_from_iso8601 (const gchar *date, gint64 *_time)
return FALSE;
}
gboolean
gdata_parser_error_required_json_content_missing (JsonReader *reader, GError **error)
{
const gchar *element_string = json_reader_get_member_name (reader);
/* Translators: the parameter is the name of an JSON element.
*
* For example:
* A 'title' element was missing required content. */
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A \'%s\' element was missing required content."), element_string);
return FALSE;
}
gboolean
gdata_parser_error_duplicate_json_element (JsonReader *reader, GError **error)
{
const gchar *element_string = json_reader_get_member_name (reader);
/* Translators: the parameter is the name of an JSON element.
*
* For example:
* A singleton element (title) was duplicated. */
g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A singleton element (%s) was duplicated."), element_string);