/* json-reader.h - JSON cursor parser * * This file is part of JSON-GLib * Copyright (C) 2010 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-reader * @Title: JsonReader * @short_description: A cursor-based parser * * #JsonReader provides a simple, cursor-based API for parsing a JSON DOM. It * is similar, in spirit, to the XML Reader API. * * In case of error, #JsonReader will be set in an error state; all subsequent * calls will simply be ignored until a function that resets the error state is * called, e.g.: * * |[ * // ask for the 7th element; if the element does not exist, the * // reader will be put in an error state * json_reader_read_element (reader, 6); * * // in case of error, this will return NULL, otherwise it will * // return the value of the element * str = json_reader_get_string_value (value); * * // this function resets the error state if any was set * json_reader_end_element (reader); * ]| * * If you want to detect the error state as soon as possible, you can use * json_reader_get_error(): * * |[ * // like the example above, but in this case we print out the * // error immediately * if (!json_reader_read_element (reader, 6)) * { * const GError *error = json_reader_get_error (reader); * g_print ("Unable to read the element: %s", error->message); * } * ]| * * #JsonReader is available since JSON-GLib 0.12. */ #include "config.h" #include #include #include "json-reader.h" #include "json-types-private.h" #include "json-debug.h" #define json_reader_return_if_error_set(r) G_STMT_START { \ if (((JsonReader *) (r))->priv->error != NULL) \ return; } G_STMT_END #define json_reader_return_val_if_error_set(r,v) G_STMT_START { \ if (((JsonReader *) (r))->priv->error != NULL) \ return (v); } G_STMT_END struct _JsonReaderPrivate { JsonNode *root; JsonNode *current_node; JsonNode *previous_node; /* Stack of member names. */ GPtrArray *members; GError *error; }; enum { PROP_0, PROP_ROOT, PROP_LAST }; static GParamSpec *reader_properties[PROP_LAST] = { NULL, }; G_DEFINE_TYPE_WITH_PRIVATE (JsonReader, json_reader, G_TYPE_OBJECT) G_DEFINE_QUARK (json-reader-error-quark, json_reader_error) static void json_reader_finalize (GObject *gobject) { JsonReaderPrivate *priv = JSON_READER (gobject)->priv; if (priv->root != NULL) json_node_unref (priv->root); if (priv->error != NULL) g_clear_error (&priv->error); if (priv->members != NULL) g_ptr_array_unref (priv->members); G_OBJECT_CLASS (json_reader_parent_class)->finalize (gobject); } static void json_reader_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_ROOT: json_reader_set_root (JSON_READER (gobject), g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void json_reader_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_ROOT: g_value_set_boxed (value, JSON_READER (gobject)->priv->root); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void json_reader_class_init (JsonReaderClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); /** * JsonReader:root: * * The root of the JSON tree that the #JsonReader should read. * * Since: 0.12 */ reader_properties[PROP_ROOT] = g_param_spec_boxed ("root", "Root Node", "The root of the tree to read", JSON_TYPE_NODE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); gobject_class->finalize = json_reader_finalize; gobject_class->set_property = json_reader_set_property; gobject_class->get_property = json_reader_get_property; g_object_class_install_properties (gobject_class, PROP_LAST, reader_properties); } static void json_reader_init (JsonReader *self) { self->priv = json_reader_get_instance_private (self); self->priv->members = g_ptr_array_new_with_free_func (g_free); } /** * json_reader_new: * @node: (allow-none): a #JsonNode, or %NULL * * Creates a new #JsonReader. You can use this object to read the contents of * the JSON tree starting from @node * * Return value: the newly created #JsonReader. Use g_object_unref() to * release the allocated resources when done * * Since: 0.12 */ JsonReader * json_reader_new (JsonNode *node) { return g_object_new (JSON_TYPE_READER, "root", node, NULL); } /* * json_reader_unset_error: * @reader: a #JsonReader * * Unsets the error state of @reader, if set * * Return value: TRUE if an error was set. */ static inline gboolean json_reader_unset_error (JsonReader *reader) { if (reader->priv->error != NULL) { g_clear_error (&(reader->priv->error)); return TRUE; } return FALSE; } /** * json_reader_set_root: * @reader: a #JsonReader * @root: (allow-none): a #JsonNode * * Sets the root #JsonNode to be read by @reader. The @reader will take * a copy of @root * * If another #JsonNode is currently set as root, it will be replaced. * * Since: 0.12 */ void json_reader_set_root (JsonReader *reader, JsonNode *root) { JsonReaderPrivate *priv; g_return_if_fail (JSON_IS_READER (reader)); priv = reader->priv; if (priv->root == root) return; if (priv->root != NULL) { json_node_unref (priv->root); priv->root = NULL; priv->current_node = NULL; priv->previous_node = NULL; } if (root != NULL) { priv->root = json_node_copy (root); priv->current_node = priv->root; priv->previous_node = NULL; } g_object_notify_by_pspec (G_OBJECT (reader), reader_properties[PROP_ROOT]); } /* * json_reader_ser_error: * @reader: a #JsonReader * @error_code: the #JsonReaderError code for the error * @error_fmt: format string * @Varargs: list of arguments for the @error_fmt string * * Sets the error state of @reader using the given error code * and string * * Return value: %FALSE, to be used to return immediately from * the caller function */ G_GNUC_PRINTF (3, 4) static gboolean json_reader_set_error (JsonReader *reader, JsonReaderError error_code, const gchar *error_fmt, ...) { JsonReaderPrivate *priv = reader->priv; va_list args; gchar *error_msg; if (priv->error != NULL) g_clear_error (&priv->error); va_start (args, error_fmt); error_msg = g_strdup_vprintf (error_fmt, args); va_end (args); g_set_error_literal (&priv->error, JSON_READER_ERROR, error_code, error_msg); g_free (error_msg); return FALSE; } /** * json_reader_get_error: * @reader: a #JsonReader * * Retrieves the #GError currently set on @reader, if the #JsonReader * is in error state * * Return value: (nullable) (transfer none): the pointer to the error, or %NULL * * Since: 0.12 */ const GError * json_reader_get_error (JsonReader *reader) { g_return_val_if_fail (JSON_IS_READER (reader), NULL); return reader->priv->error; } /** * json_reader_is_array: * @reader: a #JsonReader * * Checks whether the @reader is currently on an array * * Return value: %TRUE if the #JsonReader is on an array, and %FALSE * otherwise * * Since: 0.12 */ gboolean json_reader_is_array (JsonReader *reader) { g_return_val_if_fail (JSON_IS_READER (reader), FALSE); json_reader_return_val_if_error_set (reader, FALSE); if (reader->priv->current_node == NULL) return FALSE; return JSON_NODE_HOLDS_ARRAY (reader->priv->current_node); } /** * json_reader_is_object: * @reader: a #JsonReader * * Checks whether the @reader is currently on an object * * Return value: %TRUE if the #JsonReader is on an object, and %FALSE * otherwise * * Since: 0.12 */ gboolean json_reader_is_object (JsonReader *reader) { g_return_val_if_fail (JSON_IS_READER (reader), FALSE); json_reader_return_val_if_error_set (reader, FALSE); if (reader->priv->current_node == NULL) return FALSE; return JSON_NODE_HOLDS_OBJECT (reader->priv->current_node); } /** * json_reader_is_value: * @reader: a #JsonReader * * Checks whether the @reader is currently on a value * * Return value: %TRUE if the #JsonReader is on a value, and %FALSE * otherwise * * Since: 0.12 */ gboolean json_reader_is_value (JsonReader *reader) { g_return_val_if_fail (JSON_IS_READER (reader), FALSE); json_reader_return_val_if_error_set (reader, FALSE); if (reader->priv->current_node == NULL) return FALSE; return JSON_NODE_HOLDS_VALUE (reader->priv->current_node) || JSON_NODE_HOLDS_NULL (reader->priv->current_node); } /** * json_reader_read_element: * @reader: a #JsonReader * @index_: the index of the element * * Advances the cursor of @reader to the element @index_ of the array * or the object at the current position. * * You can use the json_reader_get_value* family of functions to retrieve * the value of the element; for instance: * * |[ * json_reader_read_element (reader, 0); * int_value = json_reader_get_int_value (reader); * ]| * * After reading the value, json_reader_end_element() should be called to * reposition the cursor inside the #JsonReader, e.g.: * * |[ * json_reader_read_element (reader, 1); * str_value = json_reader_get_string_value (reader); * json_reader_end_element (reader); * * json_reader_read_element (reader, 2); * str_value = json_reader_get_string_value (reader); * json_reader_end_element (reader); * ]| * * If @reader is not currently on an array or an object, or if the @index_ is * bigger than the size of the array or the object, the #JsonReader will be * put in an error state until json_reader_end_element() is called. This means * that if used conditionally, json_reader_end_element() must be called on both * code paths: * * |[ * if (!json_reader_read_element (reader, 1)) * { * json_reader_end_element (reader); * g_set_error (error, …); * return FALSE; * } * * str_value = json_reader_get_string_value (reader); * json_reader_end_element (reader); * ]| * * Return value: %TRUE on success, and %FALSE otherwise * * Since: 0.12 */ gboolean json_reader_read_element (JsonReader *reader, guint index_) { JsonReaderPrivate *priv; g_return_val_if_fail (JSON_READER (reader), FALSE); json_reader_return_val_if_error_set (reader, FALSE); priv = reader->priv; if (priv->current_node == NULL) priv->current_node = priv->root; if (!(JSON_NODE_HOLDS_ARRAY (priv->current_node) || JSON_NODE_HOLDS_OBJECT (priv->current_node))) return json_reader_set_error (reader, JSON_READER_ERROR_NO_ARRAY, _("The current node is of type “%s”, but " "an array or an object was expected."), json_node_type_name (priv->current_node)); switch (json_node_get_node_type (priv->current_node)) { case JSON_NODE_ARRAY: { JsonArray *array = json_node_get_array (priv->current_node); if (index_ >= json_array_get_length (array)) return json_reader_set_error (reader, JSON_READER_ERROR_INVALID_INDEX, _("The index “%d” is greater than the size " "of the array at the current position."), index_); priv->previous_node = priv->current_node; priv->current_node = json_array_get_element (array, index_); } break; case JSON_NODE_OBJECT: { JsonObject *object = json_node_get_object (priv->current_node); GList *members; const gchar *name; if (index_ >= json_object_get_size (object)) return json_reader_set_error (reader, JSON_READER_ERROR_INVALID_INDEX, _("The index “%d” is greater than the size " "of the object at the current position."), index_); priv->previous_node = priv->current_node; members = json_object_get_members (object); name = g_list_nth_data (members, index_); priv->current_node = json_object_get_member (object, name); g_ptr_array_add (priv->members, g_strdup (name)); g_list_free (members); } break; default: g_assert_not_reached (); return FALSE; } return TRUE; } /** * json_reader_end_element: * @reader: a #JsonReader * * Moves the cursor back to the previous node after being positioned * inside an array * * This function resets the error state of @reader, if any was set * * Since: 0.12 */ void json_reader_end_element (JsonReader *reader) { JsonReaderPrivate *priv; JsonNode *tmp; g_return_if_fail (JSON_IS_READER (reader)); if (json_reader_unset_error (reader)) return; priv = reader->priv; if (priv->previous_node != NULL) tmp = json_node_get_parent (priv->previous_node); else tmp = NULL; if (json_node_get_node_type (priv->previous_node) == JSON_NODE_OBJECT) g_ptr_array_remove_index (priv->members, priv->members->len - 1); priv->current_node = priv->previous_node; priv->previous_node = tmp; } /** * json_reader_count_elements: * @reader: a #JsonReader * * Counts the elements of the current position, if @reader is * positioned on an array * * Return value: the number of elements, or -1. In case of failure * the #JsonReader is set in an error state * * Since: 0.12 */ gint json_reader_count_elements (JsonReader *reader) { JsonReaderPrivate *priv; g_return_val_if_fail (JSON_IS_READER (reader), -1); priv = reader->priv; if (priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return -1; } if (!JSON_NODE_HOLDS_ARRAY (priv->current_node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_ARRAY, _("The current position holds a “%s” and not an array"), json_node_type_get_name (JSON_NODE_TYPE (priv->current_node))); return -1; } return json_array_get_length (json_node_get_array (priv->current_node)); } /** * json_reader_read_member: * @reader: a #JsonReader * @member_name: the name of the member to read * * Advances the cursor of @reader to the @member_name of the object at the * current position. * * You can use the json_reader_get_value* family of functions to retrieve * the value of the member; for instance: * * |[ * json_reader_read_member (reader, "width"); * width = json_reader_get_int_value (reader); * ]| * * After reading the value, json_reader_end_member() should be called to * reposition the cursor inside the #JsonReader, e.g.: * * |[ * json_reader_read_member (reader, "author"); * author = json_reader_get_string_value (reader); * json_reader_end_member (reader); * * json_reader_read_member (reader, "title"); * title = json_reader_get_string_value (reader); * json_reader_end_member (reader); * ]| * * If @reader is not currently on an object, or if the @member_name is not * defined in the object, the #JsonReader will be put in an error state until * json_reader_end_member() is called. This means that if used conditionally, * json_reader_end_member() must be called on both code paths: * * |[ * if (!json_reader_read_member (reader, "title")) * { * json_reader_end_member (reader); * g_set_error (error, …); * return FALSE; * } * * str_value = json_reader_get_string_value (reader); * json_reader_end_member (reader); * ]| * * Return value: %TRUE on success, and %FALSE otherwise * * Since: 0.12 */ gboolean json_reader_read_member (JsonReader *reader, const gchar *member_name) { JsonReaderPrivate *priv; JsonObject *object; g_return_val_if_fail (JSON_READER (reader), FALSE); g_return_val_if_fail (member_name != NULL, FALSE); json_reader_return_val_if_error_set (reader, FALSE); priv = reader->priv; if (priv->current_node == NULL) priv->current_node = priv->root; if (!JSON_NODE_HOLDS_OBJECT (priv->current_node)) return json_reader_set_error (reader, JSON_READER_ERROR_NO_OBJECT, _("The current node is of type “%s”, but " "an object was expected."), json_node_type_name (priv->current_node)); object = json_node_get_object (priv->current_node); if (!json_object_has_member (object, member_name)) return json_reader_set_error (reader, JSON_READER_ERROR_INVALID_MEMBER, _("The member “%s” is not defined in the " "object at the current position."), member_name); priv->previous_node = priv->current_node; priv->current_node = json_object_get_member (object, member_name); g_ptr_array_add (priv->members, g_strdup (member_name)); return TRUE; } /** * json_reader_end_member: * @reader: a #JsonReader * * Moves the cursor back to the previous node after being positioned * inside an object * * This function resets the error state of @reader, if any was set * * Since: 0.12 */ void json_reader_end_member (JsonReader *reader) { JsonReaderPrivate *priv; JsonNode *tmp; g_return_if_fail (JSON_IS_READER (reader)); if (json_reader_unset_error (reader)) return; priv = reader->priv; if (priv->previous_node != NULL) tmp = json_node_get_parent (priv->previous_node); else tmp = NULL; g_ptr_array_remove_index (priv->members, priv->members->len - 1); priv->current_node = priv->previous_node; priv->previous_node = tmp; } /** * json_reader_list_members: * @reader: a #JsonReader * * Retrieves a list of member names from the current position, if @reader * is positioned on an object. * * Return value: (transfer full): a newly allocated, %NULL-terminated * array of strings holding the members name. Use g_strfreev() when * done. * * Since: 0.14 */ gchar ** json_reader_list_members (JsonReader *reader) { JsonReaderPrivate *priv; GList *members, *l; gchar **retval; gint i; g_return_val_if_fail (JSON_IS_READER (reader), NULL); priv = reader->priv; if (priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return NULL; } if (!JSON_NODE_HOLDS_OBJECT (priv->current_node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_OBJECT, _("The current position holds a “%s” and not an object"), json_node_type_get_name (JSON_NODE_TYPE (priv->current_node))); return NULL; } members = json_object_get_members (json_node_get_object (priv->current_node)); if (members == NULL) return NULL; retval = g_new (gchar*, g_list_length (members) + 1); for (l = members, i = 0; l != NULL; l = l->next, i += 1) retval[i] = g_strdup (l->data); retval[i] = NULL; g_list_free (members); return retval; } /** * json_reader_count_members: * @reader: a #JsonReader * * Counts the members of the current position, if @reader is * positioned on an object * * Return value: the number of members, or -1. In case of failure * the #JsonReader is set in an error state * * Since: 0.12 */ gint json_reader_count_members (JsonReader *reader) { JsonReaderPrivate *priv; g_return_val_if_fail (JSON_IS_READER (reader), -1); priv = reader->priv; if (priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return -1; } if (!JSON_NODE_HOLDS_OBJECT (priv->current_node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_OBJECT, _("The current position holds a “%s” and not an object"), json_node_type_get_name (JSON_NODE_TYPE (priv->current_node))); return -1; } return json_object_get_size (json_node_get_object (priv->current_node)); } /** * json_reader_get_value: * @reader: a #JsonReader * * Retrieves the #JsonNode of the current position of @reader * * Return value: (nullable) (transfer none): a #JsonNode, or %NULL. The * returned node is owned by the #JsonReader and it should not be * modified or freed directly * * Since: 0.12 */ JsonNode * json_reader_get_value (JsonReader *reader) { JsonNode *node; g_return_val_if_fail (JSON_IS_READER (reader), NULL); json_reader_return_val_if_error_set (reader, NULL); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return NULL; } node = reader->priv->current_node; if (!JSON_NODE_HOLDS_VALUE (node) && !JSON_NODE_HOLDS_NULL (node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_VALUE, _("The current position holds a “%s” and not a value"), json_node_type_get_name (JSON_NODE_TYPE (node))); return NULL; } return reader->priv->current_node; } /** * json_reader_get_int_value: * @reader: a #JsonReader * * Retrieves the integer value of the current position of @reader * * Return value: the integer value * * Since: 0.12 */ gint64 json_reader_get_int_value (JsonReader *reader) { JsonNode *node; g_return_val_if_fail (JSON_IS_READER (reader), 0); json_reader_return_val_if_error_set (reader, 0); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return 0; } node = reader->priv->current_node; if (!JSON_NODE_HOLDS_VALUE (node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_VALUE, _("The current position holds a “%s” and not a value"), json_node_type_get_name (JSON_NODE_TYPE (node))); return 0; } return json_node_get_int (reader->priv->current_node); } /** * json_reader_get_double_value: * @reader: a #JsonReader * * Retrieves the floating point value of the current position of @reader * * Return value: the floating point value * * Since: 0.12 */ gdouble json_reader_get_double_value (JsonReader *reader) { JsonNode *node; g_return_val_if_fail (JSON_IS_READER (reader), 0.0); json_reader_return_val_if_error_set (reader, 0.0); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return 0.0; } node = reader->priv->current_node; if (!JSON_NODE_HOLDS_VALUE (node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_VALUE, _("The current position holds a “%s” and not a value"), json_node_type_get_name (JSON_NODE_TYPE (node))); return 0.0; } return json_node_get_double (reader->priv->current_node); } /** * json_reader_get_string_value: * @reader: a #JsonReader * * Retrieves the string value of the current position of @reader * * Return value: the string value * * Since: 0.12 */ const gchar * json_reader_get_string_value (JsonReader *reader) { JsonNode *node; g_return_val_if_fail (JSON_IS_READER (reader), NULL); json_reader_return_val_if_error_set (reader, NULL); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return NULL; } node = reader->priv->current_node; if (!JSON_NODE_HOLDS_VALUE (node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_VALUE, _("The current position holds a “%s” and not a value"), json_node_type_get_name (JSON_NODE_TYPE (node))); return NULL; } if (json_node_get_value_type (node) != G_TYPE_STRING) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_TYPE, _("The current position does not hold a string type")); return NULL; } return json_node_get_string (reader->priv->current_node); } /** * json_reader_get_boolean_value: * @reader: a #JsonReader * * Retrieves the boolean value of the current position of @reader * * Return value: the boolean value * * Since: 0.12 */ gboolean json_reader_get_boolean_value (JsonReader *reader) { JsonNode *node; g_return_val_if_fail (JSON_IS_READER (reader), FALSE); json_reader_return_val_if_error_set (reader, FALSE); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return FALSE; } node = reader->priv->current_node; if (!JSON_NODE_HOLDS_VALUE (node)) { json_reader_set_error (reader, JSON_READER_ERROR_NO_VALUE, _("The current position holds a “%s” and not a value"), json_node_type_get_name (JSON_NODE_TYPE (node))); return FALSE; } return json_node_get_boolean (node); } /** * json_reader_get_null_value: * @reader: a #JsonReader * * Checks whether the value of the current position of @reader is 'null' * * Return value: %TRUE if 'null' is set, and %FALSE otherwise * * Since: 0.12 */ gboolean json_reader_get_null_value (JsonReader *reader) { g_return_val_if_fail (JSON_IS_READER (reader), FALSE); json_reader_return_val_if_error_set (reader, FALSE); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return FALSE; } return JSON_NODE_HOLDS_NULL (reader->priv->current_node); } /** * json_reader_get_member_name: * @reader: a #JsonReader * * Retrieves the name of the current member. * * Return value: (nullable) (transfer none): the name of the member, or %NULL * * Since: 0.14 */ const gchar * json_reader_get_member_name (JsonReader *reader) { g_return_val_if_fail (JSON_IS_READER (reader), NULL); json_reader_return_val_if_error_set (reader, NULL); if (reader->priv->current_node == NULL) { json_reader_set_error (reader, JSON_READER_ERROR_INVALID_NODE, _("No node available at the current position")); return NULL; } if (reader->priv->members->len == 0) return NULL; return g_ptr_array_index (reader->priv->members, reader->priv->members->len - 1); }