Am 21.03.2018 um 20:28 schrieb g...@jeffhostetler.com:
> From: Jeff Hostetler <jeffh...@microsoft.com>
> 
> Add basic routines to generate data in JSON format.
> 
> Signed-off-by: Jeff Hostetler <jeffh...@microsoft.com>
> ---
>   Makefile                    |   2 +
>   json-writer.c               | 321 +++++++++++++++++++++++++++++++++
>   json-writer.h               |  86 +++++++++
>   t/helper/test-json-writer.c | 420 
> ++++++++++++++++++++++++++++++++++++++++++++
>   t/t0019-json-writer.sh      | 102 +++++++++++
>   5 files changed, 931 insertions(+)
>   create mode 100644 json-writer.c
>   create mode 100644 json-writer.h
>   create mode 100644 t/helper/test-json-writer.c
>   create mode 100755 t/t0019-json-writer.sh
> 
> diff --git a/Makefile b/Makefile
> index 1a9b23b..57f58e6 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -662,6 +662,7 @@ TEST_PROGRAMS_NEED_X += test-fake-ssh
>   TEST_PROGRAMS_NEED_X += test-genrandom
>   TEST_PROGRAMS_NEED_X += test-hashmap
>   TEST_PROGRAMS_NEED_X += test-index-version
> +TEST_PROGRAMS_NEED_X += test-json-writer
>   TEST_PROGRAMS_NEED_X += test-lazy-init-name-hash
>   TEST_PROGRAMS_NEED_X += test-line-buffer
>   TEST_PROGRAMS_NEED_X += test-match-trees
> @@ -815,6 +816,7 @@ LIB_OBJS += hashmap.o
>   LIB_OBJS += help.o
>   LIB_OBJS += hex.o
>   LIB_OBJS += ident.o
> +LIB_OBJS += json-writer.o
>   LIB_OBJS += kwset.o
>   LIB_OBJS += levenshtein.o
>   LIB_OBJS += line-log.o
> diff --git a/json-writer.c b/json-writer.c
> new file mode 100644
> index 0000000..89a6abb
> --- /dev/null
> +++ b/json-writer.c
> @@ -0,0 +1,321 @@
> +#include "cache.h"
> +#include "json-writer.h"
> +
> +static char g_ch_open[2]  = { '{', '[' };
> +static char g_ch_close[2] = { '}', ']' };
> +
> +/*
> + * Append JSON-quoted version of the given string to 'out'.
> + */
> +static void append_quoted_string(struct strbuf *out, const char *in)
> +{
> +     strbuf_addch(out, '"');
> +     for (/**/; *in; in++) {
> +             unsigned char c = (unsigned char)*in;
> +             if (c == '"')
> +                     strbuf_add(out, "\\\"", 2);
> +             else if (c == '\\')
> +                     strbuf_add(out, "\\\\", 2);
> +             else if (c == '\n')
> +                     strbuf_add(out, "\\n", 2);
> +             else if (c == '\r')
> +                     strbuf_add(out, "\\r", 2);
> +             else if (c == '\t')
> +                     strbuf_add(out, "\\t", 2);
> +             else if (c == '\f')
> +                     strbuf_add(out, "\\f", 2);
> +             else if (c == '\b')
> +                     strbuf_add(out, "\\b", 2);

Using strbuf_addstr() here would result in the same object code (its
strlen() call is inlined for constants) and avoid having to specify
the redundant length 2.

> +             else if (c < 0x20)
> +                     strbuf_addf(out, "\\u%04x", c);
> +             else
> +                     strbuf_addch(out, c);
> +     }
> +     strbuf_addch(out, '"');
> +}
> +
> +
> +static inline void begin(struct json_writer *jw, int is_array)
> +{
> +     ALLOC_GROW(jw->levels, jw->nr + 1, jw->alloc);
> +
> +     jw->levels[jw->nr].level_is_array = !!is_array;
> +     jw->levels[jw->nr].level_is_empty = 1;
> +
> +     strbuf_addch(&jw->json, g_ch_open[!!is_array]);
> +
> +     jw->nr++;
> +}
> +
> +/*
> + * Assert that we have an open object at this level.
> + */
> +static void inline assert_in_object(const struct json_writer *jw, const char 
> *key)
> +{
> +     if (!jw->nr)
> +             BUG("object: missing jw_object_begin(): '%s'", key);
> +     if (jw->levels[jw->nr - 1].level_is_array)
> +             BUG("object: not in object: '%s'", key);
> +}
> +
> +/*
> + * Assert that we have an open array at this level.
> + */
> +static void inline assert_in_array(const struct json_writer *jw)
> +{
> +     if (!jw->nr)
> +             BUG("array: missing jw_begin()");
> +     if (!jw->levels[jw->nr - 1].level_is_array)
> +             BUG("array: not in array");
> +}
> +
> +/*
> + * Add comma if we have already seen a member at this level.
> + */
> +static void inline maybe_add_comma(struct json_writer *jw)
> +{
> +     if (jw->levels[jw->nr - 1].level_is_empty)
> +             jw->levels[jw->nr - 1].level_is_empty = 0;
> +     else
> +             strbuf_addch(&jw->json, ',');
> +}
> +
> +/*
> + * Assert that the given JSON object or JSON array has been properly
> + * terminated.  (Has closing bracket.)
> + */
> +static void inline assert_is_terminated(const struct json_writer *jw)
> +{
> +     if (jw->nr)
> +             BUG("object: missing jw_end(): '%s'", jw->json.buf);
> +}
> +
> +void jw_object_begin(struct json_writer *jw)
> +{
> +     begin(jw, 0);
> +}
> +
> +void jw_object_string(struct json_writer *jw, const char *key, const char 
> *value)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addch(&jw->json, ':');
> +     append_quoted_string(&jw->json, value);
> +}
> +
> +void jw_object_int(struct json_writer *jw, const char *key, int value)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addf(&jw->json, ":%d", value);
> +}
> +
> +void jw_object_uint64(struct json_writer *jw, const char *key, uint64_t 
> value)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addf(&jw->json, ":%"PRIuMAX, value);
> +}
> +
> +void jw_object_double(struct json_writer *jw, const char *fmt,
> +                   const char *key, double value)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     if (!fmt || !*fmt)
> +             fmt = "%f";
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addch(&jw->json, ':');
> +     strbuf_addf(&jw->json, fmt, value);

