diff options
| author | Emmanuele Bassi <ebassi@linux.intel.com> | 2009-10-27 17:53:34 +0000 |
|---|---|---|
| committer | Emmanuele Bassi <ebassi@linux.intel.com> | 2009-10-27 17:53:34 +0000 |
| commit | ff986ee5b8df45255f4f5ab01be0bbad893bc55e (patch) | |
| tree | 16d565e41225266c2d762f174d1f238c7c72d2d6 | |
| parent | 7f6a73a0964b66b15e8b5a9858b9bc76b010f67b (diff) | |
| download | json-glib-ff986ee5b8df45255f4f5ab01be0bbad893bc55e.tar.gz | |
gobject: Add experimental GBoxed<->JSON transformation
Serializing and deserializing GBoxed types is fairly complicated
currently. If a GObject implements JsonSerializable it is possible
for the class to intercept the JsonNode, parse it manually and
then set the value to the property.
This leaves a hole opened for:
• manual (de)serialization of GBoxed types
• (de)serialization of GBoxed properties in classes not
implementing JsonSerializable
In order to serialize and deserialize a GBoxed JSON-GLib should
provide a mechanism similar to the GValue transformation functions:
when registering the boxed type the developer should also be able
to register a serialization and a deserialization functions pair
matching the tuple:
(GBoxed type, JSON type)
The serialization function would be:
JsonNode *(* JsonBoxedSerializeFunc) (gconstpointer boxed);
And, conversely, the deserialization function would be:
gpointer (* JsonBoxedDeserializeFunc) (JsonNode *node);
Obviously, the whole machinery works only for GBoxed types that
register the serialization and deserialization functions.
| -rw-r--r-- | .gitignore | 68 | ||||
| -rw-r--r-- | json-glib/json-gobject.c | 264 | ||||
| -rw-r--r-- | json-glib/json-gobject.h | 37 | ||||
| -rw-r--r-- | tests/Makefile.am | 8 | ||||
| -rw-r--r-- | tests/test-serialize-boxed.c | 263 |
5 files changed, 602 insertions, 38 deletions
@@ -7,6 +7,7 @@ autom4te.cache !/build/autotools/shave.m4 !/build/autotools/as-compiler-flag.m4 !/build/autotools/introspection.m4 +compile configure config.guess config.h @@ -16,7 +17,7 @@ config.log config.status config.sub depcomp -doc/reference/version.xml +/doc/reference/version.xml gtk-doc.make install-sh json-glib-1.0.pc @@ -25,46 +26,47 @@ json-glib.pc .libs *.o *.lo -json-enum-types.[ch] -json-marshal.[ch] -json-version.h -Json-1.0.gir -Json-1.0.typelib -*.la -stamp-enum-types -stamp-marshal -array-test -object-test -node-test +/json-glib/json-enum-types.[ch] +/json-glib/json-marshal.[ch] +/json-glib/json-version.h +/json-glib/Json-1.0.gir +/json-glib/Json-1.0.typelib +/json-glib/*.la +/json-glib/stamp-enum-types +/json-glib/stamp-marshal +/json-glib/tests/array-test +/json-glib/tests/object-test +/json-glib/tests/node-test libtool ltmain.sh missing stamp-h1 test-report.xml test-report.html -test-parser -test-generator -test-serialize-simple -test-serialize-complex -test-serialize-full +/tests/test-parser +/tests/test-generator +/tests/test-serialize-simple +/tests/test-serialize-complex +/tests/test-serialize-full +/tests/test-serialize-boxed .*.swp *.stamp -doc/reference/html -doc/reference/tmpl -doc/reference/xml -doc/reference/json-glib-decl-list.txt -doc/reference/json-glib-decl-list.txt.bak -doc/reference/json-glib-decl.txt -doc/reference/json-glib-decl.txt.bak -doc/reference/json-glib-overrides.txt -doc/reference/json-glib-undeclared.txt -doc/reference/json-glib-undocumented.txt -doc/reference/json-glib-unused.txt -doc/reference/json-glib.args -doc/reference/json-glib.hierarchy -doc/reference/json-glib.interfaces -doc/reference/json-glib.prerequisites -doc/reference/json-glib.signals +/doc/reference/html +/doc/reference/tmpl +/doc/reference/xml +/doc/reference/json-glib-decl-list.txt +/doc/reference/json-glib-decl-list.txt.bak +/doc/reference/json-glib-decl.txt +/doc/reference/json-glib-decl.txt.bak +/doc/reference/json-glib-overrides.txt +/doc/reference/json-glib-undeclared.txt +/doc/reference/json-glib-undocumented.txt +/doc/reference/json-glib-unused.txt +/doc/reference/json-glib.args +/doc/reference/json-glib.hierarchy +/doc/reference/json-glib.interfaces +/doc/reference/json-glib.prerequisites +/doc/reference/json-glib.signals omf.make xmldocs.make /build/autotools/gtk-doc.m4 diff --git a/json-glib/json-gobject.c b/json-glib/json-gobject.c index 2a8ec94..7c9a04d 100644 --- a/json-glib/json-gobject.c +++ b/json-glib/json-gobject.c @@ -45,6 +45,243 @@ #include "json-parser.h" #include "json-generator.h" +typedef struct _BoxedTransform BoxedTransform; + +struct _BoxedTransform +{ + GType boxed_type; + gint node_type; + + JsonBoxedSerializeFunc serialize; + JsonBoxedDeserializeFunc deserialize; +}; + +G_LOCK_DEFINE_STATIC (boxed_transforms); +static GSList *boxed_transforms = NULL; + +static gint +boxed_transforms_cmp (gconstpointer a, + gconstpointer b) +{ + const BoxedTransform *ta = a; + const BoxedTransform *tb = b; + + return tb->boxed_type - ta->boxed_type; +} + +static gint +boxed_transforms_find (gconstpointer a, + gconstpointer b) +{ + const BoxedTransform *haystack = a; + const BoxedTransform *needle = b; + + if (needle->node_type != -1) + return (haystack->boxed_type == needle->boxed_type && + haystack->node_type == needle->node_type) ? 0 : 1; + else + return (haystack->boxed_type == needle->boxed_type) ? 0 : 1; +} + +static BoxedTransform * +lookup_boxed_transform (GType gboxed_type, + JsonNodeType node_type) +{ + BoxedTransform lookup; + GSList *t; + + lookup.boxed_type = gboxed_type; + lookup.node_type = node_type; + + t = g_slist_find_custom (boxed_transforms, &lookup, boxed_transforms_find); + if (t == NULL) + return NULL; + + return t->data; +} + +/** + * json_boxed_register_transform_func: + * @gboxed_type: a boxed type + * @node_type: a node type + * @serialize_func: (allow-none): serialization function for @boxed_type + * into a #JsonNode of type @node_type; can be %NULL if @deserialize_func + * is not %NULL + * @deserialize_func: (allow-none): deserialization function for @boxed_type + * from a #JsonNode of type @node_type; can be %NULL if @serialize_func + * is not %NULL + * + * Registers a serialization and deserialization functions for a #GBoxed + * of type @gboxed_type to and from a #JsonNode of type @node_type + * + * Since: 0.10 + */ +void +json_boxed_register_transform_func (GType gboxed_type, + JsonNodeType node_type, + JsonBoxedSerializeFunc serialize_func, + JsonBoxedDeserializeFunc deserialize_func) +{ + BoxedTransform *t; + + g_return_if_fail (G_TYPE_IS_BOXED (gboxed_type)); + g_return_if_fail (G_TYPE_IS_ABSTRACT (gboxed_type) == FALSE); + + if (serialize_func == NULL) + g_return_if_fail (deserialize_func != NULL); + + if (deserialize_func == NULL) + g_return_if_fail (serialize_func != NULL); + + G_LOCK (boxed_transforms); + + t = lookup_boxed_transform (gboxed_type, node_type); + if (t == NULL) + { + t = g_slice_new (BoxedTransform); + + t->boxed_type = gboxed_type; + t->node_type = node_type; + t->serialize = serialize_func; + t->deserialize = deserialize_func; + + boxed_transforms = g_slist_insert_sorted (boxed_transforms, t, + boxed_transforms_cmp); + } + else + g_warning ("A transformation for the boxed type %s into " + "JSON nodes of type %s already exists", + g_type_name (gboxed_type), + json_node_type_get_name (node_type)); + + G_UNLOCK (boxed_transforms); +} + +/** + * json_boxed_can_serialize: + * @gboxed_type: a boxed type + * @node_type: (out): the #JsonNode type to which the boxed type can be + * deserialized into + * + * Checks whether it is possible to serialize a #GBoxed of + * type @gboxed_type into a #JsonNode of type @node_type + * + * Return value: %TRUE if the type can be serialized, %FALSE otherwise + * + * Since: 0.10 + */ +gboolean +json_boxed_can_serialize (GType gboxed_type, + JsonNodeType *node_type) +{ + BoxedTransform *t; + + g_return_val_if_fail (G_TYPE_IS_BOXED (gboxed_type), FALSE); + g_return_val_if_fail (G_TYPE_IS_ABSTRACT (gboxed_type) == FALSE, FALSE); + + t = lookup_boxed_transform (gboxed_type, -1); + if (t != NULL && t->serialize != NULL) + { + if (node_type) + *node_type = t->node_type; + + return TRUE; + } + + return FALSE; +} + +/** + * json_boxed_can_deserialize: + * @gboxed_type: a boxed type + * @node_type: a #JsonNode type + * + * Checks whether it is possible to deserialize a #GBoxed of + * type @gboxed_type from a #JsonNode of type @node_type + * + * Return value: %TRUE if the type can be deserialized, %FALSE otherwise + * + * Since: 0.10 + */ +gboolean +json_boxed_can_deserialize (GType gboxed_type, + JsonNodeType node_type) +{ + BoxedTransform *t; + + g_return_val_if_fail (G_TYPE_IS_BOXED (gboxed_type), FALSE); + g_return_val_if_fail (G_TYPE_IS_ABSTRACT (gboxed_type) == FALSE, FALSE); + + t = lookup_boxed_transform (gboxed_type, node_type); + if (t != NULL && t->deserialize != NULL) + return TRUE; + + return FALSE; +} + +/** + * json_boxed_serialize: + * @gboxed_type: a boxed type + * @node_type: a #JsonNode type + * @boxed: a pointer to a #GBoxed of type @gboxed_type + * + * Serializes @boxed, a pointer to a #GBoxed of type @gboxed_type, + * into a #JsonNode of type @node_type + * + * Return value: a #JsonNode with the serialization of the boxed + * type, or %NULL if serialization either failed or was not + * possible + * + * Since: 0.10 + */ +JsonNode * +json_boxed_serialize (GType gboxed_type, + JsonNodeType node_type, + gconstpointer boxed) +{ + BoxedTransform *t; + + g_return_val_if_fail (G_TYPE_IS_BOXED (gboxed_type), NULL); + g_return_val_if_fail (G_TYPE_IS_ABSTRACT (gboxed_type) == FALSE, NULL); + g_return_val_if_fail (boxed != NULL, NULL); + + t = lookup_boxed_transform (gboxed_type, node_type); + if (t != NULL && t->serialize != NULL) + return t->serialize (boxed); + + return NULL; +} + +/** + * json_boxed_serialize: + * @gboxed_type: a boxed type + * @node: a #JsonNode + * + * Deserializes @node into @boxed, a pointer to a #GBoxed of type + * @gboxed_type + * + * Since: 0.10 + */ +gpointer +json_boxed_deserialize (GType gboxed_type, + JsonNode *node) +{ + JsonNodeType node_type; + BoxedTransform *t; + + g_return_val_if_fail (G_TYPE_IS_BOXED (gboxed_type), NULL); + g_return_val_if_fail (G_TYPE_IS_ABSTRACT (gboxed_type) == FALSE, NULL); + g_return_val_if_fail (node != NULL, NULL); + + node_type = json_node_get_node_type (node); + + t = lookup_boxed_transform (gboxed_type, node_type); + if (t != NULL && t->deserialize != NULL) + return t->deserialize (node); + + return NULL; +} + /* forward declaration */ static JsonNode *json_serialize_pspec (const GValue *real_value, GParamSpec *pspec); @@ -396,6 +633,21 @@ json_deserialize_pspec (GValue *value, GValue node_value = { 0, }; gboolean retval = FALSE; + if (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (value)) == G_TYPE_BOXED) + { + JsonNodeType node_type = json_node_get_node_type (node); + GType boxed_type = G_VALUE_TYPE (value); + + if (json_boxed_can_deserialize (boxed_type, node_type)) + { + gpointer boxed = json_boxed_deserialize (boxed_type, node); + + g_value_take_boxed (value, boxed); + + return TRUE; + } + } + switch (JSON_NODE_TYPE (node)) { case JSON_NODE_OBJECT: @@ -554,6 +806,7 @@ json_serialize_pspec (const GValue *real_value, { JsonNode *retval = NULL; GValue value = { 0, }; + JsonNodeType node_type; switch (G_TYPE_FUNDAMENTAL (G_VALUE_TYPE (real_value))) { @@ -611,10 +864,17 @@ json_serialize_pspec (const GValue *real_value, retval = json_node_new (JSON_NODE_ARRAY); json_node_take_array (retval, array); } - else + else if (json_boxed_can_serialize (G_VALUE_TYPE (real_value), &node_type)) { - g_warning ("Unsupported type `%s'", g_type_name (G_VALUE_TYPE (real_value))); + gpointer boxed = g_value_get_boxed (real_value); + + retval = json_boxed_serialize (G_VALUE_TYPE (real_value), + node_type, + boxed); } + else + g_warning ("Boxed type '%s' is not handled by JSON-GLib", + g_type_name (G_VALUE_TYPE (real_value))); break; case G_TYPE_UINT: diff --git a/json-glib/json-gobject.h b/json-glib/json-gobject.h index 8eacc58..207db52 100644 --- a/json-glib/json-gobject.h +++ b/json-glib/json-gobject.h @@ -79,6 +79,43 @@ gboolean json_serializable_deserialize_property (JsonSerializable *serializable GParamSpec *pspec, JsonNode *property_node); +/** + * JsonBoxedSerializeFunc: + * @boxed: a #GBoxed + * + * Serializes the passed #GBoxed and stores it inside a #JsonNode + * + * Return value: the newly created #JsonNode + * + * Since: 0.10 + */ +typedef JsonNode *(* JsonBoxedSerializeFunc) (gconstpointer boxed); + +/** + * JsonBoxedDeserializeFunc: + * @node: a #JsonNode + * + * Deserializes the contents of the passed #JsonNode into a #GBoxed + * + * Return value: the newly created boxed type + * + * Since: 0.10 + */ +typedef gpointer (* JsonBoxedDeserializeFunc) (JsonNode *node); + +void json_boxed_register_transform_func (GType gboxed_type, + JsonNodeType node_type, + JsonBoxedSerializeFunc serialize_func, + JsonBoxedDeserializeFunc deserialize_func); +gboolean json_boxed_can_serialize (GType gboxed_type, + JsonNodeType *node_type); +gboolean json_boxed_can_deserialize (GType gboxed_type, + JsonNodeType node_type); +JsonNode *json_boxed_serialize (GType gboxed_type, + JsonNodeType node_type, + gconstpointer boxed); +gpointer json_boxed_deserialize (GType gboxed_type, + JsonNode *node); GObject *json_construct_gobject (GType gtype, const gchar *data, diff --git a/tests/Makefile.am b/tests/Makefile.am index 87f24ac..8209aa9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -6,9 +6,7 @@ NULL = noinst_PROGRAMS = $(TEST_PROGS) INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/json-glib -AM_CFLAGS = -g $(JSON_CFLAGS) -AM_CPPFLAGS = $(JSON_DEBUG_CFLAGS) - +AM_CPPFLAGS = $(JSON_DEBUG_CFLAGS) $(JSON_CFLAGS) $(MAINTAINER_CFLAGS) progs_ldadd = $(top_builddir)/json-glib/libjson-glib-1.0.la $(JSON_LIBS) TESTS_ENVIRONMENT = srcdir=$(srcdir) @@ -29,6 +27,10 @@ TEST_PROGS += test-serialize-complex test_serialize_complex_SOURCES = test-serialize-complex.c test_serialize_complex_LDADD = $(progs_ldadd) +TEST_PROGS += test-serialize-boxed +test_serialize_boxed_SOURCES = test-serialize-boxed.c +test_serialize_boxed_LDADD = $(progs_ldadd) + TEST_PROGS += test-serialize-full test_serialize_full_SOURCES = test-serialize-full.c test_serialize_full_LDADD = $(progs_ldadd) diff --git a/tests/test-serialize-boxed.c b/tests/test-serialize-boxed.c new file mode 100644 index 0000000..d96acfd --- /dev/null +++ b/tests/test-serialize-boxed.c @@ -0,0 +1,263 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <glib-object.h> + +#include <json-glib/json-glib.h> +#include <json-glib/json-gobject.h> + +#define TEST_TYPE_BOXED (test_boxed_get_type ()) +#define TEST_TYPE_OBJECT (test_object_get_type ()) +#define TEST_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_OBJECT, TestObject)) +#define TEST_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_OBJECT)) +#define TEST_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_OBJECT, TestObjectClass)) +#define TEST_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_OBJECT)) +#define TEST_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_OBJECT, TestObjectClass)) + +typedef struct _TestBoxed TestBoxed; +typedef struct _TestObject TestObject; +typedef struct _TestObjectClass TestObjectClass; + +struct _TestBoxed +{ + gint foo; + gboolean bar; +}; + +struct _TestObject +{ + GObject parent_instance; + + TestBoxed blah; +}; + +struct _TestObjectClass +{ + GObjectClass parent_class; +}; + +GType test_object_get_type (void); + +/*** implementation ***/ + +static gpointer +test_boxed_copy (gpointer src) +{ + return g_slice_dup (TestBoxed, src); +} + +static void +test_boxed_free (gpointer boxed) +{ + if (G_LIKELY (boxed != NULL)) + g_slice_free (TestBoxed, boxed); +} + +static JsonNode * +test_boxed_serialize (gconstpointer boxed) +{ + const TestBoxed *test = boxed; + JsonObject *object; + JsonNode *node; + + if (boxed == NULL) + return json_node_new (JSON_NODE_NULL); + + object = json_object_new (); + node = json_node_new (JSON_NODE_OBJECT); + + json_object_set_int_member (object, "foo", test->foo); + json_object_set_boolean_member (object, "bar", test->bar); + + json_node_take_object (node, object); + + if (g_test_verbose ()) + { + g_print ("Serialize: { foo: %" G_GINT64_FORMAT ", bar: %s }\n", + json_object_get_int_member (object, "foo"), + json_object_get_boolean_member (object, "bar") ? "true" : "false"); + } + + return node; +} + +static gpointer +test_boxed_deserialize (JsonNode *node) +{ + JsonObject *object; + TestBoxed *test; + + if (json_node_get_node_type (node) != JSON_NODE_OBJECT) + return NULL; + + object = json_node_get_object (node); + + test = g_slice_new (TestBoxed); + test->foo = json_object_get_int_member (object, "foo"); + test->bar = json_object_get_boolean_member (object, "bar"); + + if (g_test_verbose ()) + { + g_print ("Deserialize: { foo: %d, bar: %s }\n", + test->foo, + test->bar ? "true" : "false"); + } + + return test; +} + +GType +test_boxed_get_type (void) +{ + static GType b_type = 0; + + if (G_UNLIKELY (b_type == 0)) + { + b_type = g_boxed_type_register_static ("TestBoxed", + test_boxed_copy, + test_boxed_free); + + if (g_test_verbose ()) + g_print ("Registering transform functions\n"); + + json_boxed_register_transform_func (b_type, JSON_NODE_OBJECT, + test_boxed_serialize, + test_boxed_deserialize); + } + + return b_type; +} + +enum +{ + PROP_0, + + PROP_BLAH +}; + +G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT); + +static void +test_object_finalize (GObject *gobject) +{ + G_OBJECT_CLASS (test_object_parent_class)->finalize (gobject); +} + +static void +test_object_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_BLAH: + { + const TestBoxed *blah = g_value_get_boxed (value); + + TEST_OBJECT (gobject)->blah = *blah; + } + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +test_object_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_BLAH: + g_value_set_boxed (value, &(TEST_OBJECT (gobject)->blah)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +test_object_class_init (TestObjectClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = test_object_set_property; + gobject_class->get_property = test_object_get_property; + gobject_class->finalize = test_object_finalize; + + g_object_class_install_property (gobject_class, + PROP_BLAH, + g_param_spec_boxed ("blah", "Blah", "Blah", + TEST_TYPE_BOXED, + G_PARAM_READWRITE)); +} + +static void +test_object_init (TestObject *object) +{ + object->blah.foo = 0; + object->blah.bar = FALSE; +} + +static const gchar *serialize_data = +"{\n" +" \"blah\" : {\n" +" \"foo\" : 42,\n" +" \"bar\" : true\n" +" }\n" +"}"; + +static void +test_serialize_boxed (void) +{ + TestBoxed boxed = { 42, TRUE }; + GObject *obj; + gchar *data; + gsize len; + + obj = g_object_new (TEST_TYPE_OBJECT, "blah", &boxed, NULL); + + data = json_serialize_gobject (obj, &len); + + g_assert_cmpint (len, ==, strlen (serialize_data)); + g_assert_cmpstr (data, ==, serialize_data); + + if (g_test_verbose ()) + g_print ("TestObject:\n%s\n", data); + + g_free (data); + g_object_unref (obj); +} + +static void +test_deserialize_boxed (void) +{ + + GObject *obj; + + obj = json_construct_gobject (TEST_TYPE_OBJECT, serialize_data, -1, NULL); + g_assert (TEST_IS_OBJECT (obj)); + g_assert_cmpint (TEST_OBJECT (obj)->blah.foo, ==, 42); + g_assert (TEST_OBJECT (obj)->blah.bar); + + g_object_unref (obj); +} + +int +main (int argc, + char *argv[]) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/boxed/serialize-property", test_serialize_boxed); + g_test_add_func ("/boxed/deserialize-property", test_deserialize_boxed); + + return g_test_run (); +} |
