From 6753c51ab8db14fd8e359f519678035968f73f14 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 26 Feb 2026 10:48:53 +0100 Subject: [PATCH] spa: add a new json-builder helper It keeps track of the json bits like commas and indentation, supports colors and recursive reformatting. It also supports a simple mode that implements the SPA syntax. --- spa/include/spa/utils/json-builder.h | 440 +++++++++++++++++++++++++++ spa/include/spa/utils/json-core.h | 44 +++ 2 files changed, 484 insertions(+) create mode 100644 spa/include/spa/utils/json-builder.h diff --git a/spa/include/spa/utils/json-builder.h b/spa/include/spa/utils/json-builder.h new file mode 100644 index 000000000..554136b90 --- /dev/null +++ b/spa/include/spa/utils/json-builder.h @@ -0,0 +1,440 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_UTILS_JSON_BUILDER_H +#define SPA_UTILS_JSON_BUILDER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + +#ifndef SPA_API_JSON_BUILDER + #ifdef SPA_API_IMPL + #define SPA_API_JSON_BUILDER SPA_API_IMPL + #else + #define SPA_API_JSON_BUILDER static inline + #endif +#endif + +/** \defgroup spa_json_builder JSON builder + * JSON builder functions + */ + +/** + * \addtogroup spa_json_builder + * \{ + */ + +struct spa_json_builder { + FILE *f; +#define SPA_JSON_BUILDER_FLAG_CLOSE (1<<0) +#define SPA_JSON_BUILDER_FLAG_INDENT (1<<1) +#define SPA_JSON_BUILDER_FLAG_SPACE (1<<2) +#define SPA_JSON_BUILDER_FLAG_PRETTY (SPA_JSON_BUILDER_FLAG_INDENT|SPA_JSON_BUILDER_FLAG_SPACE) +#define SPA_JSON_BUILDER_FLAG_COLOR (1<<3) +#define SPA_JSON_BUILDER_FLAG_SIMPLE (1<<4) + uint32_t flags; + uint32_t indent_off; + uint32_t level; + uint32_t indent; + uint32_t count; + const char *delim; + const char *comma; + const char *key_sep; +#define SPA_JSON_BUILDER_COLOR_NORMAL 0 +#define SPA_JSON_BUILDER_COLOR_KEY 1 +#define SPA_JSON_BUILDER_COLOR_LITERAL 2 +#define SPA_JSON_BUILDER_COLOR_NUMBER 3 +#define SPA_JSON_BUILDER_COLOR_STRING 4 +#define SPA_JSON_BUILDER_COLOR_CONTAINER 5 + const char *color[8]; +}; + +SPA_API_JSON_BUILDER int spa_json_builder_file(struct spa_json_builder *b, FILE *f, uint32_t flags) +{ + bool color = flags & SPA_JSON_BUILDER_FLAG_COLOR; + bool simple = flags & SPA_JSON_BUILDER_FLAG_SIMPLE; + bool space = flags & SPA_JSON_BUILDER_FLAG_SPACE; + b->f = f; + b->flags = flags; + b->level = 0; + b->indent_off = 0; + b->indent = 2; + b->count = 0; + b->delim = ""; + b->comma = simple ? space ? "" : " " : ","; + b->key_sep = simple ? space ? " =" : "=" : ":"; + b->color[0] = (color ? SPA_ANSI_RESET : ""); + b->color[1] = (color ? SPA_ANSI_BRIGHT_BLUE : ""); + b->color[2] = (color ? SPA_ANSI_BRIGHT_MAGENTA : ""); + b->color[3] = (color ? SPA_ANSI_BRIGHT_CYAN : ""); + b->color[4] = (color ? SPA_ANSI_BRIGHT_GREEN : ""); + b->color[5] = (color ? SPA_ANSI_BRIGHT_YELLOW : ""); + return 0; +} + +SPA_API_JSON_BUILDER int spa_json_builder_memstream(struct spa_json_builder *b, + char **mem, size_t *size, uint32_t flags) +{ + FILE *f; + if ((f = open_memstream(mem, size)) == NULL) + return -errno; + return spa_json_builder_file(b, f, flags | SPA_JSON_BUILDER_FLAG_CLOSE); +} + +SPA_API_JSON_BUILDER int spa_json_builder_membuf(struct spa_json_builder *b, + char *mem, size_t size, uint32_t flags) +{ + FILE *f; + if ((f = fmemopen(mem, size, "w")) == NULL) + return -errno; + return spa_json_builder_file(b, f, flags | SPA_JSON_BUILDER_FLAG_CLOSE); +} + +SPA_API_JSON_BUILDER void spa_json_builder_close(struct spa_json_builder *b) +{ + if (b->flags & SPA_JSON_BUILDER_FLAG_CLOSE) + fclose(b->f); +} + +SPA_API_JSON_BUILDER int spa_json_builder_encode_string(struct spa_json_builder *b, + bool raw, const char *before, const char *val, int size, const char *after) +{ + FILE *f = b->f; + int i, len; + if (raw) { + len = fprintf(f, "%s%.*s%s", before, size, val, after) - 1; + } else { + len = fprintf(f, "%s\"", before); + for (i = 0; i < size && val[i]; i++) { + char v = val[i]; + switch (v) { + case '\n': len += fprintf(f, "\\n"); break; + case '\r': len += fprintf(f, "\\r"); break; + case '\b': len += fprintf(f, "\\b"); break; + case '\t': len += fprintf(f, "\\t"); break; + case '\f': len += fprintf(f, "\\f"); break; + case '\\': + case '"': len += fprintf(f, "\\%c", v); break; + default: + if (v > 0 && v < 0x20) + len += fprintf(f, "\\u%04x", v); + else + len += fprintf(f, "%c", v); + break; + } + } + len += fprintf(f, "\"%s", after); + } + return len-1; +} + +SPA_API_JSON_BUILDER +void spa_json_builder_add_simple(struct spa_json_builder *b, const char *key, int key_len, + char type, const char *val, int val_len) +{ + bool indent = b->indent_off == 0 && (b->flags & SPA_JSON_BUILDER_FLAG_INDENT); + bool space = b->flags & SPA_JSON_BUILDER_FLAG_SPACE; + bool raw = true, simple = b->flags & SPA_JSON_BUILDER_FLAG_SIMPLE; + int color; + + if (val == NULL || val_len == 0) { + val = "null"; + val_len = 4; + type = 'l'; + } + if (type == 0) { + if (spa_json_is_container(val, val_len)) + type = simple ? 'C' : 'S'; + else if (val_len > 0 && (*val == '}' || *val == ']')) + type = 'e'; + else if (spa_json_is_null(val, val_len) || + spa_json_is_bool(val, val_len)) + type = 'l'; + else if (spa_json_is_float(val, val_len) || + spa_json_is_int(val, val_len)) + type = 'd'; + else if (spa_json_is_string(val, val_len)) + type = 's'; + else + type = 'S'; + } + switch (type) { + case 'e': + b->level -= b->indent; + b->delim = ""; + break; + } + + fprintf(b->f, "%s%s%*s", b->delim, indent ? b->count == 0 ? "" : "\n" : space ? " " : "", + indent ? b->level : 0, ""); + if (key) { + bool key_raw = (simple && spa_json_make_simple_string(&key, &key_len)) || + spa_json_is_string(key, key_len); + spa_json_builder_encode_string(b, key_raw, + b->color[1], key, key_len, b->color[0]); + fprintf(b->f, "%s%s", b->key_sep, space ? " " : ""); + } + b->delim = b->comma; + switch (type) { + case 'c': + color = SPA_JSON_BUILDER_COLOR_NORMAL; + val_len = 1; + b->delim = ""; + b->level += b->indent; + if (val[1] == '-') b->indent_off++; + break; + case 'e': + color = SPA_JSON_BUILDER_COLOR_NORMAL; + val_len = 1; + if (val[1] == '-') b->indent_off--; + break; + case 'l': + color = SPA_JSON_BUILDER_COLOR_LITERAL; + break; + case 'd': + color = SPA_JSON_BUILDER_COLOR_NUMBER; + break; + case 's': + color = SPA_JSON_BUILDER_COLOR_STRING; + break; + case 'C': + color = SPA_JSON_BUILDER_COLOR_CONTAINER; + break; + default: + color = SPA_JSON_BUILDER_COLOR_STRING; + raw = simple && spa_json_make_simple_string(&val, &val_len); + break; + } + spa_json_builder_encode_string(b, raw, b->color[color], val, val_len, b->color[0]); + b->count++; +} + +SPA_API_JSON_BUILDER void spa_json_builder_object_push(struct spa_json_builder *b, + const char *key, const char *val) +{ + spa_json_builder_add_simple(b, key, INT_MAX, 'c', val, INT_MAX); +} +SPA_API_JSON_BUILDER void spa_json_builder_pop(struct spa_json_builder *b, + const char *val) +{ + spa_json_builder_add_simple(b, NULL, 0, 'e', val, INT_MAX); +} +SPA_API_JSON_BUILDER void spa_json_builder_object_null(struct spa_json_builder *b, + const char *key) +{ + spa_json_builder_add_simple(b, key, INT_MAX, 'l', "null", 4); +} +SPA_API_JSON_BUILDER void spa_json_builder_object_bool(struct spa_json_builder *b, + const char *key, bool val) +{ + spa_json_builder_add_simple(b, key, INT_MAX, 'l', val ? "true" : "false", INT_MAX); +} +SPA_API_JSON_BUILDER void spa_json_builder_object_int(struct spa_json_builder *b, + const char *key, int64_t val) +{ + char str[128]; + snprintf(str, sizeof(str), "%" PRIi64, val); + spa_json_builder_add_simple(b, key, INT_MAX, 'd', str, INT_MAX); +} +SPA_API_JSON_BUILDER void spa_json_builder_object_uint(struct spa_json_builder *b, + const char *key, uint64_t val) +{ + char str[128]; + snprintf(str, sizeof(str), "%" PRIu64, val); + spa_json_builder_add_simple(b, key, INT_MAX, 'd', str, INT_MAX); +} + +SPA_API_JSON_BUILDER void spa_json_builder_object_double(struct spa_json_builder *b, + const char *key, double val) +{ + char str[64]; + spa_json_format_float(str, sizeof(str), (float)val); + spa_json_builder_add_simple(b, key, INT_MAX, 'd', str, INT_MAX); +} + +SPA_API_JSON_BUILDER void spa_json_builder_object_string(struct spa_json_builder *b, + const char *key, const char *val) +{ + spa_json_builder_add_simple(b, key, INT_MAX, 'S', val, INT_MAX); +} +SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(3,0) +void spa_json_builder_object_stringv(struct spa_json_builder *b, + const char *key, const char *fmt, va_list va) +{ + char *val; + vasprintf(&val, fmt, va); + spa_json_builder_object_string(b, key, val); + free(val); +} + +SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(3,4) +void spa_json_builder_object_stringf(struct spa_json_builder *b, + const char *key, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + spa_json_builder_object_stringv(b, key, fmt, va); + va_end(va); +} + +SPA_API_JSON_BUILDER void spa_json_builder_object_value_iter(struct spa_json_builder *b, + struct spa_json *it, const char *key, int key_len, const char *val, int len) +{ + struct spa_json sub; + if (spa_json_is_array(val, len)) { + spa_json_builder_add_simple(b, key, key_len, 'c', "[", 1); + spa_json_enter(it, &sub); + while ((len = spa_json_next(&sub, &val)) > 0) + spa_json_builder_object_value_iter(b, &sub, NULL, 0, val, len); + spa_json_builder_pop(b, "]"); + } + else if (spa_json_is_object(val, len)) { + const char *k; + int kl; + spa_json_builder_add_simple(b, key, key_len, 'c', "{", 1); + spa_json_enter(it, &sub); + while ((kl = spa_json_next(&sub, &k)) > 0) { + if ((len = spa_json_next(&sub, &val)) < 0) + break; + spa_json_builder_object_value_iter(b, &sub, k, kl, val, len); + } + spa_json_builder_pop(b, "}"); + } + else { + spa_json_builder_add_simple(b, key, key_len, 0, val, len); + } +} +SPA_API_JSON_BUILDER void spa_json_builder_object_value_full(struct spa_json_builder *b, + bool recurse, const char *key, int key_len, const char *val, int val_len) +{ + if (!recurse || val == NULL) { + spa_json_builder_add_simple(b, key, key_len, 0, val, val_len); + } else { + struct spa_json it[1]; + const char *v; + if (spa_json_begin(&it[0], val, val_len, &v) >= 0) + spa_json_builder_object_value_iter(b, &it[0], key, key_len, val, val_len); + } +} +SPA_API_JSON_BUILDER void spa_json_builder_object_value(struct spa_json_builder *b, + bool recurse, const char *key, const char *val) +{ + spa_json_builder_object_value_full(b, recurse, key, key ? strlen(key) : 0, + val, val ? strlen(val) : 0); +} +SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(4,5) +void spa_json_builder_object_valuef(struct spa_json_builder *b, + bool recurse, const char *key, const char *fmt, ...) +{ + va_list va; + char *val; + va_start(va, fmt); + vasprintf(&val, fmt, va); + va_end(va); + spa_json_builder_object_value(b, recurse, key, val); + free(val); +} + + +/* array functions */ +SPA_API_JSON_BUILDER void spa_json_builder_array_push(struct spa_json_builder *b, + const char *val) +{ + spa_json_builder_object_push(b, NULL, val); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_null(struct spa_json_builder *b) +{ + spa_json_builder_object_null(b, NULL); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_bool(struct spa_json_builder *b, + bool val) +{ + spa_json_builder_object_bool(b, NULL, val); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_int(struct spa_json_builder *b, + int64_t val) +{ + spa_json_builder_object_int(b, NULL, val); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_uint(struct spa_json_builder *b, + uint64_t val) +{ + spa_json_builder_object_uint(b, NULL, val); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_double(struct spa_json_builder *b, + double val) +{ + spa_json_builder_object_double(b, NULL, val); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_string(struct spa_json_builder *b, + const char *val) +{ + spa_json_builder_object_string(b, NULL, val); +} +SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(2,3) +void spa_json_builder_array_stringf(struct spa_json_builder *b, + const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + spa_json_builder_object_stringv(b, NULL, fmt, va); + va_end(va); +} +SPA_API_JSON_BUILDER void spa_json_builder_array_value(struct spa_json_builder *b, + bool recurse, const char *val) +{ + spa_json_builder_object_value(b, recurse, NULL, val); +} +SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(3,4) +void spa_json_builder_array_valuef(struct spa_json_builder *b, bool recurse, const char *fmt, ...) +{ + va_list va; + char *val; + va_start(va, fmt); + vasprintf(&val, fmt, va); + va_end(va); + spa_json_builder_object_value(b, recurse, NULL, val); + free(val); +} + +SPA_API_JSON_BUILDER char *spa_json_builder_reformat(const char *json, uint32_t flags) +{ + struct spa_json_builder b; + char *mem; + size_t size; + int res; + if ((res = spa_json_builder_memstream(&b, &mem, &size, flags)) < 0) { + errno = -res; + return NULL; + } + spa_json_builder_array_value(&b, true, json); + spa_json_builder_close(&b); + return mem; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_UTILS_JSON_BUILDER_H */ diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h index aa36e2d51..eed208db6 100644 --- a/spa/include/spa/utils/json-core.h +++ b/spa/include/spa/utils/json-core.h @@ -399,6 +399,10 @@ SPA_API_JSON int spa_json_is_container(const char *val, int len) { return len > 0 && (*val == '{' || *val == '['); } +SPA_API_JSON int spa_json_is_container_end(const char *val, int len) +{ + return len > 0 && (*val == '}' || *val == '}'); +} /* object */ SPA_API_JSON int spa_json_is_object(const char *val, int len) @@ -510,6 +514,46 @@ SPA_API_JSON bool spa_json_is_string(const char *val, int len) { return len > 1 && *val == '"'; } +SPA_API_JSON bool spa_json_is_simple_string(const char *val, int size) +{ + int i; + static const char *REJECT = "\"\\'=:,{}[]()#"; + for (i = 0; i < size && val[i]; i++) { + if (val[i] <= 0x20 || strchr(REJECT, val[i]) != NULL) + return false; + } + return true; +} +SPA_API_JSON bool spa_json_make_simple_string(const char **val, int *len) +{ + int i, l = *len; + const char *v = *val; + static const char *REJECT = "\"\\'=:,{}[]()#"; + int trimmed = 0, bad = 0; + for (i = 0; i < l && v[i]; i++) { + if (i == 0 && v[0] == '\"') + trimmed++; + else if ((i+1 == l || !v[i+1]) && v[i] == '\"') + trimmed++; + else if (v[i] <= 0x20 || strchr(REJECT, v[i]) != NULL) + bad++; + } + if (trimmed == 0 && bad == 0 && i > 0) + return true; + else if (trimmed == 2) { + if (bad == 0 && i > 2 && + !spa_json_is_null(&v[1], i-2) && + !spa_json_is_bool(&v[1], i-2) && + !spa_json_is_float(&v[1], i-2) && + !spa_json_is_container(&v[1], i-2) && + !spa_json_is_container_end(&v[1], i-2)) { + (*len) = i-2; + (*val)++; + } + return true; + } + return false; +} SPA_API_JSON int spa_json_parse_hex(const char *p, int num, uint32_t *res) {