/* json-generator.c - JSON streams generator * * 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 . * * Author: * Emmanuele Bassi */ /** * SECTION:json-generator * @short_description: Generates JSON data streams * * #JsonGenerator provides an object for generating a JSON data stream and * put it into a buffer or a file. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "json-types-private.h" #include "json-marshal.h" #include "json-generator.h" #define JSON_GENERATOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), JSON_TYPE_GENERATOR, JsonGeneratorPrivate)) struct _JsonGeneratorPrivate { JsonNode *root; guint indent; gunichar indent_char; gboolean pretty; }; enum { PROP_INVALID, PRETTY, INDENT, ROOT, INDENT_CHAR, LAST_PROP }; static gchar *dump_value (JsonGenerator *generator, gint level, const gchar *name, JsonNode *node, gsize *length); static gchar *dump_array (JsonGenerator *generator, gint level, const gchar *name, JsonArray *array, gsize *length); static gchar *dump_object (JsonGenerator *generator, gint level, const gchar *name, JsonObject *object, gsize *length); /* non-ASCII characters can't be escaped, otherwise UTF-8 * chars will break, so we just pregenerate this table of * high characters and then we feed it to g_strescape() */ static const char json_exceptions[] = { 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, '\0' /* g_strescape() expects a NUL-terminated string */ }; static GParamSpec *generator_props[LAST_PROP] = { NULL, }; G_DEFINE_TYPE (JsonGenerator, json_generator, G_TYPE_OBJECT); static gchar * json_strescape (const gchar *str) { return g_strescape (str, json_exceptions); } static void json_generator_finalize (GObject *gobject) { JsonGeneratorPrivate *priv = JSON_GENERATOR_GET_PRIVATE (gobject); if (priv->root) json_node_free (priv->root); G_OBJECT_CLASS (json_generator_parent_class)->finalize (gobject); } static void json_generator_class_init (JsonGeneratorClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (JsonGeneratorPrivate)); /** * JsonGenerator:pretty: * * Whether the output should be "pretty-printed", with indentation and * newlines. The indentation level can be controlled by using the * JsonGenerator:indent property */ generator_props[PRETTY] = g_boolean_property_new ("pretty", G_PROPERTY_READWRITE, G_STRUCT_OFFSET (JsonGeneratorPrivate, pretty), NULL, NULL); /** * JsonGenerator:indent: * * Number of spaces to be used to indent when pretty printing. */ generator_props[INDENT] = g_uint_property_new ("indent", G_PROPERTY_READWRITE, G_STRUCT_OFFSET (JsonGeneratorPrivate, indent), NULL, NULL); g_property_set_default (G_PROPERTY (generator_props[INDENT]), 2); /** * JsonGenerator:root: * * The root #JsonNode to be used when constructing a JSON data * stream. * * Since: 0.4 */ generator_props[ROOT] = g_boxed_property_new ("root", G_PROPERTY_READWRITE, G_STRUCT_OFFSET (JsonGeneratorPrivate, root), NULL, NULL); g_property_set_prerequisite (G_PROPERTY (generator_props[ROOT]), JSON_TYPE_NODE); /** * JsonGenerator:indent-char: * * The character that should be used when indenting in pretty print. * * Since: 0.6 */ generator_props[INDENT_CHAR] = g_unichar_property_new ("indent-char", G_PROPERTY_READWRITE, G_STRUCT_OFFSET (JsonGeneratorPrivate, indent_char), NULL, NULL); g_property_set_default (G_PROPERTY (generator_props[INDENT_CHAR]), ' '); gobject_class->finalize = json_generator_finalize; g_object_class_install_properties (gobject_class, LAST_PROP, generator_props); } static void json_generator_init (JsonGenerator *generator) { JsonGeneratorPrivate *priv; generator->priv = priv = JSON_GENERATOR_GET_PRIVATE (generator); priv->pretty = FALSE; priv->indent = 2; priv->indent_char = ' '; } static gchar * dump_value (JsonGenerator *generator, gint level, const gchar *name, JsonNode *node, gsize *length) { JsonGeneratorPrivate *priv = generator->priv; gboolean pretty = priv->pretty; guint indent = priv->indent; GValue value = { 0, }; GString *buffer; buffer = g_string_new (""); if (pretty) { guint i; for (i = 0; i < (level * indent); i++) g_string_append_c (buffer, priv->indent_char); } if (name && name[0] != '\0') { if (pretty) g_string_append_printf (buffer, "\"%s\" : ", name); else g_string_append_printf (buffer, "\"%s\":", name); } json_node_get_value (node, &value); switch (G_VALUE_TYPE (&value)) { case G_TYPE_INT64: g_string_append_printf (buffer, "%" G_GINT64_FORMAT, g_value_get_int64 (&value)); break; case G_TYPE_STRING: { gchar *tmp; tmp = json_strescape (g_value_get_string (&value)); g_string_append_printf (buffer, "\"%s\"", tmp); g_free (tmp); } break; case G_TYPE_DOUBLE: { gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; g_string_append (buffer, g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (&value))); } break; case G_TYPE_BOOLEAN: g_string_append_printf (buffer, "%s", g_value_get_boolean (&value) ? "true" : "false"); break; default: break; } g_value_unset (&value); if (length) *length = buffer->len; return g_string_free (buffer, FALSE); } static gchar * dump_array (JsonGenerator *generator, gint level, const gchar *name, JsonArray *array, gsize *length) { JsonGeneratorPrivate *priv = generator->priv; guint array_len = json_array_get_length (array); guint i; GString *buffer; gboolean pretty = priv->pretty; guint indent = priv->indent; buffer = g_string_new (""); if (pretty) { for (i = 0; i < (level * indent); i++) g_string_append_c (buffer, priv->indent_char); } if (name && name[0] != '\0') { if (pretty) g_string_append_printf (buffer, "\"%s\" : ", name); else g_string_append_printf (buffer, "\"%s\":", name); } g_string_append_c (buffer, '['); if (pretty) g_string_append_c (buffer, '\n'); for (i = 0; i < array_len; i++) { JsonNode *cur = json_array_get_element (array, i); guint sub_level = level + 1; guint j; gchar *value; switch (JSON_NODE_TYPE (cur)) { case JSON_NODE_NULL: if (pretty) { for (j = 0; j < (sub_level * indent); j++) g_string_append_c (buffer, priv->indent_char); } g_string_append (buffer, "null"); break; case JSON_NODE_VALUE: value = dump_value (generator, sub_level, NULL, cur, NULL); g_string_append (buffer, value); g_free (value); break; case JSON_NODE_ARRAY: value = dump_array (generator, sub_level, NULL, json_node_get_array (cur), NULL); g_string_append (buffer, value); g_free (value); break; case JSON_NODE_OBJECT: value = dump_object (generator, sub_level, NULL, json_node_get_object (cur), NULL); g_string_append (buffer, value); g_free (value); break; } if ((i + 1) != array_len) g_string_append_c (buffer, ','); if (pretty) g_string_append_c (buffer, '\n'); } if (pretty) { for (i = 0; i < (level * indent); i++) g_string_append_c (buffer, priv->indent_char); } g_string_append_c (buffer, ']'); if (length) *length = buffer->len; return g_string_free (buffer, FALSE); } static gchar * dump_object (JsonGenerator *generator, gint level, const gchar *name, JsonObject *object, gsize *length) { JsonGeneratorPrivate *priv = generator->priv; GList *members, *l; GString *buffer; gboolean pretty = priv->pretty; guint indent = priv->indent; guint i; buffer = g_string_new (""); if (pretty) { for (i = 0; i < (level * indent); i++) g_string_append_c (buffer, priv->indent_char); } if (name && name[0] != '\0') { if (pretty) g_string_append_printf (buffer, "\"%s\" : ", name); else g_string_append_printf (buffer, "\"%s\":", name); } g_string_append_c (buffer, '{'); if (pretty) g_string_append_c (buffer, '\n'); members = json_object_get_members (object); for (l = members; l != NULL; l = l->next) { const gchar *member_name = l->data; JsonNode *cur = json_object_get_member (object, member_name); guint sub_level = level + 1; guint j; gchar *value; switch (JSON_NODE_TYPE (cur)) { case JSON_NODE_NULL: if (pretty) { for (j = 0; j < (sub_level * indent); j++) g_string_append_c (buffer, priv->indent_char); g_string_append_printf (buffer, "\"%s\" : null", member_name); } else { g_string_append_printf (buffer, "\"%s\":null", member_name); } break; case JSON_NODE_VALUE: value = dump_value (generator, sub_level, member_name, cur, NULL); g_string_append (buffer, value); g_free (value); break; case JSON_NODE_ARRAY: value = dump_array (generator, sub_level, member_name, json_node_get_array (cur), NULL); g_string_append (buffer, value); g_free (value); break; case JSON_NODE_OBJECT: value = dump_object (generator, sub_level, member_name, json_node_get_object (cur), NULL); g_string_append (buffer, value); g_free (value); break; } if (l->next != NULL) g_string_append_c (buffer, ','); if (pretty) g_string_append_c (buffer, '\n'); } g_list_free (members); if (pretty) { for (i = 0; i < (level * indent); i++) g_string_append_c (buffer, priv->indent_char); } g_string_append_c (buffer, '}'); if (length) *length = buffer->len; return g_string_free (buffer, FALSE); } /** * json_generator_new: * * Creates a new #JsonGenerator. You can use this object to generate a * JSON data stream starting from a data object model composed by * #JsonNodes. * * Return value: the newly created #JsonGenerator instance */ JsonGenerator * json_generator_new (void) { return g_object_new (JSON_TYPE_GENERATOR, NULL); } /** * json_generator_to_data: * @generator: a #JsonGenerator * @length: (out): return location for the length of the returned * buffer, or %NULL * * Generates a JSON data stream from @generator and returns it as a * buffer. * * Return value: a newly allocated buffer holding a JSON data stream. * Use g_free() to free the allocated resources. */ gchar * json_generator_to_data (JsonGenerator *generator, gsize *length) { JsonNode *root; gchar *retval = NULL; g_return_val_if_fail (JSON_IS_GENERATOR (generator), NULL); root = generator->priv->root; if (!root) { if (length) *length = 0; return NULL; } switch (JSON_NODE_TYPE (root)) { case JSON_NODE_ARRAY: retval = dump_array (generator, 0, NULL, json_node_get_array (root), length); break; case JSON_NODE_OBJECT: retval = dump_object (generator, 0, NULL, json_node_get_object (root), length); break; case JSON_NODE_NULL: retval = g_strdup ("null"); if (length) *length = 4; break; case JSON_NODE_VALUE: retval = dump_value (generator, 0, NULL, root, length); break; } return retval; } /** * json_generator_to_file: * @generator: a #JsonGenerator * @filename: path to the target file * @error: return location for a #GError, or %NULL * * Creates a JSON data stream and puts it inside @filename, overwriting the * current file contents. This operation is atomic. * * Return value: %TRUE if saving was successful. */ gboolean json_generator_to_file (JsonGenerator *generator, const gchar *filename, GError **error) { gchar *buffer; gsize len; gboolean retval; g_return_val_if_fail (JSON_IS_GENERATOR (generator), FALSE); g_return_val_if_fail (filename != NULL, FALSE); buffer = json_generator_to_data (generator, &len); retval = g_file_set_contents (filename, buffer, len, error); g_free (buffer); return retval; } /** * json_generator_to_stream: * @generator: a #JsonGenerator * @stream: a #GOutputStream * @cancellable: (allow-none): a #GCancellable, or %NULL * @error: return location for a #GError, or %NULL * * Outputs JSON data and streams it (synchronously) to @stream. * * Return value: %TRUE if the write operation was successful, and %FALSE * on failure. In case of error, the #GError will be filled accordingly * * Since: 0.12 */ gboolean json_generator_to_stream (JsonGenerator *generator, GOutputStream *stream, GCancellable *cancellable, GError **error) { gboolean retval; gchar *buffer; gsize len; g_return_val_if_fail (JSON_IS_GENERATOR (generator), FALSE); g_return_val_if_fail (G_IS_OUTPUT_STREAM (stream), FALSE); if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; buffer = json_generator_to_data (generator, &len); retval = g_output_stream_write (stream, buffer, len, cancellable, error); g_free (buffer); return retval; } /** * json_generator_set_root: * @generator: a #JsonGenerator * @node: a #JsonNode * * Sets @node as the root of the JSON data stream to be serialized by * the #JsonGenerator. * * The node is copied by the generator object, so it can be safely * freed after calling this function. */ void json_generator_set_root (JsonGenerator *generator, JsonNode *node) { g_return_if_fail (JSON_IS_GENERATOR (generator)); g_property_set (G_PROPERTY (generator_props[ROOT]), generator, node); } /** * json_generator_get_root: * @generator: a #JsonGenerator * * Retrieves a pointer to the root #JsonNode set using * json_generator_set_root(). * * Return value: (transfer none): a #JsonNode, or %NULL. The returned node * is owned by the #JsonGenerator and it should not be freed * * Since: 0.14 */ JsonNode * json_generator_get_root (JsonGenerator *generator) { JsonNode *retval = NULL; g_return_val_if_fail (JSON_IS_GENERATOR (generator), NULL); g_property_get (G_PROPERTY (generator_props[ROOT]), generator, &retval); return retval; } /** * json_generator_set_pretty: * @self: a #JsonGenerator * @value: whether the generated string should be pretty printed * * Sets whether the generated JSON should be pretty printed, using the * indentation character specified in the #JsonGenerator:indent-char * property and the spacing specified in #JsonGenerator:indent property. * * Since: 0.14 */ /** * json_generator_get_pretty: * @self: a #JsonGenerator * * Retrieves the value set using json_generator_set_pretty(). * * Return value: %TRUE if the generated JSON should be pretty-printed, and * %FALSE otherwise * * Since: 0.14 */ G_DEFINE_PROPERTY_GET_SET (JsonGenerator, json_generator, gboolean, pretty) /** * json_generator_set_indent: * @self: a #JsonGenerator * @value: the number of repetitions of the indentation character * that should be applied when pretty printing * * Sets the number of repetitions for each indentation level. * * Since: 0.14 */ /** * json_generator_get_indent: * @generator: a #JsonGenerator * * Retrieves the value set using json_generator_set_indent(). * * Return value: the number of repetitions per indentation level * * Since: 0.14 */ G_DEFINE_PROPERTY_GET_SET (JsonGenerator, json_generator, guint, indent) /** * json_generator_set_indent_char: * @self: a #JsonGenerator * @value: a Unicode character to be used when indenting * * Sets the character to be used when indenting * * Since: 0.14 */ /** * json_generator_get_indent_char: * @generator: a #JsonGenerator * * Retrieves the value set using json_generator_set_indent_char(). * * Return value: the character to be used when indenting * * Since: 0.14 */ G_DEFINE_PROPERTY_GET_SET (JsonGenerator, json_generator, gunichar, indent_char)