diff options
| author | Eduardo Lima Mitev <elima@igalia.com> | 2010-11-09 16:45:30 +0100 |
|---|---|---|
| committer | Emmanuele Bassi <ebassi@linux.intel.com> | 2011-01-26 16:48:53 +0000 |
| commit | 212b243c07721242da3dc2c0e6dfe979f73ee5c6 (patch) | |
| tree | a6e670eda73817da40169c65dcb258bb2256a2f9 /json-glib | |
| parent | 822b6be848f3ad53ad113f0ce317b01f1a91a54f (diff) | |
| download | json-glib-212b243c07721242da3dc2c0e6dfe979f73ee5c6.tar.gz | |
gvariant: Adds JSON GVariant integration API, with docs and tests
https://bugzilla.gnome.org/show_bug.cgi?id=632940
Diffstat (limited to 'json-glib')
| -rw-r--r-- | json-glib/Makefile.am | 2 | ||||
| -rw-r--r-- | json-glib/json-glib.h | 2 | ||||
| -rw-r--r-- | json-glib/json-gvariant.c | 1296 | ||||
| -rw-r--r-- | json-glib/json-gvariant.h | 46 | ||||
| -rw-r--r-- | json-glib/tests/Makefile.am | 4 | ||||
| -rw-r--r-- | json-glib/tests/gvariant-test.c | 233 |
6 files changed, 1583 insertions, 0 deletions
diff --git a/json-glib/Makefile.am b/json-glib/Makefile.am index d5da07b..1068807 100644 --- a/json-glib/Makefile.am +++ b/json-glib/Makefile.am @@ -36,6 +36,7 @@ source_h = \ $(top_srcdir)/json-glib/json-parser.h \ $(top_srcdir)/json-glib/json-reader.h \ $(top_srcdir)/json-glib/json-types.h \ + $(top_srcdir)/json-glib/json-gvariant.h \ $(NULL) source_h_private = \ @@ -58,6 +59,7 @@ source_c = \ $(srcdir)/json-reader.c \ $(srcdir)/json-scanner.c \ $(srcdir)/json-serializable.c \ + $(srcdir)/json-gvariant.c \ $(NULL) # glib-mkenums rules diff --git a/json-glib/json-glib.h b/json-glib/json-glib.h index 7a4d783..a14416b 100644 --- a/json-glib/json-glib.h +++ b/json-glib/json-glib.h @@ -38,6 +38,8 @@ #include <json-glib/json-gobject.h> +#include <json-glib/json-gvariant.h> + #undef __JSON_GLIB_INSIDE__ #endif /* __JSON_GLIB_H__ */ diff --git a/json-glib/json-gvariant.c b/json-glib/json-gvariant.c new file mode 100644 index 0000000..9952089 --- /dev/null +++ b/json-glib/json-gvariant.c @@ -0,0 +1,1296 @@ +/* json-gvariant.c - JSON GVariant integration + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Author: + * Eduardo Lima Mitev <elima@igalia.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +#include "json-gvariant.h" + +/** + * SECTION:json-gvariant + * @short_description: Serialize and deserialize GVariant types + * @Title: JSON GVariant Integration + * + * Use json_gvariant_serialize() and json_gvariant_serialize_data() to + * convert from any #GVariant value to a #JsonNode tree or its string + * representation. + * + * Use json_gvariant_deserialize() and json_gvariant_deserialize_data() to + * obtain the #GVariant value from a #JsonNode tree or directly from a JSON + * string. + * Since many #GVariant data types cannot be directly represented as + * JSON, a #GVariant type string (signature) should be provided to these + * methods in order to obtain a correct, type-contrained result. + * If no signature is provided, conversion can still be done, but the + * resulting #GVariant value will be "guessed" from the JSON data types, + * according to the following table: + * + * <table frame='all'><title>Default JSON to GVariant conversion (without signature constrains)</title> + * <tgroup cols='2' align='left' colsep='1' rowsep='1'> + * <thead> + * <row> + * <entry>JSON</entry> + * <entry>GVariant</entry> + * </row> + * </thead> + * <tfoot> + * <row> + * <entry>string</entry> + * <entry>string (s)</entry> + * </row> + * <row> + * <entry>int64</entry> + * <entry>int64 (x)</entry> + * </row> + * <row> + * <entry>boolean</entry> + * <entry>boolean (b)</entry> + * </row> + * <row> + * <entry>double</entry> + * <entry>double (d)</entry> + * </row> + * <row> + * <entry>array</entry> + * <entry>array of variants (av)</entry> + * </row> + * <row> + * <entry>object</entry> + * <entry>dictionary of string-variant (a{sv})</entry> + * </row> + * <row> + * <entry>null</entry> + * <entry>maybe variant (mv)</entry> + * </row> + * </tfoot> + * </tgroup> + * </table> + */ + +#define G_VARIANT_CLASS_DICTIONARY 'c' + +typedef void (* GVariantForeachFunc) (GVariant *variant_child, + gpointer user_data); + +static GVariant * json_to_gvariant_recurse (JsonNode *json_node, + const gchar **signature, + GError **error); + +/* ========================================================================== */ +/* GVariant to JSON */ +/* ========================================================================== */ + +static void +gvariant_foreach (GVariant *variant, + GVariantForeachFunc func, + gpointer user_data) +{ + GVariantIter iter; + GVariant *variant_child; + + g_variant_iter_init (&iter, variant); + while ( (variant_child = g_variant_iter_next_value (&iter)) != NULL) + { + func (variant_child, user_data); + g_variant_unref (variant_child); + } +} + +static void +gvariant_to_json_array_foreach (GVariant *variant_child, + gpointer user_data) +{ + JsonNode *json_child; + JsonArray *array = (JsonArray *) user_data; + + json_child = json_gvariant_serialize (variant_child); + json_array_add_element (array, json_child); +} + +static JsonNode * +gvariant_to_json_array (GVariant *variant) +{ + JsonArray *array; + JsonNode *json_node; + + array = json_array_new (); + json_node = json_node_new (JSON_NODE_ARRAY); + json_node_set_array (json_node, array); + json_array_unref (array); + + gvariant_foreach (variant, + gvariant_to_json_array_foreach, + array); + + return json_node; +} + +static gchar * +gvariant_simple_to_string (GVariant *variant) +{ + GVariantClass class; + gchar *str; + + class = g_variant_classify (variant); + switch (class) + { + case G_VARIANT_CLASS_BOOLEAN: + if (g_variant_get_boolean (variant)) + str = g_strdup ("true"); + else + str = g_strdup ("false"); + break; + + case G_VARIANT_CLASS_BYTE: + str = g_strdup_printf ("%u", g_variant_get_byte (variant)); + break; + case G_VARIANT_CLASS_INT16: + str = g_strdup_printf ("%d", g_variant_get_int16 (variant)); + break; + case G_VARIANT_CLASS_UINT16: + str = g_strdup_printf ("%u", g_variant_get_uint16 (variant)); + break; + case G_VARIANT_CLASS_INT32: + str = g_strdup_printf ("%d", g_variant_get_int32 (variant)); + break; + case G_VARIANT_CLASS_UINT32: + str = g_strdup_printf ("%u", g_variant_get_uint32 (variant)); + break; + case G_VARIANT_CLASS_INT64: + str = g_strdup_printf ("%" G_GINT64_FORMAT, + g_variant_get_int64 (variant)); + break; + case G_VARIANT_CLASS_UINT64: + str = g_strdup_printf ("%" G_GUINT64_FORMAT, + g_variant_get_uint64 (variant)); + break; + case G_VARIANT_CLASS_HANDLE: + str = g_strdup_printf ("%d", g_variant_get_handle (variant)); + break; + + case G_VARIANT_CLASS_DOUBLE: + { + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_formatd (buf, + G_ASCII_DTOSTR_BUF_SIZE, + "%f", + g_variant_get_double (variant)); + + str = g_strdup (buf); + break; + } + + case G_VARIANT_CLASS_STRING: + case G_VARIANT_CLASS_OBJECT_PATH: + case G_VARIANT_CLASS_SIGNATURE: + str = g_strdup (g_variant_get_string (variant, NULL)); + break; + + default: + g_assert_not_reached (); + break; + } + + return str; +} + +static JsonNode * +gvariant_dict_entry_to_json (GVariant *variant, gchar **member_name) +{ + GVariant *member; + GVariant *value; + JsonNode *json_node; + + member = g_variant_get_child_value (variant, 0); + *member_name = gvariant_simple_to_string (member); + + value = g_variant_get_child_value (variant, 1); + json_node = json_gvariant_serialize (value); + + g_variant_unref (member); + g_variant_unref (value); + + return json_node; +} + +static void +gvariant_to_json_object_foreach (GVariant *variant_child, gpointer user_data) +{ + gchar *member_name; + JsonNode *json_child; + JsonObject *object = (JsonObject *) user_data; + + json_child = gvariant_dict_entry_to_json (variant_child, &member_name); + json_object_set_member (object, member_name, json_child); + g_free (member_name); +} + +static JsonNode * +gvariant_to_json_object (GVariant *variant) +{ + JsonNode *json_node; + JsonObject *object; + + json_node = json_node_new (JSON_NODE_OBJECT); + object = json_object_new (); + json_node_set_object (json_node, object); + json_object_unref (object); + + gvariant_foreach (variant, + gvariant_to_json_object_foreach, + object); + + return json_node; +} + +/** + * json_gvariant_serialize: + * @variant: A #GVariant to convert + * + * Converts @variant to a JSON tree. + * + * Return value: (transfer full): A #JsonNode representing the root of the + * JSON data structure obtained from @variant + * + * Since: 0.14 + */ +JsonNode * +json_gvariant_serialize (GVariant *variant) +{ + JsonNode *json_node = NULL; + GVariantClass class; + + g_return_val_if_fail (variant != NULL, NULL); + + class = g_variant_classify (variant); + + if (! g_variant_is_container (variant)) + { + json_node = json_node_new (JSON_NODE_VALUE); + + switch (class) + { + case G_VARIANT_CLASS_BOOLEAN: + json_node_set_boolean (json_node, g_variant_get_boolean (variant)); + break; + + case G_VARIANT_CLASS_BYTE: + json_node_set_int (json_node, g_variant_get_byte (variant)); + break; + case G_VARIANT_CLASS_INT16: + json_node_set_int (json_node, g_variant_get_int16 (variant)); + break; + case G_VARIANT_CLASS_UINT16: + json_node_set_int (json_node, g_variant_get_uint16 (variant)); + break; + case G_VARIANT_CLASS_INT32: + json_node_set_int (json_node, g_variant_get_int32 (variant)); + break; + case G_VARIANT_CLASS_UINT32: + json_node_set_int (json_node, g_variant_get_uint32 (variant)); + break; + case G_VARIANT_CLASS_INT64: + json_node_set_int (json_node, g_variant_get_int64 (variant)); + break; + case G_VARIANT_CLASS_UINT64: + json_node_set_int (json_node, g_variant_get_uint64 (variant)); + break; + case G_VARIANT_CLASS_HANDLE: + json_node_set_int (json_node, g_variant_get_handle (variant)); + break; + + case G_VARIANT_CLASS_DOUBLE: + json_node_set_double (json_node, g_variant_get_double (variant)); + break; + + case G_VARIANT_CLASS_STRING: + case G_VARIANT_CLASS_OBJECT_PATH: + case G_VARIANT_CLASS_SIGNATURE: + json_node_set_string (json_node, g_variant_get_string (variant, NULL)); + break; + + default: + break; + } + } + else + { + switch (class) + { + case G_VARIANT_CLASS_MAYBE: + { + GVariant *value; + + value = g_variant_get_maybe (variant); + if (value == NULL) + { + json_node = json_node_new (JSON_NODE_NULL); + } + else + { + json_node = json_gvariant_serialize (value); + g_variant_unref (value); + } + + break; + } + + case G_VARIANT_CLASS_VARIANT: + { + GVariant *value; + + value = g_variant_get_variant (variant); + json_node = json_gvariant_serialize (value); + g_variant_unref (value); + + break; + } + + case G_VARIANT_CLASS_ARRAY: + { + const gchar *type; + + type = g_variant_get_type_string (variant); + + if (type[1] == G_VARIANT_CLASS_DICT_ENTRY) + /* array of dictionary entries => JsonObject */ + json_node = gvariant_to_json_object (variant); + else + /* array of anything else => JsonArray */ + json_node = gvariant_to_json_array (variant); + + break; + } + + case G_VARIANT_CLASS_DICT_ENTRY: + { + gchar *member_name; + JsonObject *object; + JsonNode *child; + + /* a single dictionary entry => JsonObject */ + json_node = json_node_new (JSON_NODE_OBJECT); + object = json_object_new (); + json_node_set_object (json_node, object); + json_object_unref (object); + + child = gvariant_dict_entry_to_json (variant, &member_name); + + json_object_set_member (object, member_name, child); + g_free (member_name); + + break; + } + + case G_VARIANT_CLASS_TUPLE: + json_node = gvariant_to_json_array (variant); + break; + + default: + break; + } + } + + return json_node; +} + +/** + * json_gvariant_serialize_data: + * @variant: A #GVariant to convert + * @length: (out) (allow-none): Return location for the length of the returned + * string, or %NULL + * + * Converts @variant to its JSON encoded string representation. This method + * is actually a helper function. It uses json_gvariant_serialize() to obtain the + * JSON tree, and then #JsonGenerator to stringify it. + * + * Return value: (transfer full): The JSON encoded string corresponding to + * @variant + * + * Since: 0.14 + */ +gchar * +json_gvariant_serialize_data (GVariant *variant, gsize *length) +{ + JsonNode *json_node; + JsonGenerator *generator; + gchar *json; + + json_node = json_gvariant_serialize (variant); + + generator = json_generator_new (); + + json_generator_set_root (generator, json_node); + json = json_generator_to_data (generator, length); + + g_object_unref (generator); + + json_node_free (json_node); + + return json; +} + +/* ========================================================================== */ +/* JSON to GVariant */ +/* ========================================================================== */ + +static GVariantClass +json_to_gvariant_get_next_class (JsonNode *json_node, + const gchar **signature) +{ + if (signature == NULL) + { + GVariantClass class = 0; + + switch (json_node_get_node_type (json_node)) + { + case JSON_NODE_VALUE: + switch (json_node_get_value_type (json_node)) + { + case G_TYPE_BOOLEAN: + class = G_VARIANT_CLASS_BOOLEAN; + break; + + case G_TYPE_INT64: + class = G_VARIANT_CLASS_INT64; + break; + + case G_TYPE_DOUBLE: + class = G_VARIANT_CLASS_DOUBLE; + break; + + case G_TYPE_STRING: + class = G_VARIANT_CLASS_STRING; + break; + } + + break; + + case JSON_NODE_ARRAY: + class = G_VARIANT_CLASS_ARRAY; + break; + + case JSON_NODE_OBJECT: + class = G_VARIANT_CLASS_DICTIONARY; + break; + + case JSON_NODE_NULL: + class = G_VARIANT_CLASS_MAYBE; + break; + } + + return class; + } + else + { + if ( (*signature)[0] == G_VARIANT_CLASS_ARRAY + && (*signature)[1] == G_VARIANT_CLASS_DICT_ENTRY) + return G_VARIANT_CLASS_DICTIONARY; + else + return (*signature)[0]; + } +} + +static gboolean +json_node_assert_type (JsonNode *json_node, + JsonNodeType type, + GType sub_type, + GError **error) +{ + if (JSON_NODE_TYPE (json_node) != type + || (type == JSON_NODE_VALUE + && (json_node_get_value_type (json_node) != sub_type) ) ) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Unexpected data-type in JSON node"); + return FALSE; + } + else + { + return TRUE; + } +} + +static void +json_to_gvariant_foreach_add (gpointer data, gpointer user_data) +{ + GVariantBuilder *builder = (GVariantBuilder *) user_data; + GVariant *child = (GVariant *) data; + + g_variant_builder_add_value (builder, child); +} + +static void +json_to_gvariant_foreach_free (gpointer data, gpointer user_data) +{ + GVariant *child = (GVariant *) data; + + g_variant_unref (child); +} + +static GVariant * +json_to_gvariant_build_from_glist (GList *list, const gchar *signature) +{ + GVariantBuilder *builder; + GVariant *result; + + builder = g_variant_builder_new (G_VARIANT_TYPE (signature)); + + g_list_foreach (list, json_to_gvariant_foreach_add, builder); + result = g_variant_builder_end (builder); + + g_variant_builder_unref (builder); + + return result; +} + +static GVariant * +json_to_gvariant_tuple (JsonNode *json_node, + const gchar **signature, + GError **error) +{ + GVariant *variant = NULL; + JsonArray *array; + gint i; + GList *children = NULL; + gboolean roll_back = FALSE; + const gchar *initial_signature; + + array = json_node_get_array (json_node); + + initial_signature = *signature; + (*signature)++; + i = 1; + while ((*signature)[0] != ')' && (*signature)[0] != '\0') + { + JsonNode *json_child; + GVariant *variant_child; + + if (i - 1 >= json_array_get_length (array)) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Missing elements in JSON array to conform tuple"); + roll_back = TRUE; + break; + } + + json_child = json_array_get_element (array, i - 1); + + variant_child = json_to_gvariant_recurse (json_child, signature, error); + if (variant_child != NULL) + { + children = g_list_append (children, variant_child); + } + else + { + roll_back = TRUE; + break; + } + + i++; + } + + if (! roll_back) + { + if ( (*signature)[0] != ')') + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Missing closing symbol ')' in GVariant tuple type"); + roll_back = TRUE; + } + else if (json_array_get_length (array) >= i) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Unexpected extra elements in JSON array"); + roll_back = TRUE; + } + else + { + gchar *tuple_type; + + tuple_type = g_strndup (initial_signature, + (*signature) - initial_signature + 1); + + variant = json_to_gvariant_build_from_glist (children, tuple_type); + + g_free (tuple_type); + } + } + + if (roll_back) + g_list_foreach (children, json_to_gvariant_foreach_free, NULL); + + g_list_free (children); + + return variant; +} + +static gchar * +signature_get_next_complete_type (const gchar **signature) +{ + GVariantClass class; + const gchar *initial_signature; + gchar *result; + + /* here it is assumed that 'signature' is a valid type string */ + + initial_signature = *signature; + class = (*signature)[0]; + + if (class == G_VARIANT_CLASS_TUPLE || class == G_VARIANT_CLASS_DICT_ENTRY) + { + gchar stack[256] = {0}; + guint stack_len = 0; + + do + { + if ( (*signature)[0] == G_VARIANT_CLASS_TUPLE) + { + stack[stack_len] = ')'; + stack_len++; + } + else if ( (*signature)[0] == G_VARIANT_CLASS_DICT_ENTRY) + { + stack[stack_len] = '}'; + stack_len++; + } + + (*signature)++; + + if ( (*signature)[0] == stack[stack_len - 1]) + stack_len--; + } + while (stack_len > 0); + + (*signature)++; + } + else if (class == G_VARIANT_CLASS_ARRAY || class == G_VARIANT_CLASS_MAYBE) + { + gchar *tmp_sig; + + (*signature)++; + tmp_sig = signature_get_next_complete_type (signature); + g_free (tmp_sig); + } + else + { + (*signature)++; + } + + result = g_strndup (initial_signature, (*signature) - initial_signature); + + return result; +} + +static GVariant * +json_to_gvariant_maybe (JsonNode *json_node, + const gchar **signature, + GError **error) +{ + GVariant *variant = NULL; + GVariant *value; + gchar *maybe_signature; + + if (signature) + { + (*signature)++; + maybe_signature = signature_get_next_complete_type (signature); + } + else + { + maybe_signature = g_strdup ("v"); + } + + if (json_node_get_node_type (json_node) == JSON_NODE_NULL) + { + variant = g_variant_new_maybe (G_VARIANT_TYPE (maybe_signature), NULL); + } + else + { + const gchar *tmp_signature; + + tmp_signature = maybe_signature; + value = json_to_gvariant_recurse (json_node, + &tmp_signature, + error); + + if (value != NULL) + variant = g_variant_new_maybe (G_VARIANT_TYPE (maybe_signature), value); + } + + g_free (maybe_signature); + + /* compensate the (*signature)++ call at the end of 'recurse()' */ + if (signature) + (*signature)--; + + return variant; +} + +static GVariant * +json_to_gvariant_array (JsonNode *json_node, + const gchar **signature, + GError **error) +{ + GVariant *variant = NULL; + JsonArray *array; + GList *children = NULL; + gboolean roll_back = FALSE; + const gchar *orig_signature; + gchar *child_signature; + + array = json_node_get_array (json_node); + + if (signature != NULL) + { + orig_signature = *signature; + + (*signature)++; + child_signature = signature_get_next_complete_type (signature); + } + else + { + child_signature = g_strdup ("v"); + } + + if (json_array_get_length (array) > 0) + { + gint i; + guint len; + + len = json_array_get_length (array); + for (i=0; i<len; i++) + { + JsonNode *json_child; + GVariant *variant_child; + const gchar *tmp_signature; + + json_child = json_array_get_element (array, i); + + tmp_signature = child_signature; + variant_child = json_to_gvariant_recurse (json_child, + &tmp_signature, + error); + if (variant_child != NULL) + { + children = g_list_append (children, variant_child); + } + else + { + roll_back = TRUE; + break; + } + } + } + + if (! roll_back) + { + gchar *array_signature; + + if (signature) + array_signature = g_strndup (orig_signature, (*signature) - orig_signature); + else + array_signature = g_strdup ("av"); + + variant = json_to_gvariant_build_from_glist (children, array_signature); + + g_free (array_signature); + + /* compensate the (*signature)++ call at the end of 'recurse()' */ + if (signature) + (*signature)--; + } + else + { + g_list_foreach (children, json_to_gvariant_foreach_free, NULL); + } + + g_list_free (children); + g_free (child_signature); + + return variant; +} + +static GVariant * +gvariant_simple_from_string (const gchar *st, + GVariantClass class, + GError **error) +{ + GVariant *variant = NULL; + gchar *nptr = NULL; + + errno = 0; + + switch (class) + { + case G_VARIANT_CLASS_BOOLEAN: + if (g_strcmp0 (st, "true") == 0) + variant = g_variant_new_boolean (TRUE); + else if (g_strcmp0 (st, "false") == 0) + variant = g_variant_new_boolean (FALSE); + else + errno = 1; + break; + + case G_VARIANT_CLASS_BYTE: + variant = g_variant_new_byte (g_ascii_strtoll (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_INT16: + variant = g_variant_new_int16 (g_ascii_strtoll (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_UINT16: + variant = g_variant_new_uint16 (g_ascii_strtoll (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_INT32: + variant = g_variant_new_int32 (g_ascii_strtoll (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_UINT32: + variant = g_variant_new_uint32 (g_ascii_strtoull (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_INT64: + variant = g_variant_new_int64 (g_ascii_strtoll (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_UINT64: + variant = g_variant_new_uint64 (g_ascii_strtoull (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_HANDLE: + variant = g_variant_new_handle (strtol (st, &nptr, 10)); + break; + + case G_VARIANT_CLASS_DOUBLE: + variant = g_variant_new_double (g_ascii_strtod (st, &nptr)); + break; + + case G_VARIANT_CLASS_STRING: + case G_VARIANT_CLASS_OBJECT_PATH: + case G_VARIANT_CLASS_SIGNATURE: + variant = g_variant_new_string (st); + break; + + default: + g_assert_not_reached (); + break; + } + + if (errno != 0 || nptr == st) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "Invalid string value converting to GVariant"); + if (variant != NULL) + { + g_variant_unref (variant); + variant = NULL; + } + } + + return variant; +} + +static void +parse_dict_entry_signature (const gchar **signature, + gchar **entry_signature, + gchar **key_signature, + gchar **value_signature) +{ + const gchar *tmp_sig; + + if (signature != NULL) + *entry_signature = signature_get_next_complete_type (signature); + else + *entry_signature = g_strdup ("{sv}"); + + tmp_sig = (*entry_signature) + 1; + *key_signature = signature_get_next_complete_type (&tmp_sig); + *value_signature = signature_get_next_complete_type (&tmp_sig); +} + +static GVariant * +json_to_gvariant_dict_entry (JsonNode *json_node, + const gchar **signature, + GError **error) +{ + GVariant *variant = NULL; + JsonObject *obj; + + gchar *entry_signature; + gchar *key_signature; + gchar *value_signature; + const gchar *tmp_signature; + + GList *member; + + const gchar *json_member; + JsonNode *json_value; + GVariant *variant_member; + GVariant *variant_value; + + obj = json_node_get_object (json_node); + + if (json_object_get_size (obj) != 1) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "GVariant dictionary entry expects JSON object with exactly one member"); + return NULL; + } + + parse_dict_entry_signature (signature, + &entry_signature, + &key_signature, + &value_signature); + + member = json_object_get_members (obj); + + json_member = (const gchar *) member->data; + variant_member = gvariant_simple_from_string (json_member, + key_signature[0], + error); + if (variant_member != NULL) + { + json_value = json_object_get_member (obj, json_member); + + tmp_signature = value_signature; + variant_value = json_to_gvariant_recurse (json_value, + &tmp_signature, + error); + + if (variant_value != NULL) + { + GVariantBuilder *builder; + + builder = g_variant_builder_new (G_VARIANT_TYPE (entry_signature)); + g_variant_builder_add_value (builder, variant_member); + g_variant_builder_add_value (builder, variant_value); + variant = g_variant_builder_end (builder); + + g_variant_builder_unref (builder); + } + } + + g_list_free (member); + g_free (value_signature); + g_free (key_signature); + g_free (entry_signature); + + /* compensate the (*signature)++ call at the end of 'recurse()' */ + if (signature) + (*signature)--; + + return variant; +} + +static GVariant * +json_to_gvariant_dictionary (JsonNode *json_node, + const gchar **signature, + GError **error) +{ + GVariant *variant = NULL; + JsonObject *obj; + gboolean roll_back = FALSE; + + gchar *dict_signature; + gchar *entry_signature; + gchar *key_signature; + gchar *value_signature; + const gchar *tmp_signature; + + GVariantBuilder *builder; + GList *members; + GList *member; + + obj = json_node_get_object (json_node); + + if (signature != NULL) + (*signature)++; + + parse_dict_entry_signature (signature, + &entry_signature, + &key_signature, + &value_signature); + + dict_signature = g_strdup_printf ("a%s", entry_signature); + + builder = g_variant_builder_new (G_VARIANT_TYPE (dict_signature)); + + members = json_object_get_members (obj); + + member = members; + while (member != NULL) + { + const gchar *json_member; + JsonNode *json_value; + GVariant *variant_member; + GVariant *variant_value; + + json_member = (const gchar *) member->data; + variant_member = gvariant_simple_from_string (json_member, + key_signature[0], + error); + if (variant_member == NULL) + { + roll_back = TRUE; + break; + } + + json_value = json_object_get_member (obj, json_member); + + tmp_signature = value_signature; + variant_value = json_to_gvariant_recurse (json_value, + &tmp_signature, + error); + + if (variant_value != NULL) + { + g_variant_builder_open (builder, G_VARIANT_TYPE (entry_signature)); + g_variant_builder_add_value (builder, variant_member); + g_variant_builder_add_value (builder, variant_value); + g_variant_builder_close (builder); + } + else + { + roll_back = TRUE; + break; + } + + member = member->next; + } + + if (! roll_back) + variant = g_variant_builder_end (builder); + + g_variant_builder_unref (builder); + g_list_free (members); + g_free (value_signature); + g_free (key_signature); + g_free (entry_signature); + g_free (dict_signature); + + /* compensate the (*signature)++ call at the end of 'recurse()' */ + if (signature != NULL) + (*signature)--; + + return variant; +} + +static GVariant * +json_to_gvariant_recurse (JsonNode *json_node, + const gchar **signature, + GError **error) +{ + GVariant *variant = NULL; + GVariantClass class; + gchar class_type[2] = {0, 0}; + + class = json_to_gvariant_get_next_class (json_node, signature); + class_type[0] = class; + + switch (class) + { + case G_VARIANT_CLASS_BOOLEAN: + if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_BOOLEAN, error)) + variant = g_variant_new (class_type, json_node_get_boolean (json_node)); + break; + + case G_VARIANT_CLASS_BYTE: + case G_VARIANT_CLASS_INT16: + case G_VARIANT_CLASS_UINT16: + case G_VARIANT_CLASS_INT32: + case G_VARIANT_CLASS_UINT32: + case G_VARIANT_CLASS_INT64: + case G_VARIANT_CLASS_UINT64: + case G_VARIANT_CLASS_HANDLE: + if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error)) + variant = g_variant_new (class_type, json_node_get_int (json_node)); + break; + + case G_VARIANT_CLASS_DOUBLE: + if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_DOUBLE, error)) + variant = g_variant_new (class_type, json_node_get_double (json_node)); + break; + + case G_VARIANT_CLASS_STRING: + case G_VARIANT_CLASS_OBJECT_PATH: + case G_VARIANT_CLASS_SIGNATURE: + if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_STRING, error)) + variant = g_variant_new (class_type, json_node_get_string (json_node)); + break; + + case G_VARIANT_CLASS_VARIANT: + variant = g_variant_new_variant (json_to_gvariant_recurse (json_node, + NULL, + error)); + break; + + case G_VARIANT_CLASS_MAYBE: + variant = json_to_gvariant_maybe (json_node, signature, error); + break; + + case G_VARIANT_CLASS_ARRAY: + if (json_node_assert_type (json_node, JSON_NODE_ARRAY, 0, error)) + variant = json_to_gvariant_array (json_node, signature, error); + break; + + case G_VARIANT_CLASS_TUPLE: + if (json_node_assert_type (json_node, JSON_NODE_ARRAY, 0, error)) + variant = json_to_gvariant_tuple (json_node, signature, error); + break; + + case G_VARIANT_CLASS_DICT_ENTRY: + if (json_node_assert_type (json_node, JSON_NODE_OBJECT, 0, error)) + variant = json_to_gvariant_dict_entry (json_node, signature, error); + break; + + case G_VARIANT_CLASS_DICTIONARY: + if (json_node_assert_type (json_node, JSON_NODE_OBJECT, 0, error)) + variant = json_to_gvariant_dictionary (json_node, signature, error); + break; + + default: + { + gchar *err_msg; + + err_msg = g_strdup_printf ("GVariant class '%c' not supported", class); + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + err_msg); + g_free (err_msg); + break; + } + } + + if (signature) + (*signature)++; + + return variant; +} + +/** + * json_gvariant_deserialize: + * @json_node: A #JsonNode to convert + * @signature: A valid #GVariant type string, or %NULL + * @error: A pointer to a #GError + * + * Converts a JSON data structure to a GVariant value using @signature to + * resolve ambiguous data types. If no error occurs, the resulting #GVariant + * is guaranteed to conform to @signature. + * + * If @signature is not %NULL but does not represent a valid GVariant type + * string, %NULL is returned and error is set to %G_IO_ERROR_INVALID_ARGUMENT. + * If a @signature is provided but the JSON structure cannot be mapped to it, + * %NULL is returned and error is set to %G_IO_ERROR_INVALID_DATA. + * If @signature is %NULL, the conversion is done based strictly on the types + * in the JSON nodes. + * + * Return value: (transfer full): A newly created #GVariant compliant with + * @signature, or %NULL on error + * + * Since: 0.14 + */ +GVariant * +json_gvariant_deserialize (JsonNode *json_node, + const gchar *signature, + GError **error) +{ + g_return_val_if_fail (json_node != NULL, NULL); + + if (signature != NULL && ! g_variant_type_string_is_valid (signature)) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Invalid GVariant type string"); + return NULL; + } + else + { + return json_to_gvariant_recurse (json_node, &signature, error); + } +} + +/** + * json_gvariant_deserialize_data: + * @json: A JSON data string + * @length: The length of @json, or -1 if %NULL-terminated + * @signature: A valid #GVariant type string, or %NULL + * @error: A pointer to a #GError + * + * Converts a JSON string to a #GVariant value. This method works exactly + * like json_gvariant_deserialize(), but takes a JSON encoded string instead. + * The string is first converted to a #JsonNode using #JsonParser, and then + * json_gvariant_deserialize() is called. + * + * Returns: (transfer full): A newly created #GVariant compliant with + * @signature, or %NULL on error + * + * Since: 0.14 + */ +GVariant * +json_gvariant_deserialize_data (const gchar *json, + gssize length, + const gchar *signature, + GError **error) +{ + JsonParser *parser; + GVariant *variant = NULL; + JsonNode *root; + + parser = json_parser_new (); + + if (! json_parser_load_from_data (parser, json, length, error)) + return NULL; + + root = json_parser_get_root (parser); + if (root == NULL) + { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "JSON data is empty"); + } + else + { + variant = + json_gvariant_deserialize (json_parser_get_root (parser), signature, error); + } + + g_object_unref (parser); + + return variant; +} diff --git a/json-glib/json-gvariant.h b/json-glib/json-gvariant.h new file mode 100644 index 0000000..6644b21 --- /dev/null +++ b/json-glib/json-gvariant.h @@ -0,0 +1,46 @@ +/* json-gvariant.h - JSON GVariant integration + * + * This file is part of JSON-GLib + * Copyright (C) 2007 OpenedHand Ltd. + * Copyright (C) 2009 Intel Corp. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Eduardo Lima Mitev <elima@igalia.com> + */ + +#ifndef __JSON_GVARIANT_H__ +#define __JSON_GVARIANT_H__ + +#include <glib.h> +#include <json-glib/json-glib.h> + +G_BEGIN_DECLS + +JsonNode * json_gvariant_serialize (GVariant *variant); +gchar * json_gvariant_serialize_data (GVariant *variant, + gsize *length); + +GVariant * json_gvariant_deserialize (JsonNode *json_node, + const gchar *signature, + GError **error); +GVariant * json_gvariant_deserialize_data (const gchar *json, + gssize length, + const gchar *signature, + GError **error); + +G_END_DECLS + +#endif /* __JSON_GVARIANT_H__ */ diff --git a/json-glib/tests/Makefile.am b/json-glib/tests/Makefile.am index ad87bcf..defcd2f 100644 --- a/json-glib/tests/Makefile.am +++ b/json-glib/tests/Makefile.am @@ -47,4 +47,8 @@ TEST_PROGS += reader-test reader_test_SOURCES = reader-test.c reader_test_LDADD = $(progs_ldadd) +TEST_PROGS += gvariant-test +gvariant_test_SOURCES = gvariant-test.c +gvariant_test_LDADD = $(progs_ldadd) + -include $(top_srcdir)/build/autotools/Makefile.am.gitignore diff --git a/json-glib/tests/gvariant-test.c b/json-glib/tests/gvariant-test.c new file mode 100644 index 0000000..e3678cc --- /dev/null +++ b/json-glib/tests/gvariant-test.c @@ -0,0 +1,233 @@ +#include <glib/gtestutils.h> +#include <json-glib/json-glib.h> +#include <string.h> + +typedef struct +{ + gchar *test_name; + gchar *signature; + gchar *variant_data; + gchar *json_data; +} TestCase; + +/* each entry in this list spawns to a GVariant-to-JSON and + JSON-to-GVariant test */ +const TestCase test_cases[] = + { + /* boolean */ + { "/boolean", "(b)", "(true,)", "[ true ]" }, + + /* byte */ + { "/byte", "(y)", "(byte 0xff,)", "[ 255 ]" }, + + /* int16 */ + { "/int16", "(n)", "(int16 -12345,)", "[ -12345 ]" }, + + /* uint16 */ + { "/uint16", "(q)", "(uint16 40001,)", "[ 40001 ]" }, + + /* int32 */ + { "/int32", "(i)", "(-7654321,)", "[ -7654321 ]" }, + + /* uint32 */ + { "/uint32", "(u)", "(uint32 12345678,)", "[ 12345678 ]" }, + + /* int64 */ + { "/int64", "(x)", "(int64 -666999666999,)", "[ -666999666999 ]" }, + + /* uint64 */ + { "/uint64", "(t)", "(uint64 1999999999999999,)", "[ 1999999999999999 ]" }, + + /* handle */ + { "/handle", "(h)", "(handle 1,)", "[ 1 ]" }, + + /* double */ + { "/double", "(d)", "(1.23,)", "[ 1.23 ]" }, + + /* string */ + { "/string", "(s)", "('hello world!',)", "[ \"hello world!\" ]" }, + + /* object-path */ + { "/object-path", "(o)", "(objectpath '/org/gtk/json_glib',)", "[ \"/org/gtk/json_glib\" ]" }, + + /* signature */ + { "/signature", "(g)", "(signature '(asna{sv}i)',)", "[ \"(asna{sv}i)\" ]" }, + + /* maybe - null string */ + { "/maybe/simple/null", "(ms)", "(@ms nothing,)", "[ null ]" }, + + /* maybe - simple string */ + { "/maybe/simple/string", "(ms)", "(@ms 'maybe string',)", "[ \"maybe string\" ]" }, + + /* maybe - null container */ + { "/maybe/container/null", "(m(sn))", "(@m(sn) nothing,)", "[ null ]" }, + + /* maybe - tuple container */ + { "/maybe/container/tuple", "(m(sn))", "(@m(sn) ('foo', 0),)", "[ [ \"foo\", 0 ] ]" }, + + /* maybe - variant boolean */ + { "/maybe/variant/boolean", "(mv)", "(@mv <true>,)", "[ true ]" }, + + /* empty array */ + { "/array/empty", "as", "@as []", "[ ]" }, + + /* array of bytes */ + { "/array/byte", "ay", "[byte 0x01, 0x0a, 0x03, 0xff]", "[ 1, 10, 3, 255 ]" }, + + /* array of strings */ + { "/array/string", "as", "['a', 'b', 'ab', 'ba']", "[ \"a\", \"b\", \"ab\", \"ba\" ]" }, + + /* array of array of int32 */ + { "/array/array/int32", "aai", "[[1, 2], [3, 4], [5, 6]]", "[ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ]" }, + + /* array of variants */ + { "/array/variant", "av", "[<true>, <int64 1>, <'oops'>, <int64 -2>, <0.5>]", "[ true, 1, \"oops\", -2, 0.5 ]" }, + + /* tuple */ + { "/tuple", "(bynqiuxthds)", + "(false, byte 0x00, int16 -1, uint16 1, -2, uint32 2, int64 429496729, uint64 3, handle 16, 2.48, 'end')", + "[ false, 0, -1, 1, -2, 2, 429496729, 3, 16, 2.48, \"end\" ]" }, + + /* empty dictionary */ + { "/dictionary/empty", "a{sv}", "@a{sv} {}", "{ }" }, + + /* single dictionary entry */ + { "/dictionary/single-entry", "{ss}", "{'hello', 'world'}", "{ \"hello\" : \"world\" }" }, + + /* dictionary - string : int32 */ + { "/dictionary/string-int32", "a{si}", "{'foo': 1, 'bar': 2}", "{ \"foo\" : 1, \"bar\" : 2 }" }, + + /* dictionary - string : variant */ + { "/dictionary/string-variant", "a{sv}", "{'str': <'hi!'>, 'bool': <true>}", "{ \"str\" : \"hi!\", \"bool\" : true }" }, + + /* dictionary - int64 : variant */ + { "/dictionary/int64-variant", "a{xv}", + "{int64 -5: <'minus five'>, 10: <'ten'>}", "{ \"-5\" : \"minus five\", \"10\" : \"ten\" }" }, + + /* dictionary - uint64 : variant */ + { "/dictionary/uint64-boolean", "a{tb}", + "{uint64 999888777666: true, 0: false}", "{ \"999888777666\" : true, \"0\" : false }" }, + + /* dictionary - boolean : variant */ + { "/dictionary/boolean-variant", "a{by}", "{true: byte 0x01, false: 0x00}", "{ \"true\" : 1, \"false\" : 0 }" }, + + /* dictionary - double : string */ + { "/dictionary/double-string", "a{ds}", "{1.0: 'one point zero'}", "{ \"1.000000\" : \"one point zero\" }" }, + + /* variant - string */ + { "/variant/string", "(v)", "(<'string within variant'>,)", "[ \"string within variant\" ]" }, + + /* variant - maybe null */ + { "/variant/maybe/null", "(v)", "(<@mv nothing>,)", "[ null ]" }, + + /* variant - dictionary */ + { "/variant/dictionary", "v", "<{'foo': <'bar'>, 'hi': <int64 1024>}>", "{ \"foo\" : \"bar\", \"hi\" : 1024 }" }, + + /* variant - variant - array */ + { "/variant/variant/array", "v", "<[<'any'>, <'thing'>, <int64 0>, <int64 -1>]>", "[ \"any\", \"thing\", 0, -1 ]" }, + + /* deep-nesting */ + { "/deep-nesting", + "a(a(a(a(a(a(a(a(a(a(s))))))))))", + "[([([([([([([([([([('sorprise',)],)],)],)],)],)],)],)],)],)]", + "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ \"sorprise\" ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" }, + + /* mixed1 */ + { "/mixed1", + "a{s(syba(od))}", + "{'foo': ('bar', byte 0x64, true, [(objectpath '/baz', 1.3), ('/cat', -2.5)])}", + "{ \"foo\" : [ \"bar\", 100, true, [ [ \"/baz\", 1.3 ], [ \"/cat\", -2.5 ] ] ] }" }, + + /* mixed2 */ + { "/mixed2", + "(a{by}amsvmaba{qm(sg)})", + "({true: byte 0x01, false: 0x00}, [@ms 'do', nothing, 'did'], <@av []>, @mab nothing, {uint16 10000: @m(sg) ('yes', 'august'), 0: nothing})", + "[ { \"true\" : 1, \"false\" : 0 }, [ \"do\", null, \"did\" ], [ ], null, { \"10000\" : [ \"yes\", \"august\" ], \"0\" : null } ]" }, + }; + +static void +test_gvariant_to_json (gconstpointer test_data) +{ + TestCase *test_case = (TestCase *) test_data; + GVariant *variant; + gchar *json_data; + gsize len; + + variant = g_variant_parse (G_VARIANT_TYPE (test_case->signature), + test_case->variant_data, + NULL, + NULL, + NULL); + + json_data = json_gvariant_serialize_data (variant, &len); + g_assert (json_data != NULL); + + g_assert_cmpstr (test_case->json_data, ==, json_data); + + g_free (json_data); + g_variant_unref (variant); +} + +static void +test_json_to_gvariant (gconstpointer test_data) +{ + TestCase *test_case = (TestCase *) test_data; + GVariant *variant; + gchar *variant_data; + GError *error = NULL; + + variant = json_gvariant_deserialize_data (test_case->json_data, + -1, + test_case->signature, + &error); + + if (variant == NULL) + { + g_assert_no_error (error); + g_error_free (error); + } + else + { + variant_data = g_variant_print (variant, TRUE); + + g_assert_cmpstr (test_case->variant_data, ==, variant_data); + + g_free (variant_data); + g_variant_unref (variant); + } +} + +gint +main (gint argc, gchar *argv[]) +{ + gint i; + TestCase test_case; + gchar *test_name; + + g_type_init (); + g_test_init (&argc, &argv, NULL); + + /* GVariant to JSON */ + for (i = 0; i < sizeof (test_cases) / sizeof (TestCase); i++) + { + test_case = test_cases[i]; + test_name = g_strdup_printf ("/gvariant/to-json/%s", test_case.test_name); + + g_test_add_data_func (test_name, &test_cases[i], test_gvariant_to_json); + + g_free (test_name); + } + + /* JSON to GVariant */ + for (i = 0; i < sizeof (test_cases) / sizeof (TestCase); i++) + { + test_case = test_cases[i]; + test_name = g_strdup_printf ("/gvariant/from-json/%s", test_case.test_name); + + g_test_add_data_func (test_name, &test_cases[i], test_json_to_gvariant); + + g_free (test_name); + } + + return g_test_run (); +} |
