From patchwork Wed Apr 21 12:27:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolas George X-Patchwork-Id: 27199 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a6b:5014:0:0:0:0:0 with SMTP id e20csp350319iob; Wed, 21 Apr 2021 05:28:40 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyqXZXfdx8igmHnJkwTJuBqwpR25ueU4GTr+O/4vAMlYbWxG2GvgQuFdVMXCx81Hyeva7Wr X-Received: by 2002:a17:907:9485:: with SMTP id dm5mr32579460ejc.194.1619008120599; Wed, 21 Apr 2021 05:28:40 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1619008120; cv=none; d=google.com; s=arc-20160816; b=Y29v8uUvMy1j1svQyeAidcGLyHUcSq8JgY5chSm1dRF6ofhlc3xs2MSbIpcQ7RfEig I2qgrKh2oVhFxipKy0ztVjAXX7rDQTy6Ss8JJM1Whq3v166sm32vWC+TTE0PG6y8CVso cKxgWLV9OE+oz5gwf9RUw0P8vjYnwYKWLwZxj2iFppztaPEhQ0MbrSbUyfYYhpuN9A2N I1abcbiFigFiK/tj8Z0JgMLY6JgSiMrlF96J3J2M9s1u4aIUDsADHb6FZlM5MoKFFu+H VKU1WbV+szQkkFopJh2hJPaK64NDW8hMynyJnl1/bR+otT3Bng2M/13q+tbKDqJJ6vwH s4PA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:references:in-reply-to:message-id :date:to:from:delivered-to; bh=L0lJ3ax6aJc5mOGkUD9OoRlhFa2BSsNOjiS9XCbY58s=; b=j4+Gjf1W0Cc0oxqhmJoBistEXTzFuSAsTEBx1yrs6h6RjmyIz26aNH/ZRgFgGoLoXH cTL4FKJny5uIT5FEHrDD1NZr37NELeIzQdh1E9mpFT9uX5egFwspIho06iW1CfJNRr4W LXQxagvIzACHjBl0UNX6mwW2O8K/rgrvgxq8ar1w7eiDZ3v/WqqU7oZRdlfFmnk8qRlX gwY7UCX2y3jHERjcC4R9B0Ro4Qg7yjCj35y8ISqNXsNsDROiDU9lK9duQXwMDpKTKAlP omYyuDGgGXz/TeT4mMvNhAsxqQzE4Vq2NKFgQsPgg+ZawOPymgt5xlWtRxSJnYIFk8CO CybA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id f9si1752069ejk.446.2021.04.21.05.28.40; Wed, 21 Apr 2021 05:28:40 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id D5F52689A19; Wed, 21 Apr 2021 15:27:24 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from nef.ens.fr (nef2.ens.fr [129.199.96.40]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id BFC9F6899E8 for ; Wed, 21 Apr 2021 15:27:14 +0300 (EEST) X-ENS-nef-client: 129.199.129.80 ( name = phare.normalesup.org ) Received: from phare.normalesup.org (phare.normalesup.org [129.199.129.80]) by nef.ens.fr (8.14.4/1.01.28121999) with ESMTP id 13LCRCr6006310 for ; Wed, 21 Apr 2021 14:27:13 +0200 Received: by phare.normalesup.org (Postfix, from userid 1001) id EF9F9EB5BD; Wed, 21 Apr 2021 14:27:12 +0200 (CEST) From: Nicolas George To: ffmpeg-devel@ffmpeg.org Date: Wed, 21 Apr 2021 14:27:05 +0200 Message-Id: <20210421122706.9002-7-george@nsup.org> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210421122706.9002-1-george@nsup.org> References: <20210421122706.9002-1-george@nsup.org> MIME-Version: 1.0 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.4.3 (nef.ens.fr [129.199.96.32]); Wed, 21 Apr 2021 14:27:14 +0200 (CEST) Subject: [FFmpeg-devel] [PATCH 6/7] lavu: add a JSON writer API. X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: iGpVNhg5RpkF Signed-off-by: Nicolas George --- libavutil/Makefile | 1 + libavutil/json.c | 368 +++++++++++++++++++++++++++++++++++ libavutil/json.h | 467 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 836 insertions(+) create mode 100644 libavutil/json.c create mode 100644 libavutil/json.h diff --git a/libavutil/Makefile b/libavutil/Makefile index 80d0580289..6ad31426e5 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -133,6 +133,7 @@ OBJS = adler32.o \ imgutils.o \ integer.o \ intmath.o \ + json.o \ lfg.o \ lls.o \ log.o \ diff --git a/libavutil/json.c b/libavutil/json.c new file mode 100644 index 0000000000..7ee68aaa4a --- /dev/null +++ b/libavutil/json.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2021 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avassert.h" +#include "common.h" +#include "json.h" +#include "opt.h" + +#define FIELDOK(st, f) ((char *)(&(st)->f + 1) <= (char *)(st) + (st)->self_size) + +#define json_assert_abi(jwr) av_assert1(FIELDOK(jwr, padding)) +#define json_escape_writer_assert_abi(jwr) av_assert1(FIELDOK(jwr, padding)) + +AVClass *av_json_get_class(void) +{ + return NULL; +} + +int av_json_alloc(AVJson **jc, unsigned max_depth) +{ + return AVERROR_BUG; +} + +void av_json_free(AVJson **jc) +{ +} + +AVJson *av_json_preinit(AVJson *jc, AVJsonEscapeWriter *jwr) +{ + json_assert_abi(jc); + json_escape_writer_assert_abi(jwr); + jc->av_class = av_json_get_class(); + jc->escape_writer = jwr; + av_opt_set_defaults(jc); + return jc; +} + +void av_json_init(AVJson *jc, AVWriter wr, unsigned flags, AVDictionary *options) +{ + jc->out = wr; + jc->flags = flags; + jc->in_string = 0; + jc->in_object = 0; + jc->first_element = 1; + jc->depth = 0; + jc->stack = NULL; +} + +static inline int check_value_must_be_string(AVJson *jc, int string) +{ + return !jc->object_key || string; +} + +static inline int check_stack_clean(AVJsonStack *stack) +{ + return !stack->prev; +} + +static inline int check_begin_end_balanced(AVJson *jc, int obj) +{ + return jc->in_object == obj && jc->stack; +} + +static inline int flag_pretty_print(AVJson *jc) +{ + return !!(jc->flags & AV_JSON_FLAG_PRETTY_PRINT); +} + +static void end_value(AVJson *jc) +{ + if (!jc->stack && flag_pretty_print(jc)) + av_writer_print(jc->out, "\n"); +} + +static void auto_end_string(AVJson *jc) +{ + if (jc->in_string) { + av_writer_print(jc->out, "\""); + jc->in_string = 0; + end_value(jc); + } +} + +static void auto_add_separator(AVJson *jc) +{ + int indent = flag_pretty_print(jc); + + auto_end_string(jc); + if (!jc->first_element) { + if (jc->object_key) { + av_writer_print(jc->out, flag_pretty_print(jc) ? " : " : ":"); + indent = jc->object_key = 0; + } else { + av_writer_print(jc->out, ","); + jc->object_key = jc->in_object; + } + } + if (indent) { + if (jc->stack) + av_writer_print(jc->out, "\n"); + av_writer_add_chars(jc->out, ' ', 3 * jc->depth); + } + jc->first_element = 0; +} + +static void begin_value(AVJson *jc, int string) +{ + auto_add_separator(jc); + av_assert1(check_value_must_be_string(jc, string)); +} + +static void begin_compound(AVJson *jc, AVJsonStack *stack, unsigned obj) +{ + av_assert1(FIELDOK(stack, in_object)); + av_assert1(check_stack_clean(stack)); + stack->prev = jc->stack; + stack->in_object = jc->in_object; + jc->stack = stack; + jc->first_element = 1; + jc->in_object = jc->object_key = obj; + jc->depth++; +} + +static void end_compound(AVJson *jc, unsigned obj) +{ + AVJsonStack *stack = jc->stack; + + av_assert1(check_begin_end_balanced(jc, obj)); + auto_end_string(jc); + jc->depth--; + if (!jc->first_element && flag_pretty_print(jc)) { + av_writer_print(jc->out, "\n"); + av_writer_add_chars(jc->out, ' ', 3 * jc->depth); + } + jc->in_object = stack->in_object; + jc->object_key = 0; + jc->first_element = 0; + jc->stack = stack->prev; + stack->prev = NULL; +} + +void av_json_begin_object_with_stack(AVJson *jc, AVJsonStack *stack) +{ + begin_value(jc, 0); + begin_compound(jc, stack, 1); + av_writer_print(jc->out, "{"); +} + +void av_json_end_object(AVJson *jc) +{ + end_compound(jc, 1); + av_writer_print(jc->out, "}"); + end_value(jc); +} + +void av_json_begin_array_with_stack(AVJson *jc, AVJsonStack *stack) +{ + begin_value(jc, 0); + begin_compound(jc, stack, 0); + av_writer_print(jc->out, "["); +} + +void av_json_end_array(AVJson *jc) +{ + end_compound(jc, 0); + av_writer_print(jc->out, "]"); + end_value(jc); +} + +AVWriter av_json_begin_string(AVJson *jc) +{ + begin_value(jc, 1); + jc->in_string = 1; + av_writer_print(jc->out, "\""); + av_json_escape_writer_init(jc->escape_writer, jc->out, jc->flags); + return av_json_escape_writer_wrap(jc->escape_writer); +} + +void av_json_end_string(AVJson *jc) +{ + av_assert1(jc->in_string); + auto_end_string(jc); +} + +void av_json_add_string(AVJson *jc, const char *str) +{ + AVWriter wr = av_json_begin_string(jc); + av_writer_print(wr, str); +} + +void av_json_add_string_vprintf(AVJson *jc, const char *fmt, va_list va) +{ + AVWriter wr = av_json_begin_string(jc); + av_writer_vprintf(wr, fmt, va); + av_json_end_string(jc); +} + +void av_json_add_string_printf(AVJson *jc, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + av_json_add_string_vprintf(jc, fmt, va); + va_end(va); +} + +void av_json_add_int(AVJson *jc, intmax_t val) +{ + begin_value(jc, 0); + av_writer_printf(jc->out, "%jd", val); + end_value(jc); +} + +void av_json_add_double(AVJson *jc, double val) +{ + begin_value(jc, 0); + av_writer_printf(jc->out, "%.15g", val); + end_value(jc); +} + +void av_json_add_bool(AVJson *jc, int val) +{ + begin_value(jc, 0); + av_writer_print(jc->out, val ? "true" : "false"); + end_value(jc); +} + +void av_json_add_null(AVJson *jc) +{ + begin_value(jc, 0); + av_writer_print(jc->out, "null"); + end_value(jc); +} + +void av_json_add_raw_vprintf(AVJson *jc, const char *fmt, va_list va) +{ + begin_value(jc, 1); + av_writer_vprintf(jc->out, fmt, va); + end_value(jc); +} + +void av_json_add_raw_printf(AVJson *jc, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + av_json_add_raw_vprintf(jc, fmt, va); + va_end(va); +} + +/*************************************************************************** + * AVJsonEscapeWriter - escpae JSON strings + ***************************************************************************/ + +static void json_escape_writer_write(AVWriter wr, const char *data, size_t size) +{ + AVJsonEscapeWriter *jwr = wr.obj; + const uint8_t *end = data + size; + const uint8_t *cur = data; + const uint8_t *written = data; + const uint8_t *raw = data; + unsigned char buf[13]; + const char *escape; + int32_t c; + int ret; + + av_assert1(av_json_escape_writer_check(wr)); + json_escape_writer_assert_abi(jwr); + if (jwr->error) + return; + while (cur < end) { + raw = cur; + ret = av_utf8_decode(&c, &cur, end, 0); + if (ret < 0) { + if ((jwr->flags & AV_JSON_FLAG_BAD_ENCODING_EXPLODE)) { + av_log(NULL, AV_LOG_PANIC, "Bad UTF-8 in JSON\n"); + abort(); + } + if ((jwr->flags & AV_JSON_FLAG_BAD_ENCODING_REPLACE)) { + c = 0xFFFD; + } else { + jwr->error = ret; + return; + } + } + av_assert1(c >= 0 && c <= 0x10FFFF); + if ((unsigned)(c - ' ') < 127 - ' ' && c != '"' && c != '\\') + continue; + if ((unsigned)(c - 0x0080) <= (0xFFFF - 0x0080)) /* TODO flag */ + continue; + if (c == '"') { + escape = "\\\""; + } else if (c == '\\') { + escape = "\\\\"; + } else if (c == '\n') { + escape = "\\n"; + } else if (c == '\r') { + escape = "\\r"; + } else if (c == '\t') { + escape = "\\t"; + } else if (c == '\f') { + escape = "\\f"; + } else if (c == '\b') { + escape = "\\b"; + } else { + if (c < 0x10000) + snprintf(buf, sizeof(buf), "\\u%04X", c); + else /* JSON sucks: UTF-16 */ + snprintf(buf, sizeof(buf), "\\u%04X\\u%04X", + 0xD800 + (((c - 0x10000) >> 10) & 0x3FF), + 0xDC00 + (((c - 0x10000) >> 0) & 0x3FF)); + escape = buf; + } + if (raw > written) + av_writer_write(jwr->owr, written, raw - written); + av_writer_print(jwr->owr, escape); + written = raw = cur; + } + if (cur > written) + av_writer_write(jwr->owr, written, cur - written); +} + +static int json_escape_writer_get_error(AVWriter wr, int self_only) +{ + AVJsonEscapeWriter *jwr = wr.obj; + + av_assert1(av_json_escape_writer_check(wr)); + return jwr->error ? jwr->error : + self_only ? 0 : av_writer_get_error(jwr->owr, 0); +} + +AV_WRITER_DEFINE_METHODS(/*public*/, AVJsonEscapeWriter, av_json_escape_writer) { + .self_size = sizeof(AVWriterMethods), + .name = "AVJsonEscapeWriter", + .write = json_escape_writer_write, + .get_error = json_escape_writer_get_error, +}; + +AVJsonEscapeWriter *av_json_escape_writer_init(AVJsonEscapeWriter *jwr, AVWriter owr, unsigned flags) +{ + json_escape_writer_assert_abi(jwr); + jwr->owr = owr; + jwr->flags = flags; + return jwr; +} + +AVWriter av_json_escape_writer_wrap(AVJsonEscapeWriter *jwr) +{ + AVWriter r = { av_json_escape_writer_get_methods(), jwr }; + json_escape_writer_assert_abi(jwr); + return r; +} + diff --git a/libavutil/json.h b/libavutil/json.h new file mode 100644 index 0000000000..8048305c11 --- /dev/null +++ b/libavutil/json.h @@ -0,0 +1,467 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_JSON +#define AVUTIL_JSON + +#include +#include "dict.h" +#include "extendable.h" +#include "log.h" +#include "writer.h" + +/** + * @defgroup av_json AVJson + * + * API to serialize data to JSON. + * + * API summary and quick HOWTO: + * + * AVWriter out = av_dynbuf_writer(); + * AVJson *jc = AV_JSON_DEFINE(); + * av_json_init(jc, out, AV_JSON_FLAG_PRETTY_PRINT, NULL); + * av_json_begin_object(jc); + * av_json_add_string("user"); + * av_json_add_string(user_name); + * av_json_add_string("score"); + * av_json_add_int("%d", score); + * av_json_end_object(jc); + * av_json_finish(jc); + * ret = av_writer_get_error(out, 0); + * if (ret < 0) ... + * data = av_dynbuf_writer_get_data(wr, &size); + * + * @{ + */ + +typedef struct AVJson AVJson; +typedef struct AVJsonStack AVJsonStack; +typedef struct AVJsonEscapeWriter AVJsonEscapeWriter; +typedef struct AVJsonValue AVJsonValue; +typedef enum AVJsonValueType AVJsonValueType; + +/** + * Produce ASCII output by escaping non-ASCII characters + */ +#define AV_JSON_FLAG_ASCII 0x0001 + +/** + * Abort if the input is not valid UTF-8. + */ +#define AV_JSON_FLAG_BAD_ENCODING_EXPLODE 0x0002 + +/** + * If the input is not valid UTF-8, replace the offending partis with a + * replacement character. + */ +#define AV_JSON_FLAG_BAD_ENCODING_REPLACE 0x0004 + +/** + * Pretty-print the output with spaces and indentation. + * XXX + */ +#define AV_JSON_FLAG_PRETTY_PRINT 0x0008 + +/** + * Consider strings to be pseudo-binary instead of UTF-8. + * + * Warning: without AV_JSON_FLAG_ASCII, it will probably produce + * non-standard JSON. + * XXX + */ +#define AV_JSON_FLAG_PSEUDO_BINARY 0x0010 + +/** + * Define a JSON context by allocating it as compound literals + * (hidden local variables). + * The context will be valid in the current scope and no further. + */ +#define AV_JSON_DEFINE(max_depth) \ + av_json_preinit(&FF_NEW_SZ(AVJson), &FF_NEW_SZ(AVJsonEscapeWriter)) + +/** + * Get the AVClass for a JSON context. + */ +AVClass *av_json_get_class(void); + +/** + * Pre-initialize a JSON context with its escape writer. + * @return jc itself + */ +AVJson *av_json_preinit(AVJson *jc, AVJsonEscapeWriter *jwr); + +/** + * Allocate a new JSON context. + * Only use this if AV_JSON_DEFINE() is not suitable. + * @return 0 or an AVERROR code, including AVERROR(ENOMEM). + */ +int av_json_alloc(AVJson **jc, unsigned max_depth); + +/** + * Free a JSON context allocated with av_json_alloc(). + */ +void av_json_free(AVJson **jc); + +/** + * Initialize a JSON context with output, flags an options. + * This function can be called several times on the same context to reuse + * it. + */ +void av_json_init(AVJson *jc, AVWriter wr, unsigned flags, AVDictionary *options); + +/** + * Begin an object, i.e. a { key : value... } dictionary. + * After this, every other value must be a string. + * The behavior is undefined if a key is not a string. + * The stack object must be allocated and inited, and valid until the + * corresponding av_json_end_object(). + * See av_json_begin_object() for a more convenient version. + */ +void av_json_begin_object_with_stack(AVJson *jc, AVJsonStack *stack); + +/** + * End an object. + */ +void av_json_end_object(AVJson *jc); + +/** + * Begin an array, i.e. a [ value, value... ] list. + * The stack object must be allocated and inited, and valid until the + * corresponding av_json_end_object(). + * See av_json_begin_array() for a more convenient version. + */ +void av_json_begin_array_with_stack(AVJson *jc, AVJsonStack *stack); + +/** + * End an array. + */ +void av_json_end_array(AVJson *jc); + +/** + * Begin a string and a return an AVWriter to write its contents. + */ +AVWriter av_json_begin_string(AVJson *jc); + +/** + * End a string. Optional. + */ +void av_json_end_string(AVJson *jc); + +/** + * Add a string all at once. + */ +void av_json_add_string(AVJson *jc, const char *str); + +/** + * Add a string all at once from a format string and a va_list. + */ +void av_json_add_string_vprintf(AVJson *jc, const char *fmt, va_list va); + +/** + * Add a sring all at once from a format string and arguments. + */ +void av_json_add_string_printf(AVJson *jc, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Add an integer number. + */ +void av_json_add_int(AVJson *jc, intmax_t val); + +/** + * Add a floating-point number. + */ +void av_json_add_double(AVJson *jc, double val); + +/** + * Add a boolean value (true/false). + */ +void av_json_add_bool(AVJson *jc, int val); + +/** + * Add a null value. + */ +void av_json_add_null(AVJson *jc); + +/** + * Add an arbitrary value from a format string and a va_list. + * Useful for adding a floating point number and controlling the format. + * Warning: the validity of the output cannot be guaranteed. + */ +void av_json_add_raw_vprintf(AVJson *jc, const char *fmt, va_list va); + +/** + * Add an arbitrary value from a format string and arguments. + * Useful for adding a floating point number and controlling the format. + * Warning: the validity of the output cannot be guaranteed. + */ +void av_json_add_raw_printf(AVJson *jc, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Define and init a stack element as a compound literal + * (hidden local variable). + * Using this directly is not recommended. + */ +#define AV_JSON_STACK() (&FF_NEW_SZ(AVJsonStack)) + +/** + * Allocate a stack element. + * Only use this if av_json_begin_object() / av_json_begin_array() cannot be + * used. + * @return 0 or an AVERROR code, including AVERROR(ENOMEM). + */ +int av_json_stack_alloc(AVJsonStack **stack); + +/** + * Free a stack element allocated with av_json_stack_alloc(). + */ +void av_json_stack_free(AVJsonStack **stack); + +/** + * Begin an object with a stack element as a compound literal + * (hidden local variable). + * The corresponding av_json_end_object() must be in the same scope. + * After this, every other value must be a string. + * The behavior is undefined if a key is not a string. + */ +#define av_json_begin_object(jc) av_json_begin_object_with_stack((jc), AV_JSON_STACK()) + +/** + * Begin an array with a stack element as a compound literal + * (hidden local variable). + * The corresponding av_json_end_array() must be in the same scope. + */ +#define av_json_begin_array(jc) av_json_begin_array_with_stack((jc), AV_JSON_STACK()) + +/** + * An AVWriter object to escape JSON strings. + * + * Can be allocated on the stack. + * + * Should be inited with one of the utility functions. + */ +struct AVJsonEscapeWriter { + size_t self_size; /**< Size of the structure itself */ + AVWriter owr; /**< AVWriter to send the output */ + unsigned flags; /**< Escaping flags, see AV_JSON_FLAG_* */ + int error; /**< Error status */ + unsigned replacement_char; /**< Replacement character for bad UTF-8 */ + FFReservedPadding padding[2]; +}; + +/** + * Get the methods for a JSON escape writer. + * Probably not useful to use directly. + */ +const AVWriterMethods *av_json_escape_writer_get_methods(void); + +/** + * Check if a writer is a JSON escape writer. + */ +int av_json_escape_writer_check(AVWriter wr); + +/** + * Initialize an AVJsonEscapeWriter to an already-allocated memory buffer. + * + * @return jwr itself + * jwr->self_size must be set. + */ +AVJsonEscapeWriter *av_json_escape_writer_init(AVJsonEscapeWriter *jwr, AVWriter owr, unsigned flags); + +/** + * Create an AVWriter from an AVJsonEscapeWriter structure. + */ +AVWriter av_json_escape_writer_wrap(AVJsonEscapeWriter *jwr); + +/** + * Create an AVWriter to escape JSON strings. + * + * Note: as it relies on a compound statement, the AVJsonEscapeWriter object has + * a scope limited to the block where this macro is called. + */ +#define av_json_escape_writer(owr, flags) \ + av_json_escape_writer_wrap(av_json_escape_writer_init(&FF_NEW_SZ(AVJsonEscapeWriter), (owr), (flags))) + +/** + * JSON encoding context. + * + * This structure must be allocated with AV_JSON_DEFINE() or equivalent + * code that will set av_class, self_size and escape_writer. + * + * It then must be initialized using av_json_init(). + */ +struct AVJson { + + /** + * Class; must be initialized to av_json_get_class(). + */ + const AVClass *av_class; + + /** + * Size of the structure, must be initialized at allocation. + */ + size_t self_size; + + /** + * Encoding flags, see AV_JSON_FLAG_*. + */ + unsigned flags; + + /** + * Indentation shift. + */ + unsigned indentation; + + /** + * Output writer. + */ + AVWriter out; + + /**************************************************************** + * The fields below this limit are private. + ****************************************************************/ + + /** + * Stack of states (object/array) + */ + AVJsonStack *stack; + + /** + * Pre-allocated writer for escaping strings. + * + * Must be allocated before init. + */ + AVJsonEscapeWriter *escape_writer; + + /** + * Depth of nested structures, for indentation. + */ + unsigned depth; + + /** + * True if a string is being constructed. + */ + unsigned in_string : 1; + + /** + * True if we currently are directly in an object. + */ + unsigned in_object : 1; + + /** + * True if we are about to write the first element of a structure. + */ + unsigned first_element : 1; + + /** + * True if we are about to write the key in an object. + */ + unsigned object_key : 1; + + FFReservedPadding padding[8]; +}; + +/** + * Stack element for the JSON context. + */ +struct AVJsonStack { + size_t self_size; + AVJsonStack *prev; + unsigned short in_object; + FFReservedPadding padding; +}; + +/** + * Type of a JSON value. + * + * Note that JSON does not distinguish int and float values. + */ +enum AVJsonValueType { + AV_JSON_TYPE_NULL, + AV_JSON_TYPE_BOOL, + AV_JSON_TYPE_STRING, + AV_JSON_TYPE_INT, + AV_JSON_TYPE_DOUBLE, + AV_JSON_TYPE_OBJECT, + AV_JSON_TYPE_ARRAY, +}; + +/** + * Typed atomic JSON values. + * Objects and arrays cannot be represented. + * This structure is meant to be passed by value. + */ +struct AVJsonValue { + AVJsonValueType type; + union AVJsonValueValue { + intmax_t i; + double d; + const char *s; + } val; +}; + +/** + * Build an AVJsonValue for null. + */ +static inline AVJsonValue av_json_value_null(void) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_NULL }; + return ret; +} + +/** + * Build an AVJsonValue for a boolean. + */ +static inline AVJsonValue av_json_value_bool(int val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_BOOL, .val.i = val }; + return ret; +} + +/** + * Build an AVJsonValue for a string. + * The pointer must stay valid while the value is used. + */ +static inline AVJsonValue av_json_value_string(const char *val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_STRING, .val.s = val }; + return ret; +} + +/** + * Build an AVJsonValue for an integer. + */ +static inline AVJsonValue av_json_value_int(intmax_t val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_INT, .val.i = val }; + return ret; +} + +/** + * Build an AVJsonValue for an integer. + */ +static inline AVJsonValue av_json_value_double(double val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_INT, .val.d = val }; + return ret; +} + +/** + * @} + */ + +#endif /* AVUTIL_JSON */