Hmm.  Can compilers check such a caller-supplied format matches the
value's type?  (I don't know how to specify a format attribute for
GCC and Clang for this function.)

> +}
> +
> +void jw_object_true(struct json_writer *jw, const char *key)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addstr(&jw->json, ":true");
> +}
> +
> +void jw_object_false(struct json_writer *jw, const char *key)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addstr(&jw->json, ":false");
> +}
> +
> +void jw_object_null(struct json_writer *jw, const char *key)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addstr(&jw->json, ":null");
> +}
> +
> +void jw_object_sub(struct json_writer *jw, const char *key,
> +                const struct json_writer *value)
> +{
> +     assert_is_terminated(value);
> +
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addch(&jw->json, ':');
> +     strbuf_addstr(&jw->json, value->json.buf);

strbuf_addbuf() would be a better fit here -- it avoids a strlen() call
and NUL handling issues.

> +}
> +
> +void jw_object_inline_begin_object(struct json_writer *jw, const char *key)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addch(&jw->json, ':');
> +
> +     jw_object_begin(jw);
> +}
> +
> +void jw_object_inline_begin_array(struct json_writer *jw, const char *key)
> +{
> +     assert_in_object(jw, key);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, key);
> +     strbuf_addch(&jw->json, ':');
> +
> +     jw_array_begin(jw);
> +}

Those duplicate calls in the last ten functions feel mind-numbing.  A
helper function for adding comma, key and colon might be a good idea.

> +
> +void jw_array_begin(struct json_writer *jw)
> +{
> +     begin(jw, 1);
> +}
> +
> +void jw_array_string(struct json_writer *jw, const char *value)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     append_quoted_string(&jw->json, value);
> +}
> +
> +void jw_array_int(struct json_writer *jw,int value)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     strbuf_addf(&jw->json, "%d", value);
> +}
> +
> +void jw_array_uint64(struct json_writer *jw, uint64_t value)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     strbuf_addf(&jw->json, "%"PRIuMAX, value);
> +}
> +
> +void jw_array_double(struct json_writer *jw, const char *fmt, double value)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     if (!fmt || !*fmt)
> +             fmt = "%f";
> +
> +     strbuf_addf(&jw->json, fmt, value);
> +}

Same question/quibbling about format checking again.

