Files
wireplumber/lib/wp/spa-json.c

1587 lines
42 KiB
C

/* WirePlumber
*
* Copyright © 2022 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#define G_LOG_DOMAIN "wp-spa-json"
#include <spa/utils/defs.h>
#include <spa/utils/json.h>
#include "spa-json.h"
#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 that references the data in \a json_str. \a json_str
* is not copied, so it needs to stay alive.
*/
WpSpaJson *
wp_spa_json_new_from_string (const gchar *json_str)
{
return wp_spa_json_new_from_stringn(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 that references the data in \a json_str. \a json_str
* is not copied, so it needs to stay alive.
*
* \since 0.4.10
*/
WpSpaJson *
wp_spa_json_new_from_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 wether 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 wether 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_json (WpSpaJsonBuilder *self, WpSpaJson *json)
{
g_return_if_fail (self->max_size - self->size >= json->size + 1);
snprintf (self->data + self->size, json->size + 1, "%s", json->data);
self->size += json->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_json (self, json);
}
/*!
* \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;
}
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
*
* \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 (&self->curr) : 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;
}