/* WirePlumber * * Copyright © 2022 Collabora Ltd. * @author Julian Bouzas * * SPDX-License-Identifier: MIT */ #include "spa-json.h" #include "log.h" #include #include WP_DEFINE_LOCAL_LOG_TOPIC ("wp-spa-json") #define WP_SPA_JSON_STRING_INIT_SIZE 64 #define WP_SPA_JSON_BUILDER_INIT_SIZE 64 /*! \defgroup wpspajson WpSpaJson */ /*! * \struct WpSpaJson * * \since 0.4.8 */ /*! * \struct WpSpaJsonBuilder * * \since 0.4.8 */ /*! * \struct WpSpaJsonParser * * \since 0.4.8 */ static void builder_add_formatted (WpSpaJsonBuilder *self, const gchar *fmt, ...) G_GNUC_PRINTF (2, 3); static WpSpaJsonBuilder * wp_spa_json_builder_new_formatted (const gchar *fmt, ...) G_GNUC_PRINTF (1, 2); enum { FLAG_NO_OWNERSHIP = (1 << 0), }; struct _WpSpaJson { grefcount ref; guint32 flags; /* only used if FLAG_NO_OWNERSHIP is not set */ WpSpaJsonBuilder *builder; /* not used if constructed with _new_wrap() */ struct spa_json json_data; /* json data */ gchar *data; size_t size; struct spa_json *json; }; G_DEFINE_BOXED_TYPE (WpSpaJson, wp_spa_json, wp_spa_json_ref, wp_spa_json_unref) struct _WpSpaJsonBuilder { gboolean add_separator; gchar *data; size_t size; size_t max_size; }; G_DEFINE_BOXED_TYPE (WpSpaJsonBuilder, wp_spa_json_builder, wp_spa_json_builder_ref, wp_spa_json_builder_unref) struct _WpSpaJsonParser { WpSpaJson *json; struct spa_json data[2]; struct spa_json *pos; struct spa_json curr; }; G_DEFINE_BOXED_TYPE (WpSpaJsonParser, wp_spa_json_parser, wp_spa_json_parser_ref, wp_spa_json_parser_unref) /*! * \brief Increases the reference count of a spa json object * \ingroup wpspajson * \param self a spa json object * \returns (transfer full): \a self with an additional reference count on it */ WpSpaJson * wp_spa_json_ref (WpSpaJson *self) { g_ref_count_inc (&self->ref); return self; } static void wp_spa_json_free (WpSpaJson *self) { g_clear_pointer (&self->builder, wp_spa_json_builder_unref); g_slice_free (WpSpaJson, self); } /*! * \brief Decreases the reference count on \a self and frees it when the ref * count reaches zero. * \ingroup wpspajson * \param self (transfer full): a spa json object */ void wp_spa_json_unref (WpSpaJson *self) { if (g_ref_count_dec (&self->ref)) wp_spa_json_free (self); } static WpSpaJsonBuilder * wp_spa_json_builder_new (const gchar *data, size_t size) { WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder); self->add_separator = FALSE; self->data = g_new0 (gchar, size + 1); self->max_size = size; memcpy (self->data, data, size), self->data[size] = '\0'; self->size = size; return self; } static WpSpaJsonBuilder * wp_spa_json_builder_new_empty (size_t size) { WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder); self->add_separator = FALSE; self->data = g_new0 (gchar, size + 1); self->max_size = size; self->data[0] = '\0'; self->size = 0; return self; } static WpSpaJsonBuilder * wp_spa_json_builder_new_formatted (const gchar *fmt, ...) { va_list args; WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder); self->add_separator = FALSE; va_start (args, fmt); self->data = g_strdup_vprintf (fmt, args); va_end (args); self->size = strlen (self->data); self->max_size = self->size; return self; } static WpSpaJson * wp_spa_json_new_from_builder (WpSpaJsonBuilder *builder) { WpSpaJson *self = g_slice_new0 (WpSpaJson); g_ref_count_init (&self->ref); self->flags = 0; self->builder = builder; self->data = builder->data; self->size = builder->size; spa_json_init (&self->json_data, self->data, self->size); self->json = &self->json_data; return self; } static WpSpaJson * wp_spa_json_new (const gchar *data, size_t size) { return wp_spa_json_new_from_builder (wp_spa_json_builder_new (data, size)); } /*! * \brief Constructs a new WpSpaJson from a JSON string. * * \ingroup wpspajson * \param json_str a JSON string * \returns a new WpSpaJson; unlike the "wrap" variants, this function copies * the data in \a json_str, so it does not need to stay alive. * \since 0.5.0 */ WP_API WpSpaJson * wp_spa_json_new_from_string (const gchar *json_str) { return wp_spa_json_new (json_str, strlen (json_str)); } /*! * \brief Constructs a new WpSpaJson from a JSON string with specific length. * * \ingroup wpspajson * \param json_str a JSON string * \param len the specific length of the string * \returns a new WpSpaJson; unlike the "wrap" variants, this function copies * the data in \a json_str, so it does not need to stay alive. * \since 0.5.0 */ WP_API WpSpaJson * wp_spa_json_new_from_stringn (const gchar *json_str, size_t len) { return wp_spa_json_new (json_str, len); } /*! * \brief Constructs a new WpSpaJson that wraps a JSON string. * * \ingroup wpspajson * \param json_str a JSON string * \returns a new WpSpaJson that references the data in \a json_str. \a json_str * is not copied, so it needs to stay alive. * \since 0.5.0 */ WpSpaJson * wp_spa_json_new_wrap_string (const gchar *json_str) { return wp_spa_json_new_wrap_stringn(json_str, strlen (json_str)); } /*! * \brief Constructs a new WpSpaJson that wraps a JSON string with specific length. * * \ingroup wpspajson * \param json_str a JSON string * \param len the specific length of the string * \returns a new WpSpaJson that references the data in \a json_str. \a json_str * is not copied, so it needs to stay alive. * \since 0.5.0 */ WpSpaJson * wp_spa_json_new_wrap_stringn (const gchar *json_str, size_t len) { WpSpaJson *self = g_slice_new0 (WpSpaJson); g_ref_count_init (&self->ref); self->flags = FLAG_NO_OWNERSHIP; spa_json_init (&self->json_data, json_str, len); self->builder = NULL; self->data = (gchar *)self->json_data.cur; self->size = self->json_data.end - self->json_data.cur; self->json = &self->json_data; return self; } /*! * \brief Constructs a new WpSpaJson that wraps the given `spa_json`. * * \ingroup wpspajson * \param json a spa_json * \returns a new WpSpaJson that references the data in \a json. \a json is not * copied, so it needs to stay alive. */ WpSpaJson * wp_spa_json_new_wrap (struct spa_json *json) { WpSpaJson *self = g_slice_new0 (WpSpaJson); g_ref_count_init (&self->ref); self->flags = FLAG_NO_OWNERSHIP; self->builder = NULL; self->data = (gchar *)json->cur; self->size = json->end - json->cur; self->json = json; return self; } /*! * \brief Converts a WpSpaJson pointer to a `struct spa_json` one, for use with * native pipewire & spa functions. The returned pointer is owned by WpSpaJson * and may not be modified or freed. * * \ingroup wpspajson * \param self a spa json object * \returns a const pointer to the underlying spa_json structure */ const struct spa_json * wp_spa_json_get_spa_json (const WpSpaJson *self) { return self->json; } /*! * \brief Returns the json data * * \ingroup wpspajson * \param self a spa json object * \returns a const pointer to the json data */ const gchar * wp_spa_json_get_data (const WpSpaJson *self) { return self->data; } /*! * \brief Returns the json data size * * \ingroup wpspajson * \param self a spa json object * \returns the json data size */ size_t wp_spa_json_get_size (const WpSpaJson *self) { return self->size; } /*! * \brief Returns a newly allocated json string with length matching the size * * \ingroup wpspajson * \param self a spa json object * \returns (transfer full): the json string with length matching the size * \since 0.4.11 */ gchar * wp_spa_json_to_string (const WpSpaJson *self) { return g_strndup (self->data, self->size); } /*! * \brief Copies a spa json object * * \ingroup wpspajson * \param other a spa json object * \returns (transfer full): The newly copied spa json */ WpSpaJson * wp_spa_json_copy (WpSpaJson *other) { g_return_val_if_fail (other, NULL); g_return_val_if_fail (other->json, NULL); return wp_spa_json_new (other->data, other->size); } /*! * \brief Checks if the json is the unique owner of its data or not * * \ingroup wpspajson * \param self a spa json object * \returns TRUE if the json owns the data, FALSE otherwise. */ gboolean wp_spa_json_is_unique_owner (WpSpaJson *self) { return g_ref_count_compare (&self->ref, 1) && !(self->flags & FLAG_NO_OWNERSHIP); } /*! * \brief If \a self is not uniquely owned already, then it is unrefed and a * copy of it is returned instead. You should always consider \a self as unsafe * to use after this call and you should use the returned object instead. * * \ingroup wpspajson * \param self (transfer full): a spa json object * \returns (transfer full): the uniquely owned spa json object which may or may * not be the same as \a self. */ WpSpaJson * wp_spa_json_ensure_unique_owner (WpSpaJson *self) { WpSpaJson *copy = NULL; if (wp_spa_json_is_unique_owner (self)) return self; copy = wp_spa_json_copy (self); wp_spa_json_unref (self); return copy; } /*! * \brief Creates a spa json of type NULL * \ingroup wpspajson * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_null (void) { return wp_spa_json_new_from_builder ( wp_spa_json_builder_new_formatted ("%s", "null")); } /*! * \brief Creates a spa json of type boolean * * \ingroup wpspajson * \param value the boolean value * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_boolean (gboolean value) { return wp_spa_json_new_from_builder ( wp_spa_json_builder_new_formatted ("%s", value ? "true" : "false")); } /*! * \brief Creates a spa json of type int * * \ingroup wpspajson * \param value the int value * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_int (gint value) { return wp_spa_json_new_from_builder ( wp_spa_json_builder_new_formatted ("%d", value)); } /*! * \brief Creates a spa json of type float * * \ingroup wpspajson * \param value the float value * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_float (float value) { return wp_spa_json_new_from_builder ( wp_spa_json_builder_new_formatted ("%.6f", value)); } static void ensure_allocated_max_size (WpSpaJsonBuilder *self, size_t size) { size_t new_size = self->size + size + 1; /* '\0' because of vsnprintf */ if (new_size > self->max_size) { size_t next_size = new_size * 2; self->data = g_realloc (self->data, next_size); self->max_size = next_size; } } /*! * \brief Creates a spa json of type string * * \ingroup wpspajson * \param value the string value * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_string (const gchar *value) { WpSpaJsonBuilder *b = wp_spa_json_builder_new_empty (strlen (value)); size_t enc_size = spa_json_encode_string (b->data + b->size, b->max_size - b->size, value); if (enc_size + 1 > b->max_size - b->size) { ensure_allocated_max_size (b, enc_size); enc_size = spa_json_encode_string (b->data + b->size, b->max_size - b->size, value); g_assert (enc_size < b->max_size - b->size); } b->size += enc_size; return wp_spa_json_new_from_builder (b); } /* Args is not a pointer in some architectures, so this needs to be a macro to * avoid args being copied */ #define wp_spa_json_builder_add_value(self,fmt,args) \ do { \ switch (*fmt) { \ case 'n': \ wp_spa_json_builder_add_null (self); \ break; \ case 'b': \ wp_spa_json_builder_add_boolean (self, va_arg(args, gboolean)); \ break; \ case 'i': \ wp_spa_json_builder_add_int (self, va_arg(args, gint)); \ break; \ case 'f': \ wp_spa_json_builder_add_float (self, (float)va_arg(args, double)); \ break; \ case 's': \ wp_spa_json_builder_add_string (self, va_arg(args, const gchar *)); \ break; \ case 'J': \ wp_spa_json_builder_add_json (self, va_arg(args, WpSpaJson *)); \ break; \ default: \ break; \ } \ } while(false) /*! * \brief Creates a spa json of type array * * \ingroup wpspajson * \param format (nullable): the first value format ("n", "b", "i", "f", "s" or "J") * \param ... a list of array types and values, followed by NULL * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_array (const gchar *format, ...) { va_list args; WpSpaJson *res; va_start (args, format); res = wp_spa_json_new_array_valist (format, args); va_end (args); return res; } /*! * \brief This is the `va_list` version of wp_spa_json_new_array() * * \ingroup wpspajson * \param format (nullable): the first value format ("n", "b", "i", "f", "s" or "J") * \param args the variable arguments passed to wp_spa_json_new_array() * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_array_valist (const gchar *format, va_list args) { g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_array (); if (!format) return wp_spa_json_builder_end (b); wp_spa_json_builder_add_value (b, format, args); wp_spa_json_builder_add_valist (b, args); return wp_spa_json_builder_end (b); } /*! * \brief Creates a spa json of type object * * \ingroup wpspajson * \param key (nullable): the first object property key * \param format (nullable): the first property format ("n", "b", "i", "f", "s" or "J") * \param ... a list of object properties and values, followed by NULL * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_object (const gchar *key, const gchar *format, ...) { va_list args; WpSpaJson *res; va_start (args, format); res = wp_spa_json_new_object_valist (key, format, args); va_end (args); return res; } /*! * \brief This is the `va_list` version of wp_spa_json_new_object() * * \ingroup wpspajson * \param key (nullable): the first object property key * \param format (nullable): the first property format ("n", "b", "i", "f", "s" or "J") * \param args the variable arguments passed to wp_spa_json_new_object() * \returns (transfer full): The new spa json */ WpSpaJson * wp_spa_json_new_object_valist (const gchar *key, const gchar *format, va_list args) { g_autoptr (WpSpaJsonBuilder) b = wp_spa_json_builder_new_object (); if (!key || !format) return wp_spa_json_builder_end (b); wp_spa_json_builder_add_property (b, key); wp_spa_json_builder_add_value (b, format, args); wp_spa_json_builder_add_valist (b, args); return wp_spa_json_builder_end (b); } /*! * \brief Checks wether the spa json is of type null or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type null, FALSE otherwise */ gboolean wp_spa_json_is_null (WpSpaJson *self) { return spa_json_is_null (self->data, self->size); } /*! * \brief Checks wether the spa json is of type boolean or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type boolean, FALSE otherwise */ gboolean wp_spa_json_is_boolean (WpSpaJson *self) { return spa_json_is_bool (self->data, self->size); } /*! * \brief Checks wether the spa json is of type int or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type int, FALSE otherwise */ gboolean wp_spa_json_is_int (WpSpaJson *self) { return spa_json_is_int (self->data, self->size); } /*! * \brief Checks wether the spa json is of type float or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type float, FALSE otherwise */ gboolean wp_spa_json_is_float (WpSpaJson *self) { return spa_json_is_float (self->data, self->size); } /*! * \brief Checks wether the spa json is of type string or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type string, FALSE otherwise */ gboolean wp_spa_json_is_string (WpSpaJson *self) { return spa_json_is_string (self->data, self->size); } /*! * \brief Checks whether the spa json is of type array or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type array, FALSE otherwise */ gboolean wp_spa_json_is_array (WpSpaJson *self) { return spa_json_is_array (self->data, self->size); } /*! * \brief Checks whether the spa json is of type container or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type container, FALSE otherwise */ gboolean wp_spa_json_is_container (WpSpaJson *self) { return spa_json_is_container (self->data, self->size); } /*! * \brief Checks whether the spa json is of type object or not * * \ingroup wpspajson * \param self the spa json object * \returns TRUE if it is of type object, FALSE otherwise */ gboolean wp_spa_json_is_object (WpSpaJson *self) { return spa_json_is_object (self->data, self->size); } gboolean wp_spa_json_parse_boolean_internal (const gchar *data, int len, gboolean *value) { bool v = false; if (spa_json_parse_bool (data, len, &v) < 0) return FALSE; *value = v ? TRUE : FALSE; return TRUE; } /*! * \brief Parses the boolean value of a spa json object * * \ingroup wpspajson * \param self the spa json object * \param value (out): the boolean value * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parse_boolean (WpSpaJson *self, gboolean *value) { return wp_spa_json_parse_boolean_internal (self->data, self->size, value); } /*! * \brief Parses the int value of a spa json object * * \ingroup wpspajson * \param self the spa json object * \param value (out): the int value * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parse_int (WpSpaJson *self, gint *value) { return spa_json_parse_int (self->data, self->size, value) >= 0; } /*! * \brief Parses the float value of a spa json object * * \ingroup wpspajson * \param self the spa json object * \param value (out): the float value * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parse_float (WpSpaJson *self, float *value) { return spa_json_parse_float (self->data, self->size, value) >= 0; } static gchar * wp_spa_json_parse_string_internal (const gchar *data, int len) { gchar *res = g_new0 (gchar, len+1); if (res) spa_json_parse_string (data, len, res); return res; } /*! * \brief Parses the string value of a spa json object * * \ingroup wpspajson * \param self the spa json object * \returns (transfer full): The newly allocated parsed string */ gchar * wp_spa_json_parse_string (WpSpaJson *self) { return wp_spa_json_parse_string_internal (self->data, self->size); } /*! * \brief Parses the array types and values of a spa json object * * \ingroup wpspajson * \param self the spa json object * \param ... (out): the list of array types and values, followed by NULL * \returns TRUE if the types and values were obtained, FALSE otherwise */ gboolean wp_spa_json_parse_array (WpSpaJson *self, ...) { va_list args; gboolean res; va_start (args, self); res = wp_spa_json_parse_array_valist (self, args); va_end (args); return res; } /*! * \brief This is the `va_list` version of wp_spa_json_parse_array() * * \ingroup wpspajson * \param self the spa json object * \param args (out): the variable arguments passed to wp_spa_json_parse_array() * \returns TRUE if the types and values were obtained, FALSE otherwise */ gboolean wp_spa_json_parse_array_valist (WpSpaJson *self, va_list args) { g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_array (self); gboolean res = wp_spa_json_parser_get_valist (p, args); if (res) wp_spa_json_parser_end (p); return res; } /*! * \brief Parses the object properties and values of a spa json object * * \ingroup wpspajson * \param self the spa json object * \param ... (out): the list of object properties and values, followed by NULL * \returns TRUE if the properties and values were obtained, FALSE otherwise */ gboolean wp_spa_json_parse_object (WpSpaJson *self, ...) { va_list args; gboolean res; va_start (args, self); res = wp_spa_json_parse_object_valist (self, args); va_end (args); return res; } /*! * \brief This is the `va_list` version of wp_spa_json_parse_object() * * \ingroup wpspajson * \param self the spa json object * \param args (out): the variable arguments passed to wp_spa_json_parse_object() * \returns TRUE if the properties and values were obtained, FALSE otherwise */ gboolean wp_spa_json_parse_object_valist (WpSpaJson *self, va_list args) { g_autoptr (WpSpaJsonParser) p = wp_spa_json_parser_new_object (self); gboolean res = wp_spa_json_parser_get_valist (p, args); if (res) wp_spa_json_parser_end (p); return res; } /* Args is not a pointer in some architectures, so this needs to be a macro to * avoid args being copied */ #define wp_spa_json_parse_value(data,len,fmt,args) \ do { \ switch (*fmt) { \ case 'n': \ if (!spa_json_is_null (data, len)) \ return FALSE; \ break; \ case 'b': \ if (!wp_spa_json_parse_boolean_internal (data, len, \ va_arg(args, gboolean *))) \ return FALSE; \ break; \ case 'i': \ if (spa_json_parse_int (data, len, va_arg(args, gint *)) < 0) \ return FALSE; \ break; \ case 'f': \ if (spa_json_parse_float (data, len, va_arg(args, float *)) < 0) \ return FALSE; \ break; \ case 's': { \ gchar *str = wp_spa_json_parse_string_internal (data, len); \ if (!str) \ return FALSE; \ *va_arg(args, gchar **) = str; \ break; \ } \ case 'J': { \ WpSpaJson *j = wp_spa_json_new (data, len); \ if (!j) \ return FALSE; \ *va_arg(args, WpSpaJson **) = j; \ break; \ } \ default: \ return FALSE; \ } \ } while(false) /*! * \brief Parses the object property values of a spa json object * * \ingroup wpspajson * \param self the spa json object * \param ... the list of property names, formats and values, followed by NULL * \returns TRUE if the properties and values were obtained, FALSE otherwise */ gboolean wp_spa_json_object_get (WpSpaJson *self, ...) { va_list args; gboolean res; va_start (args, self); res = wp_spa_json_object_get_valist (self, args); va_end (args); return res; } /*! * \brief This is the `va_list` version of wp_spa_json_object_get() * * \ingroup wpspajson * \param self the spa json object * \param args the variable arguments passed to wp_spa_json_object_get() * \returns TRUE if the properties and values were obtained, FALSE otherwise */ gboolean wp_spa_json_object_get_valist (WpSpaJson *self, va_list args) { g_auto (GValue) item = G_VALUE_INIT; g_autoptr (WpIterator) it = NULL; const gchar *lookup_key = NULL; const gchar *lookup_fmt = NULL; g_return_val_if_fail (wp_spa_json_is_object (self), FALSE); lookup_key = va_arg(args, const gchar *); if (!lookup_key) return TRUE; lookup_fmt = va_arg(args, const gchar *); if (!lookup_fmt) return FALSE; it = wp_spa_json_new_iterator (self); for (; wp_iterator_next (it, &item); g_value_unset (&item)) { WpSpaJson *key = g_value_get_boxed (&item); g_autofree gchar *key_str = NULL; WpSpaJson *value = NULL; key_str = wp_spa_json_parse_string (key); g_return_val_if_fail (key_str, FALSE); g_value_unset (&item); if (!wp_iterator_next (it, &item)) return FALSE; value = g_value_get_boxed (&item); if (g_strcmp0 (key_str, lookup_key) == 0) { wp_spa_json_parse_value (value->data, value->size, lookup_fmt, args); lookup_key = va_arg(args, const gchar *); if (!lookup_key) return TRUE; lookup_fmt = va_arg(args, const gchar *); if (!lookup_fmt) return FALSE; wp_iterator_reset (it); } } return FALSE; } /*! * \brief Increases the reference count of a spa json builder * * \ingroup wpspajson * \param self a spa json builder object * \returns (transfer full): \a self with an additional reference count on it */ WpSpaJsonBuilder * wp_spa_json_builder_ref (WpSpaJsonBuilder *self) { return (WpSpaJsonBuilder *) g_rc_box_acquire ((gpointer) self); } static void wp_spa_json_builder_free (WpSpaJsonBuilder *self) { g_clear_pointer (&self->data, g_free); } /*! * \brief Decreases the reference count on \a self and frees it when the ref * count reaches zero. * * \ingroup wpspajson * \param self (transfer full): a spa json builder object */ void wp_spa_json_builder_unref (WpSpaJsonBuilder *self) { g_rc_box_release_full (self, (GDestroyNotify) wp_spa_json_builder_free); } /*! * \brief Creates a spa json builder of type array * * \ingroup wpspajson * \returns (transfer full): the new spa json builder */ WpSpaJsonBuilder * wp_spa_json_builder_new_array (void) { WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder); self->add_separator = FALSE; self->data = g_new0 (gchar, WP_SPA_JSON_BUILDER_INIT_SIZE); self->max_size = WP_SPA_JSON_BUILDER_INIT_SIZE; self->data[0] = '['; self->size = 1; return self; } /*! * \brief Creates a spa json builder of type object * * \ingroup wpspajson * \returns (transfer full): the new spa json builder */ WpSpaJsonBuilder * wp_spa_json_builder_new_object (void) { WpSpaJsonBuilder *self = g_rc_box_new0 (WpSpaJsonBuilder); self->add_separator = FALSE; self->data = g_new0 (gchar, WP_SPA_JSON_BUILDER_INIT_SIZE); self->max_size = WP_SPA_JSON_BUILDER_INIT_SIZE; self->data[0] = '{'; self->size = 1; return self; } static void ensure_separator (WpSpaJsonBuilder *self, gboolean for_property) { gboolean insert = (self->data[0] == '{' && for_property) || (self->data[0] == '[' && !for_property); if (insert) { if (self->add_separator) { ensure_allocated_max_size (self, 2); self->data[self->size++] = ','; self->data[self->size++] = ' '; } else { self->add_separator = TRUE; } } } static void builder_add_formatted (WpSpaJsonBuilder *self, const gchar *fmt, ...) { int s; va_list args; va_start (args, fmt); s = vsnprintf (self->data + self->size, self->max_size - self->size, fmt, args); va_end (args); g_return_if_fail (s > 0); self->size += s; } static void builder_add (WpSpaJsonBuilder *self, const gchar *data, size_t size) { g_return_if_fail (self->max_size - self->size >= size + 1); snprintf (self->data + self->size, size + 1, "%s", data); self->size += size; } /*! * \brief Adds a property into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param key the name of the property */ void wp_spa_json_builder_add_property (WpSpaJsonBuilder *self, const gchar *key) { size_t enc_size; ensure_separator (self, TRUE); enc_size = spa_json_encode_string (self->data + self->size, self->max_size - self->size, key); if (enc_size + 2 > self->max_size - self->size) { ensure_allocated_max_size (self, enc_size + 1); enc_size = spa_json_encode_string (self->data + self->size, self->max_size - self->size, key); g_assert (enc_size + 1 < self->max_size - self->size); } self->data[self->size + enc_size] = ':'; self->size += enc_size + 1; } /*! * \brief Adds a null value into the builder * * \ingroup wpspajson * \param self the spa json builder object */ void wp_spa_json_builder_add_null (WpSpaJsonBuilder *self) { ensure_separator (self, FALSE); ensure_allocated_max_size (self, 4); builder_add_formatted (self, "%s", "null"); } /*! * \brief Adds a boolean value into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param value the boolean value */ void wp_spa_json_builder_add_boolean (WpSpaJsonBuilder *self, gboolean value) { ensure_separator (self, FALSE); ensure_allocated_max_size (self, value ? 4 : 5); builder_add_formatted (self, "%s", value ? "true" : "false"); } /*! * \brief Adds a int value into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param value the int value */ void wp_spa_json_builder_add_int (WpSpaJsonBuilder *self, gint value) { ensure_separator (self, FALSE); ensure_allocated_max_size (self, 16); builder_add_formatted (self, "%d", value); } /*! * \brief Adds a float value into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param value the float value */ void wp_spa_json_builder_add_float (WpSpaJsonBuilder *self, float value) { ensure_separator (self, FALSE); ensure_allocated_max_size (self, 32); builder_add_formatted (self, "%.6f", value); } /*! * \brief Adds a string value into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param value the string value */ void wp_spa_json_builder_add_string (WpSpaJsonBuilder *self, const gchar *value) { size_t enc_size; ensure_separator (self, FALSE); enc_size = spa_json_encode_string (self->data + self->size, self->max_size - self->size, value); if (enc_size + 1 > self->max_size - self->size) { ensure_allocated_max_size (self, enc_size); enc_size = spa_json_encode_string (self->data + self->size, self->max_size - self->size, value); g_assert (enc_size < self->max_size - self->size); } self->size += enc_size; } /*! * \brief Adds a json value into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param json (transfer none): the json value */ void wp_spa_json_builder_add_json (WpSpaJsonBuilder *self, WpSpaJson *json) { ensure_separator (self, FALSE); ensure_allocated_max_size (self, json->size); builder_add (self, json->data, json->size); } /*! * \brief Adds a json string into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param json_str the json string */ void wp_spa_json_builder_add_from_string (WpSpaJsonBuilder *self, const gchar *json_str) { wp_spa_json_builder_add_from_stringn (self, json_str, strlen (json_str)); } /*! * \brief Adds a json string with specific length into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param json_str the json string * \param len the specific length of the json string */ void wp_spa_json_builder_add_from_stringn (WpSpaJsonBuilder *self, const gchar *json_str, size_t len) { ensure_separator (self, FALSE); ensure_allocated_max_size (self, len); builder_add (self, json_str, len); } /*! * \brief Adds values into the builder * * \ingroup wpspajson * \param self the spa json builder object * \param ... the json values */ void wp_spa_json_builder_add (WpSpaJsonBuilder *self, ...) { va_list args; va_start (args, self); wp_spa_json_builder_add_valist (self, args); va_end (args); } /*! * \brief This is the `va_list` version of wp_spa_json_builder_add() * * \ingroup wpspajson * \param self the spa json builder object * \param args the variable arguments passed to wp_spa_json_builder_add() */ void wp_spa_json_builder_add_valist (WpSpaJsonBuilder *self, va_list args) { do { const char *format = NULL; /* add property key if object */ if (self->data[0] == '{') { const gchar *key = va_arg(args, const gchar *); if (!key) return; wp_spa_json_builder_add_property (self, key); } /* get value format */ format = va_arg(args, const gchar *); if (!format) return; /* add value */ wp_spa_json_builder_add_value (self, format, args); } while (TRUE); } /*! * \brief Ends the builder process and returns the constructed spa json object * * \ingroup wpspajson * \param self the spa json builder object * \returns (transfer full): the constructed spa json object */ WpSpaJson * wp_spa_json_builder_end (WpSpaJsonBuilder *self) { /* close */ switch (self->data[0]) { case '[': /* array */ ensure_allocated_max_size (self, 2); self->data[self->size++] = ']'; self->data[self->size] = '\0'; break; case '{': /* object */ ensure_allocated_max_size (self, 2); self->data[self->size++] = '}'; self->data[self->size] = '\0'; break; default: break; } return wp_spa_json_new_from_builder (wp_spa_json_builder_ref (self)); } /*! * \brief Increases the reference count of a spa json parser * * \ingroup wpspajson * \param self a spa json parser object * \returns (transfer full): \a self with an additional reference count on it */ WpSpaJsonParser * wp_spa_json_parser_ref (WpSpaJsonParser *self) { return (WpSpaJsonParser *) g_rc_box_acquire ((gpointer) self); } static void wp_spa_json_parser_free (WpSpaJsonParser *self) { self->json = NULL; } /*! * \brief Decreases the reference count on \a self and frees it when the ref * count reaches zero. * * \ingroup wpspajson * \param self (transfer full): a spa json parser object */ void wp_spa_json_parser_unref (WpSpaJsonParser *self) { g_rc_box_release_full (self, (GDestroyNotify) wp_spa_json_parser_free); } /*! * \brief Creates a spa json array parser. The \a json object must be valid for * the entire life-cycle of the returned parser. * * \ingroup wpspajson * \param json the spa json array to parse * \returns (transfer full): The new spa json parser */ WpSpaJsonParser * wp_spa_json_parser_new_array (WpSpaJson *json) { WpSpaJsonParser *self; g_return_val_if_fail (wp_spa_json_is_array (json), NULL); self = g_rc_box_new0 (WpSpaJsonParser); self->json = json; self->data[0] = *json->json; spa_json_enter_array (&self->data[0], &self->data[1]); self->pos = &self->data[1]; return self; } /*! * \brief Creates a spa json object parser. The \a json object must be valid for * the entire life-cycle of the returned parser. * * \ingroup wpspajson * \param json the spa json object to parse * \returns (transfer full): The new spa json parser */ WpSpaJsonParser * wp_spa_json_parser_new_object (WpSpaJson *json) { WpSpaJsonParser *self; g_return_val_if_fail (wp_spa_json_is_object (json), NULL); self = g_rc_box_new0 (WpSpaJsonParser); self->json = json; self->data[0] = *json->json; spa_json_enter_object (&self->data[0], &self->data[1]); self->pos = &self->data[1]; return self; } /*! * \brief Creates a new spa json parser for undefined type of data. The \a json * object must be valid for the entire life-cycle of the returned parser. * * This function allows creating a parser object for any type of spa json and is * mostly useful to parse non-standard JSON data that should be treated as if it * were an object or array, but does not start with a '{' or '[' character. Such * data can be for instance a comma-separated list of single values (array) or * key-value pairs (object). Such data is also the main configuration file, * which is an object but doesn't start with a '{' character. * * \note If the data is an array or object, the parser will not enter it and the * only token it will be able to parse is the same \a json object that is passed * in as an argument. Use wp_spa_json_parser_new_array() or * wp_spa_json_parser_new_object() to parse arrays or objects. * * \ingroup wpspajson * \param json the spa json to parse * \returns (transfer full): The new spa json parser * \since 0.5.0 */ WpSpaJsonParser * wp_spa_json_parser_new_undefined (WpSpaJson *json) { WpSpaJsonParser *self; self = g_rc_box_new0 (WpSpaJsonParser); self->json = json; self->data[0] = *json->json; self->pos = &self->data[0]; return self; } static int check_nested_size (struct spa_json *parent, const gchar *data, int size) { const gchar *nested_data; int nested_size; struct spa_json nested[2]; /* only arrays and objects are considered nested data */ if (!spa_json_is_array (data, size) && !spa_json_is_object (data, size)) return 0; /* enter */ nested[0] = *parent; spa_json_enter (&nested[0], &nested[1]); /* recursively advance */ while ((nested_size = spa_json_next (&nested[1], &nested_data)) > 0) { if (check_nested_size (&nested[1], nested_data, nested_size) < 0) return -1; } if (nested_size < 0) return -1; /* advance one more time to reach end of nested data */ if (spa_json_next (&nested[1], &nested_data) < 0) return -1; return nested_data - data; } static gboolean wp_spa_json_parser_advance (WpSpaJsonParser *self) { const gchar *data = NULL; int size, nested_size; if (!self->pos) return FALSE; /* advance */ size = spa_json_next (self->pos, &data); if (size <= 0) return FALSE; g_return_val_if_fail (data != NULL, FALSE); /* if array or object, add the nested size */ nested_size = check_nested_size (self->pos, data, size); if (nested_size < 0) return FALSE; size += nested_size; /* update current */ spa_json_init (&self->curr, data, size); return TRUE; } /*! * \brief Gets the null value from a spa json parser * * \ingroup wpspajson * \param self the spa json parser object * \returns TRUE if the null value is present, FALSE otherwise */ gboolean wp_spa_json_parser_get_null (WpSpaJsonParser *self) { return wp_spa_json_parser_advance (self) && spa_json_is_null (self->curr.cur, self->curr.end - self->curr.cur); } /*! * \brief Gets the boolean value from a spa json parser * * \ingroup wpspajson * \param self the spa json parser object * \param value (out): the boolean value * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parser_get_boolean (WpSpaJsonParser *self, gboolean *value) { return wp_spa_json_parser_advance (self) && wp_spa_json_parse_boolean_internal (self->curr.cur, self->curr.end - self->curr.cur, value); } /*! * \brief Gets the int value from a spa json parser object * * \ingroup wpspajson * \param self the spa json parser object * \param value (out): the int value * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parser_get_int (WpSpaJsonParser *self, gint *value) { return wp_spa_json_parser_advance (self) && spa_json_parse_int (self->curr.cur, self->curr.end - self->curr.cur, value) >= 0; } /*! * \brief Gets the float value from a spa json parser object * * \ingroup wpspajson * \param self the spa json parser object * \param value (out): the float value * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parser_get_float (WpSpaJsonParser *self, float *value) { return wp_spa_json_parser_advance (self) && spa_json_parse_float (self->curr.cur, self->curr.end - self->curr.cur, value) >= 0; } /*! * \brief Gets the string value from a spa json parser object * * \ingroup wpspajson * \param self the spa json parser object * \returns (transfer full): The newly allocated parsed string */ gchar * wp_spa_json_parser_get_string (WpSpaJsonParser *self) { return wp_spa_json_parser_advance (self) ? wp_spa_json_parse_string_internal (self->curr.cur, self->curr.end - self->curr.cur) : NULL; } /*! * \brief Gets the spa json value from a spa json parser object * * \note the returned spa json object references the original data instead * of copying it, therefore the original data must be valid for the entire * life-cycle of the returned object * * \ingroup wpspajson * \param self the spa json parser object * \returns (transfer full): The spa json value or NULL if it could not be * obtained */ WpSpaJson * wp_spa_json_parser_get_json (WpSpaJsonParser *self) { return wp_spa_json_parser_advance (self) ? wp_spa_json_new_wrap_stringn (self->curr.cur, self->curr.end - self->curr.cur) : NULL; } gboolean wp_spa_json_parser_get_value (WpSpaJsonParser *self, const gchar *fmt, va_list args) { if (wp_spa_json_parser_advance (self)) { wp_spa_json_parse_value (self->curr.cur, self->curr.end - self->curr.cur, fmt, args); return TRUE; } return FALSE; } /*! * \brief Gets the values from a spa json parser object * * \ingroup wpspajson * \param self the spa json parser object * \param ... (out): a list of values to get, followed by NULL * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parser_get (WpSpaJsonParser *self, ...) { gboolean res; va_list args; va_start (args, self); res = wp_spa_json_parser_get_valist (self, args); va_end (args); return res; } /*! * \brief This is the `va_list` version of wp_spa_json_parser_get() * * \ingroup wpspajson * \param self the spa json parser object * \param args the variable arguments passed to wp_spa_json_parser_get() * \returns TRUE if the value was obtained, FALSE otherwise */ gboolean wp_spa_json_parser_get_valist (WpSpaJsonParser *self, va_list args) { do { const char *format = NULL; /* parse property key if object */ if (self->json->data[0] == '{') { gchar **key = va_arg(args, gchar **); if (!key) return TRUE; *key = wp_spa_json_parser_get_string (self); if (!(*key)) return FALSE; } /* parse format */ format = va_arg(args, const gchar *); if (!format) return TRUE; /* advance */ if (!wp_spa_json_parser_advance (self)) return FALSE; /* parse value */ wp_spa_json_parse_value (self->curr.cur, self->curr.end - self->curr.cur, format, args); } while (TRUE); return FALSE; } void wp_spa_json_parser_end (WpSpaJsonParser *self) { self->pos = NULL; } struct _WpSpaJsonIterator { WpSpaJson *json; WpSpaJsonParser *parser; }; typedef struct _WpSpaJsonIterator WpSpaJsonIterator; static void wp_spa_json_iterator_reset (WpIterator *iterator) { WpSpaJsonIterator *self = wp_iterator_get_user_data (iterator); g_clear_pointer (&self->parser, wp_spa_json_parser_unref); } static gboolean wp_spa_json_iterator_next (WpIterator *iterator, GValue *item) { WpSpaJsonIterator *self = wp_iterator_get_user_data (iterator); /* init iterator if first time */ if (!self->parser) { switch (self->json->json->cur[0]) { case '[': /* array */ self->parser = wp_spa_json_parser_new_array (self->json); break; case '{': /* object */ self->parser = wp_spa_json_parser_new_object (self->json); break; default: return FALSE; } } /* advance */ if (!wp_spa_json_parser_advance (self->parser)) return FALSE; if (item) { g_value_init (item, WP_TYPE_SPA_JSON); g_value_take_boxed (item, wp_spa_json_new_wrap (&self->parser->curr)); } return TRUE; } static void wp_spa_json_iterator_finalize (WpIterator *iterator) { WpSpaJsonIterator *self = wp_iterator_get_user_data (iterator); g_clear_pointer (&self->parser, wp_spa_json_parser_unref); g_clear_pointer (&self->json, wp_spa_json_unref); } /*! * \brief Creates a new iterator for a spa json object. * * \ingroup wpspajson * \param self the spa json object * \returns (transfer full): the new spa json iterator */ WpIterator * wp_spa_json_new_iterator (WpSpaJson *self) { static const WpIteratorMethods methods = { .version = WP_ITERATOR_METHODS_VERSION, .reset = wp_spa_json_iterator_reset, .next = wp_spa_json_iterator_next, .fold = NULL, .foreach = NULL, .finalize = wp_spa_json_iterator_finalize }; WpIterator *it = wp_iterator_new (&methods, sizeof (WpSpaJsonIterator)); WpSpaJsonIterator *jit = wp_iterator_get_user_data (it); jit->json = wp_spa_json_ref (self); jit->parser = NULL; return it; }