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 <stddef.h>
+#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 */
