/* 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 "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include "json-gvariant.h"
#include "json-generator.h"
#include "json-parser.h"
#include "json-types-private.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
* using the following rules:
*
* ## Strings
* JSON strings map to GVariant `(s)`.
*
* ## Integers
* JSON integers map to GVariant int64 `(x)`.
*
* ## Booleans
* JSON booleans map to GVariant boolean `(b)`.
*
* ## Numbers
* JSON numbers map to GVariant double `(d)`.
*
* ## Arrays
* JSON arrays map to GVariant arrays of variants `(av)`.
*
* ## Objects
* JSON objects map to GVariant dictionaries of string to variants `(a{sv})`.
*
* ## Null values
* JSON null values map to GVariant maybe variants `(mv)`.
*/
/* custom extension to the GVariantClass enumeration to differentiate
* a single dictionary entry from an array of dictionary entries
*/
#define JSON_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)
{
JsonArray *array = user_data;
JsonNode *json_child;
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_unref (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 = JSON_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 JSON_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 (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
/* translators: the '%s' is the type name */
_("Unexpected type “%s” in JSON node"),
g_type_name (json_node_get_value_type (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 to a 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 the 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 = NULL;
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;
GQueue *members;
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,
_("A GVariant dictionary entry expects a JSON object with exactly one member"));
return NULL;
}
parse_dict_entry_signature (signature,
&entry_signature,
&key_signature,
&value_signature);
members = json_object_get_members_internal (obj);
json_member = (const gchar *) members->head->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_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;
GQueue *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_internal (obj);
for (member = members->head; member != NULL; member = member->next)
{
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;
}
}
if (! roll_back)
variant = g_variant_builder_end (builder);
g_variant_builder_unref (builder);
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;
class = json_to_gvariant_get_next_class (json_node, signature);
if (class == JSON_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);
goto out;
}
if (JSON_NODE_TYPE (json_node) == JSON_NODE_VALUE &&
json_node_get_value_type (json_node) == G_TYPE_STRING)
{
const gchar* str = json_node_get_string (json_node);
switch (class)
{
case G_VARIANT_CLASS_BOOLEAN:
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:
case G_VARIANT_CLASS_DOUBLE:
case G_VARIANT_CLASS_STRING:
variant = gvariant_simple_from_string (str, class, error);
goto out;
default:
break;
}
}
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_boolean (json_node_get_boolean (json_node));
break;
case G_VARIANT_CLASS_BYTE:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_byte (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_INT16:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_int16 (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_UINT16:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_uint16 (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_INT32:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_int32 (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_UINT32:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_uint32 (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_INT64:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_int64 (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_UINT64:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_uint64 (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_HANDLE:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_INT64, error))
variant = g_variant_new_handle (json_node_get_int (json_node));
break;
case G_VARIANT_CLASS_DOUBLE:
/* Doubles can look like ints to the json parser: when they don't have a dot */
if (JSON_NODE_TYPE (json_node) == JSON_NODE_VALUE &&
json_node_get_value_type (json_node) == G_TYPE_INT64)
variant = g_variant_new_double (json_node_get_int (json_node));
else if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_DOUBLE, error))
variant = g_variant_new_double (json_node_get_double (json_node));
break;
case G_VARIANT_CLASS_STRING:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_STRING, error))
variant = g_variant_new_string (json_node_get_string (json_node));
break;
case G_VARIANT_CLASS_OBJECT_PATH:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_STRING, error))
variant = g_variant_new_object_path (json_node_get_string (json_node));
break;
case G_VARIANT_CLASS_SIGNATURE:
if (json_node_assert_type (json_node, JSON_NODE_VALUE, G_TYPE_STRING, error))
variant = g_variant_new_signature (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;
default:
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
_("GVariant class “%c” not supported"), class);
break;
}
out:
if (signature)
(*signature)++;
return variant;
}
/**
* json_gvariant_deserialize:
* @json_node: A #JsonNode to convert
* @signature: (allow-none): 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.
*
* The returned variant has a floating reference that will need to be sunk
* by the caller code.
*
* Return value: (transfer none): A newly created, floating #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 signature"));
return NULL;
}
return json_to_gvariant_recurse (json_node, signature ? &signature : NULL, error);
}
/**
* json_gvariant_deserialize_data:
* @json: A JSON data string
* @length: The length of @json, or -1 if %NULL-terminated
* @signature: (allow-none): 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.
*
* The returned variant has a floating reference that will need to be sunk
* by the caller code.
*
* Returns: (transfer none): A newly created, floating #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;
}