/* json-generator.c - JSON tree builder * * This file is part of JSON-GLib * Copyright (C) 2010 Luca Bruno * Copyright (C) 2015 Collabora 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.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: * Luca Bruno * Philip Withnall */ /** * SECTION:json-builder * @Title: JsonBuilder * @short_description: Generates JSON trees * @See_Also: JsonGenerator * * #JsonBuilder provides an object for generating a JSON tree. * You can generate only one tree with one #JsonBuilder instance. * * The root of the JSON tree can be either a #JsonObject or a #JsonArray. * Thus the first call must necessarily be either * json_builder_begin_object() or json_builder_begin_array(). * * For convenience to language bindings, #JsonBuilder returns itself from * most of functions, making it easy to chain function calls. */ #include "config.h" #include #include #include "json-types-private.h" #include "json-builder.h" struct _JsonBuilderPrivate { GQueue *stack; JsonNode *root; gboolean immutable; }; enum { PROP_IMMUTABLE = 1, PROP_LAST }; static GParamSpec *builder_props[PROP_LAST] = { NULL, }; typedef enum { JSON_BUILDER_MODE_OBJECT, JSON_BUILDER_MODE_ARRAY, JSON_BUILDER_MODE_MEMBER } JsonBuilderMode; typedef struct { JsonBuilderMode mode; union { JsonObject *object; JsonArray *array; } data; gchar *member_name; } JsonBuilderState; static void json_builder_state_free (JsonBuilderState *state) { if (G_LIKELY (state)) { switch (state->mode) { case JSON_BUILDER_MODE_OBJECT: case JSON_BUILDER_MODE_MEMBER: json_object_unref (state->data.object); g_free (state->member_name); state->data.object = NULL; state->member_name = NULL; break; case JSON_BUILDER_MODE_ARRAY: json_array_unref (state->data.array); state->data.array = NULL; break; default: g_assert_not_reached (); } g_slice_free (JsonBuilderState, state); } } G_DEFINE_TYPE_WITH_PRIVATE (JsonBuilder, json_builder, G_TYPE_OBJECT) static void json_builder_free_all_state (JsonBuilder *builder) { JsonBuilderState *state; while (!g_queue_is_empty (builder->priv->stack)) { state = g_queue_pop_head (builder->priv->stack); json_builder_state_free (state); } if (builder->priv->root) { json_node_unref (builder->priv->root); builder->priv->root = NULL; } } static void json_builder_finalize (GObject *gobject) { JsonBuilderPrivate *priv = json_builder_get_instance_private ((JsonBuilder *) gobject); json_builder_free_all_state (JSON_BUILDER (gobject)); g_queue_free (priv->stack); priv->stack = NULL; G_OBJECT_CLASS (json_builder_parent_class)->finalize (gobject); } static void json_builder_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { JsonBuilderPrivate *priv = JSON_BUILDER (gobject)->priv; switch (prop_id) { case PROP_IMMUTABLE: /* Construct-only. */ priv->immutable = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void json_builder_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { JsonBuilderPrivate *priv = JSON_BUILDER (gobject)->priv; switch (prop_id) { case PROP_IMMUTABLE: g_value_set_boolean (value, priv->immutable); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void json_builder_class_init (JsonBuilderClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); /** * JsonBuilder:immutable: * * Whether the #JsonNode tree built by the #JsonBuilder should be immutable * when created. Making the output immutable on creation avoids the expense * of traversing it to make it immutable later. * * Since: 1.2 */ builder_props[PROP_IMMUTABLE] = g_param_spec_boolean ("immutable", "Immutable Output", "Whether the builder output is immutable.", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); gobject_class->set_property = json_builder_set_property; gobject_class->get_property = json_builder_get_property; gobject_class->finalize = json_builder_finalize; g_object_class_install_properties (gobject_class, PROP_LAST, builder_props); } static void json_builder_init (JsonBuilder *builder) { JsonBuilderPrivate *priv = json_builder_get_instance_private (builder); builder->priv = priv; priv->stack = g_queue_new (); priv->root = NULL; } static inline JsonBuilderMode json_builder_current_mode (JsonBuilder *builder) { JsonBuilderState *state = g_queue_peek_head (builder->priv->stack); return state->mode; } static inline gboolean json_builder_is_valid_add_mode (JsonBuilder *builder) { JsonBuilderMode mode = json_builder_current_mode (builder); return mode == JSON_BUILDER_MODE_MEMBER || mode == JSON_BUILDER_MODE_ARRAY; } /** * json_builder_new: * * Creates a new #JsonBuilder. You can use this object to generate a * JSON tree and obtain the root #JsonNode. * * Return value: the newly created #JsonBuilder instance */ JsonBuilder * json_builder_new (void) { return g_object_new (JSON_TYPE_BUILDER, NULL); } /** * json_builder_new_immutable: (constructor) * * Creates a new #JsonBuilder instance with its #JsonBuilder:immutable property * set to %TRUE to create immutable output trees. * * Since: 1.2 * Returns: (transfer full): a new #JsonBuilder */ JsonBuilder * json_builder_new_immutable (void) { return g_object_new (JSON_TYPE_BUILDER, "immutable", TRUE, NULL); } /** * json_builder_get_root: * @builder: a #JsonBuilder * * Returns the root of the current constructed tree, if the build is complete * (ie: all opened objects, object members and arrays are being closed). * * Return value: (nullable) (transfer full): the #JsonNode, or %NULL if the * build is not complete. Free the returned value with json_node_unref(). */ JsonNode * json_builder_get_root (JsonBuilder *builder) { JsonNode *root = NULL; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); if (builder->priv->root) root = json_node_copy (builder->priv->root); /* Sanity check. */ g_return_val_if_fail (!builder->priv->immutable || root == NULL || json_node_is_immutable (root), NULL); return root; } /** * json_builder_reset: * @builder: a #JsonBuilder * * Resets the state of the @builder back to its initial state. */ void json_builder_reset (JsonBuilder *builder) { g_return_if_fail (JSON_IS_BUILDER (builder)); json_builder_free_all_state (builder); } /** * json_builder_begin_object: * @builder: a #JsonBuilder * * Opens a subobject inside the given @builder. When done adding members to * the subobject, json_builder_end_object() must be called. * * Can be called for first or only if the call is associated to an object member * or an array element. * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_begin_object (JsonBuilder *builder) { JsonObject *object; JsonBuilderState *state; JsonBuilderState *cur_state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (builder->priv->root == NULL, NULL); g_return_val_if_fail (g_queue_is_empty (builder->priv->stack) || json_builder_is_valid_add_mode (builder), NULL); object = json_object_new (); cur_state = g_queue_peek_head (builder->priv->stack); if (cur_state) { switch (cur_state->mode) { case JSON_BUILDER_MODE_ARRAY: json_array_add_object_element (cur_state->data.array, json_object_ref (object)); break; case JSON_BUILDER_MODE_MEMBER: json_object_set_object_member (cur_state->data.object, cur_state->member_name, json_object_ref (object)); g_free (cur_state->member_name); cur_state->member_name = NULL; cur_state->mode = JSON_BUILDER_MODE_OBJECT; break; default: g_assert_not_reached (); } } state = g_slice_new (JsonBuilderState); state->data.object = object; state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; g_queue_push_head (builder->priv->stack, state); return builder; } /** * json_builder_end_object: * @builder: a #JsonBuilder * * Closes the subobject inside the given @builder that was opened by the most * recent call to json_builder_begin_object(). * * Cannot be called after json_builder_set_member_name(). * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_end_object (JsonBuilder *builder) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_current_mode (builder) == JSON_BUILDER_MODE_OBJECT, NULL); state = g_queue_pop_head (builder->priv->stack); if (builder->priv->immutable) json_object_seal (state->data.object); if (g_queue_is_empty (builder->priv->stack)) { builder->priv->root = json_node_new (JSON_NODE_OBJECT); json_node_take_object (builder->priv->root, json_object_ref (state->data.object)); if (builder->priv->immutable) json_node_seal (builder->priv->root); } json_builder_state_free (state); return builder; } /** * json_builder_begin_array: * @builder: a #JsonBuilder * * Opens a subarray inside the given @builder. When done adding members to * the subarray, json_builder_end_array() must be called. * * Can be called for first or only if the call is associated to an object member * or an array element. * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_begin_array (JsonBuilder *builder) { JsonArray *array; JsonBuilderState *state; JsonBuilderState *cur_state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (builder->priv->root == NULL, NULL); g_return_val_if_fail (g_queue_is_empty (builder->priv->stack) || json_builder_is_valid_add_mode (builder), NULL); array = json_array_new (); cur_state = g_queue_peek_head (builder->priv->stack); if (cur_state) { switch (cur_state->mode) { case JSON_BUILDER_MODE_ARRAY: json_array_add_array_element (cur_state->data.array, json_array_ref (array)); break; case JSON_BUILDER_MODE_MEMBER: json_object_set_array_member (cur_state->data.object, cur_state->member_name, json_array_ref (array)); g_free (cur_state->member_name); cur_state->member_name = NULL; cur_state->mode = JSON_BUILDER_MODE_OBJECT; break; default: g_assert_not_reached (); } } state = g_slice_new (JsonBuilderState); state->data.array = array; state->mode = JSON_BUILDER_MODE_ARRAY; g_queue_push_head (builder->priv->stack, state); return builder; } /** * json_builder_end_array: * @builder: a #JsonBuilder * * Closes the subarray inside the given @builder that was opened by the most * recent call to json_builder_begin_array(). * * Cannot be called after json_builder_set_member_name(). * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_end_array (JsonBuilder *builder) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_current_mode (builder) == JSON_BUILDER_MODE_ARRAY, NULL); state = g_queue_pop_head (builder->priv->stack); if (builder->priv->immutable) json_array_seal (state->data.array); if (g_queue_is_empty (builder->priv->stack)) { builder->priv->root = json_node_new (JSON_NODE_ARRAY); json_node_take_array (builder->priv->root, json_array_ref (state->data.array)); if (builder->priv->immutable) json_node_seal (builder->priv->root); } json_builder_state_free (state); return builder; } /** * json_builder_set_member_name: * @builder: a #JsonBuilder * @member_name: the name of the member * * Set the name of the next member in an object. The next call must add a value, * open an object or an array. * * Can be called only if the call is associated to an object. * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_set_member_name (JsonBuilder *builder, const gchar *member_name) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (member_name != NULL, NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_current_mode (builder) == JSON_BUILDER_MODE_OBJECT, NULL); state = g_queue_peek_head (builder->priv->stack); state->member_name = g_strdup (member_name); state->mode = JSON_BUILDER_MODE_MEMBER; return builder; } /** * json_builder_add_value: * @builder: a #JsonBuilder * @node: (transfer full): the value of the member or element * * If called after json_builder_set_member_name(), sets @node as member of the * most recent opened object, otherwise @node is added as element of the most * recent opened array. * * The builder will take ownership of the #JsonNode. * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_add_value (JsonBuilder *builder, JsonNode *node) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (node != NULL, NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); if (builder->priv->immutable) json_node_seal (node); switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: json_object_set_member (state->data.object, state->member_name, node); g_free (state->member_name); state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; break; case JSON_BUILDER_MODE_ARRAY: json_array_add_element (state->data.array, node); break; default: g_assert_not_reached (); } return builder; } /** * json_builder_add_int_value: * @builder: a #JsonBuilder * @value: the value of the member or element * * If called after json_builder_set_member_name(), sets @value as member of the * most recent opened object, otherwise @value is added as element of the most * recent opened array. * * See also: json_builder_add_value() * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_add_int_value (JsonBuilder *builder, gint64 value) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: json_object_set_int_member (state->data.object, state->member_name, value); g_free (state->member_name); state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; break; case JSON_BUILDER_MODE_ARRAY: json_array_add_int_element (state->data.array, value); break; default: g_assert_not_reached (); } return builder; } /** * json_builder_add_double_value: * @builder: a #JsonBuilder * @value: the value of the member or element * * If called after json_builder_set_member_name(), sets @value as member of the * most recent opened object, otherwise @value is added as element of the most * recent opened array. * * See also: json_builder_add_value() * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_add_double_value (JsonBuilder *builder, gdouble value) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: json_object_set_double_member (state->data.object, state->member_name, value); g_free (state->member_name); state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; break; case JSON_BUILDER_MODE_ARRAY: json_array_add_double_element (state->data.array, value); break; default: g_assert_not_reached (); } return builder; } /** * json_builder_add_boolean_value: * @builder: a #JsonBuilder * @value: the value of the member or element * * If called after json_builder_set_member_name(), sets @value as member of the * most recent opened object, otherwise @value is added as element of the most * recent opened array. * * See also: json_builder_add_value() * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_add_boolean_value (JsonBuilder *builder, gboolean value) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: json_object_set_boolean_member (state->data.object, state->member_name, value); g_free (state->member_name); state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; break; case JSON_BUILDER_MODE_ARRAY: json_array_add_boolean_element (state->data.array, value); break; default: g_assert_not_reached (); } return builder; } /** * json_builder_add_string_value: * @builder: a #JsonBuilder * @value: the value of the member or element * * If called after json_builder_set_member_name(), sets @value as member of the * most recent opened object, otherwise @value is added as element of the most * recent opened array. * * See also: json_builder_add_value() * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if the * call was inconsistent */ JsonBuilder * json_builder_add_string_value (JsonBuilder *builder, const gchar *value) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: json_object_set_string_member (state->data.object, state->member_name, value); g_free (state->member_name); state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; break; case JSON_BUILDER_MODE_ARRAY: json_array_add_string_element (state->data.array, value); break; default: g_assert_not_reached (); } return builder; } /** * json_builder_add_null_value: * @builder: a #JsonBuilder * * If called after json_builder_set_member_name(), sets null as member of the * most recent opened object, otherwise null is added as element of the most * recent opened array. * * See also: json_builder_add_value() * * Return value: (nullable) (transfer none): the #JsonBuilder, or %NULL if * the call was inconsistent */ JsonBuilder * json_builder_add_null_value (JsonBuilder *builder) { JsonBuilderState *state; g_return_val_if_fail (JSON_IS_BUILDER (builder), NULL); g_return_val_if_fail (!g_queue_is_empty (builder->priv->stack), NULL); g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL); state = g_queue_peek_head (builder->priv->stack); switch (state->mode) { case JSON_BUILDER_MODE_MEMBER: json_object_set_null_member (state->data.object, state->member_name); g_free (state->member_name); state->member_name = NULL; state->mode = JSON_BUILDER_MODE_OBJECT; break; case JSON_BUILDER_MODE_ARRAY: json_array_add_null_element (state->data.array); break; default: g_assert_not_reached (); } return builder; }