> +
> +void jw_array_true(struct json_writer *jw)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     strbuf_addstr(&jw->json, "true");
> +}
> +
> +void jw_array_false(struct json_writer *jw)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     strbuf_addstr(&jw->json, "false");
> +}
> +
> +void jw_array_null(struct json_writer *jw)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     strbuf_addstr(&jw->json, "null");
> +}
> +
> +void jw_array_sub(struct json_writer *jw, const struct json_writer *value)
> +{
> +     assert_is_terminated(value);
> +
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     strbuf_addstr(&jw->json, value->json.buf);

strbuf_addbuf() again.

> +}
> +
> +
> +void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv)
> +{
> +     int k;
> +
> +     for (k = 0; k < argc; k++)
> +             jw_array_string(jw, argv[k]);
> +}
> +
> +void jw_array_argv(struct json_writer *jw, const char **argv)
> +{
> +     while (*argv)
> +             jw_array_string(jw, *argv++);
> +}
> +
> +void jw_array_inline_begin_object(struct json_writer *jw)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     jw_object_begin(jw);
> +}
> +
> +void jw_array_inline_begin_array(struct json_writer *jw)
> +{
> +     assert_in_array(jw);
> +     maybe_add_comma(jw);
> +
> +     jw_array_begin(jw);
> +}
> +
> +int jw_is_terminated(const struct json_writer *jw)
> +{
> +     return !jw->nr;
> +}
> +
> +void jw_end(struct json_writer *jw)
> +{
> +     if (!jw->nr)
> +             BUG("too many jw_end(): '%s'", jw->json.buf);
> +
> +     jw->nr--;
> +
> +     strbuf_addch(&jw->json,
> +                  g_ch_close[jw->levels[jw->nr].level_is_array]);
> +}
> diff --git a/json-writer.h b/json-writer.h
> new file mode 100644
> index 0000000..ad38c95
> --- /dev/null
> +++ b/json-writer.h
> @@ -0,0 +1,86 @@
> +#ifndef JSON_WRITER_H
> +#define JSON_WRITER_H
> +
> +/*
> + * JSON data structures are defined at:
> + *      http://json.org/
> + *      http://www.ietf.org/rfc/rfc7159.txt
> + *
> + * The JSON-writer API allows one to build JSON data structures using a
> + * simple wrapper around a "struct strbuf" buffer.  It is intended as a
> + * simple API to build output strings; it is not intended to be a general
> + * object model for JSON data.  In particular, it does not re-order keys
> + * in an object (dictionary), it does not de-dup keys in an object, and
> + * it does not allow lookup or parsing of JSON data.
> + *
> + * All string values (both keys and string r-values) are properly quoted
> + * and escaped if they contain special characters.
> + *
> + * These routines create compact JSON data (with no unnecessary whitespace,
> + * newlines, or indenting).  If you get an unexpected response, verify
> + * that you're not expecting a pretty JSON string.
> + *
> + * Both "JSON objects" (aka sets of k/v pairs) and "JSON array" can be
> + * constructed using a 'begin append* end' model.
> + *
> + * Nested objects and arrays can either be constructed bottom up (by
> + * creating sub object/arrays first and appending them to the super
> + * object/array) -or- by building them inline in one pass.  This is a
> + * personal style and/or data shape choice.
> + *
> + * See t/helper/test-json-writer.c for various usage examples.
> + */
> +
> +struct json_writer_level
> +{
> +     unsigned level_is_array : 1;
> +     unsigned level_is_empty : 1;
> +};
> +
> +struct json_writer
> +{
> +     struct json_writer_level *levels;
> +     int nr, alloc;
> +     struct strbuf json;
> +};

A simpler and probably more compact representation of is_array would
be a strbuf with one char per level, e.g. '[' for an array and '{'
for an object (or ']' and '}').

I don't understand the need to track emptiness per level.  Only the
top level array/object can ever be empty, can it?

> +
> +#define JSON_WRITER_INIT { NULL, 0, 0, STRBUF_INIT }
> +
> +void jw_object_begin(struct json_writer *jw);
> +void jw_array_begin(struct json_writer *jw);
> +
> +void jw_object_string(struct json_writer *jw, const char *key,
> +                   const char *value);
> +void jw_object_int(struct json_writer *jw, const char *key, int value);
> +void jw_object_uint64(struct json_writer *jw, const char *key, uint64_t 
> value);
> +void jw_object_double(struct json_writer *jw, const char *fmt,
> +                   const char *key, double value);
> +void jw_object_true(struct json_writer *jw, const char *key);
> +void jw_object_false(struct json_writer *jw, const char *key);
> +void jw_object_null(struct json_writer *jw, const char *key);
> +void jw_object_sub(struct json_writer *jw, const char *key,
> +                const struct json_writer *value);
> +
> +void jw_object_inline_begin_object(struct json_writer *jw, const char *key);
> +void jw_object_inline_begin_array(struct json_writer *jw, const char *key);
> +
> +
> +
> +void jw_array_string(struct json_writer *jw, const char *value);
> +void jw_array_int(struct json_writer *jw,int value);
> +void jw_array_uint64(struct json_writer *jw, uint64_t value);
> +void jw_array_double(struct json_writer *jw, const char *fmt, double value);
> +void jw_array_true(struct json_writer *jw);
> +void jw_array_false(struct json_writer *jw);
> +void jw_array_null(struct json_writer *jw);
> +void jw_array_sub(struct json_writer *jw, const struct json_writer *value);
> +void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv);
> +void jw_array_argv(struct json_writer *jw, const char **argv);
> +
> +void jw_array_inline_begin_object(struct json_writer *jw);
> +void jw_array_inline_begin_array(struct json_writer *jw);
> +
> +int jw_is_terminated(const struct json_writer *jw);
> +void jw_end(struct json_writer *jw);
> +
> +#endif /* JSON_WRITER_H */

Reply via email to