{"thread":{"id":"48168","subject":"[PATCH v5] routines to generate JSON data","startedAt":"2018-03-27T21:02:48Z","lastAt":"2018-03-27T22:04:22Z","messageCount":3,"participants":["git@jeffhostetler.com","Wink Saville"],"isPatch":true,"patchVersion":5,"patchTotal":null},"messages":[{"id":"343169","messageId":"20180327210233.29076-1-git@jeffhostetler.com","threadId":"48168","inReplyTo":null,"subject":"[PATCH v5] routines to generate JSON data","fromName":"","fromEmail":"git@jeffhostetler.com","sentAt":"2018-03-27T21:02:32Z","receivedAt":"2018-03-27T21:02:48Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nThis is version 5 of my JSON data format routines.\n\nThis version address the uint64_t vs intmax_t formatting issues\nthat were discussed on the mailing list.  I removed the jw_*_int()\nand jw_*_uint64() routines and replaced them with a single\njw_*_intmax() routine.\n\nAlso added a jw_release() routine similar to strbuf_release()\nand fixed the indentation of sub-array when pretty printing is\nenabled.\n\nThe following PR includes my WIP telemetry changes that build\nupon the json-writer routines and demonstrates how they might\nbe used.  The first commit in this PR is this patch.\n\n     https://github.com/jeffhostetler/git/pull/11\n\n\nJeff Hostetler (1):\n  json_writer: new routines to create data in JSON format\n\n Makefile                    |   2 +\n json-writer.c               | 394 ++++++++++++++++++++++++++++++\n json-writer.h               |  91 +++++++\n t/helper/test-json-writer.c | 572 ++++++++++++++++++++++++++++++++++++++++++++\n t/t0019-json-writer.sh      | 253 ++++++++++++++++++++\n 5 files changed, 1312 insertions(+)\n create mode 100644 json-writer.c\n create mode 100644 json-writer.h\n create mode 100644 t/helper/test-json-writer.c\n create mode 100755 t/t0019-json-writer.sh\n\n-- \n2.9.3\n\n"},{"id":"343170","messageId":"20180327210233.29076-2-git@jeffhostetler.com","threadId":"48168","inReplyTo":"20180327210233.29076-1-git@jeffhostetler.com","subject":"[PATCH v5] json_writer: new routines to create data in JSON format","fromName":"","fromEmail":"git@jeffhostetler.com","sentAt":"2018-03-27T21:02:33Z","receivedAt":"2018-03-27T21:02:51Z","isPatch":true,"sender":{"key":"git@jeffhostetler.com","avatar":null},"body":"From: Jeff Hostetler <jeffhost@microsoft.com>\n\nAdd a series of jw_ routines and \"struct json_writer\" structure to compose\nJSON data.  The resulting string data can then be output by commands wanting\nto support a JSON output format.\n\nThe json-writer routines can be used to generate structured data in a\nJSON-like format.  We say \"JSON-like\" because we do not enforce the Unicode\n(usually UTF-8) requirement on string fields.  Internally, Git does not\nnecessarily have Unicode/UTF-8 data for most fields, so it is currently\nunclear the best way to enforce that requirement.  For example, on Linx\npathnames can contain arbitrary 8-bit character data, so a command like\n\"status\" would not know how to encode the reported pathnames.  We may want\nto revisit this (or double encode such strings) in the future.\n\nThe initial use for the json-writer routines is for generating telemetry\ndata for executed Git commands.  Later, we may want to use them in other\ncommands, such as status.\n\nHelped-by: René Scharfe <l.s.r@web.de>\nHelped-by: Wink Saville <wink@saville.com>\nHelped-by: Ramsay Jones <ramsay@ramsayjones.plus.com>\nSigned-off-by: Jeff Hostetler <jeffhost@microsoft.com>\n---\n Makefile                    |   2 +\n json-writer.c               | 394 ++++++++++++++++++++++++++++++\n json-writer.h               |  91 +++++++\n t/helper/test-json-writer.c | 572 ++++++++++++++++++++++++++++++++++++++++++++\n t/t0019-json-writer.sh      | 253 ++++++++++++++++++++\n 5 files changed, 1312 insertions(+)\n create mode 100644 json-writer.c\n create mode 100644 json-writer.h\n create mode 100644 t/helper/test-json-writer.c\n create mode 100755 t/t0019-json-writer.sh\n\ndiff --git a/Makefile b/Makefile\nindex 1a9b23b..57f58e6 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -662,6 +662,7 @@ TEST_PROGRAMS_NEED_X += test-fake-ssh\n TEST_PROGRAMS_NEED_X += test-genrandom\n TEST_PROGRAMS_NEED_X += test-hashmap\n TEST_PROGRAMS_NEED_X += test-index-version\n+TEST_PROGRAMS_NEED_X += test-json-writer\n TEST_PROGRAMS_NEED_X += test-lazy-init-name-hash\n TEST_PROGRAMS_NEED_X += test-line-buffer\n TEST_PROGRAMS_NEED_X += test-match-trees\n@@ -815,6 +816,7 @@ LIB_OBJS += hashmap.o\n LIB_OBJS += help.o\n LIB_OBJS += hex.o\n LIB_OBJS += ident.o\n+LIB_OBJS += json-writer.o\n LIB_OBJS += kwset.o\n LIB_OBJS += levenshtein.o\n LIB_OBJS += line-log.o\ndiff --git a/json-writer.c b/json-writer.c\nnew file mode 100644\nindex 0000000..1b49158\n--- /dev/null\n+++ b/json-writer.c\n@@ -0,0 +1,394 @@\n+#include \"cache.h\"\n+#include \"json-writer.h\"\n+\n+void jw_init(struct json_writer *jw)\n+{\n+\tstrbuf_reset(&jw->json);\n+\tstrbuf_reset(&jw->open_stack);\n+\tjw->first = 0;\n+\tjw->pretty = 0;\n+}\n+\n+void jw_release(struct json_writer *jw)\n+{\n+\tstrbuf_release(&jw->json);\n+\tstrbuf_release(&jw->open_stack);\n+}\n+\n+/*\n+ * Append JSON-quoted version of the given string to 'out'.\n+ */\n+static void append_quoted_string(struct strbuf *out, const char *in)\n+{\n+\tunsigned char c;\n+\n+\tstrbuf_addch(out, '\"');\n+\twhile ((c = *in++) != '\\0') {\n+\t\tif (c == '\"')\n+\t\t\tstrbuf_addstr(out, \"\\\\\\\"\");\n+\t\telse if (c == '\\\\')\n+\t\t\tstrbuf_addstr(out, \"\\\\\\\\\");\n+\t\telse if (c == '\\n')\n+\t\t\tstrbuf_addstr(out, \"\\\\n\");\n+\t\telse if (c == '\\r')\n+\t\t\tstrbuf_addstr(out, \"\\\\r\");\n+\t\telse if (c == '\\t')\n+\t\t\tstrbuf_addstr(out, \"\\\\t\");\n+\t\telse if (c == '\\f')\n+\t\t\tstrbuf_addstr(out, \"\\\\f\");\n+\t\telse if (c == '\\b')\n+\t\t\tstrbuf_addstr(out, \"\\\\b\");\n+\t\telse if (c < 0x20)\n+\t\t\tstrbuf_addf(out, \"\\\\u%04x\", c);\n+\t\telse\n+\t\t\tstrbuf_addch(out, c);\n+\t}\n+\tstrbuf_addch(out, '\"');\n+}\n+\n+static inline void indent_pretty(struct json_writer *jw)\n+{\n+\tint k;\n+\n+\tif (!jw->pretty)\n+\t\treturn;\n+\n+\tfor (k = 0; k < jw->open_stack.len; k++)\n+\t\tstrbuf_addstr(&jw->json, \"  \");\n+}\n+\n+static inline void begin(struct json_writer *jw, char ch_open, int pretty)\n+{\n+\tjw->pretty = pretty;\n+\tjw->first = 1;\n+\n+\tstrbuf_addch(&jw->json, ch_open);\n+\n+\tstrbuf_addch(&jw->open_stack, ch_open);\n+}\n+\n+/*\n+ * Assert that the top of the open-stack is an object.\n+ */\n+static inline void assert_in_object(const struct json_writer *jw, const char *key)\n+{\n+\tif (!jw->open_stack.len)\n+\t\tdie(\"json-writer: object: missing jw_object_begin(): '%s'\", key);\n+\tif (jw->open_stack.buf[jw->open_stack.len - 1] != '{')\n+\t\tdie(\"json-writer: object: not in object: '%s'\", key);\n+}\n+\n+/*\n+ * Assert that the top of the open-stack is an array.\n+ */\n+static inline void assert_in_array(const struct json_writer *jw)\n+{\n+\tif (!jw->open_stack.len)\n+\t\tdie(\"json-writer: array: missing jw_array_begin()\");\n+\tif (jw->open_stack.buf[jw->open_stack.len - 1] != '[')\n+\t\tdie(\"json-writer: array: not in array\");\n+}\n+\n+/*\n+ * Add comma if we have already seen a member at this level.\n+ */\n+static inline void maybe_add_comma(struct json_writer *jw)\n+{\n+\tif (!jw->open_stack.len)\n+\t\treturn;\n+\tif (jw->first)\n+\t\tjw->first = 0;\n+\telse\n+\t\tstrbuf_addch(&jw->json, ',');\n+}\n+\n+static inline void fmt_double(struct json_writer *jw, int precision,\n+\t\t\t      double value)\n+{\n+\tif (precision < 0) {\n+\t\tstrbuf_addf(&jw->json, \"%f\", value);\n+\t} else {\n+\t\tstruct strbuf fmt = STRBUF_INIT;\n+\t\tstrbuf_addf(&fmt, \"%%.%df\", precision);\n+\t\tstrbuf_addf(&jw->json, fmt.buf, value);\n+\t\tstrbuf_release(&fmt);\n+\t}\n+}\n+\n+static inline void object_common(struct json_writer *jw, const char *key)\n+{\n+\tassert_in_object(jw, key);\n+\tmaybe_add_comma(jw);\n+\n+\tif (jw->pretty)\n+\t\tstrbuf_addch(&jw->json, '\\n');\n+\tindent_pretty(jw);\n+\n+\tappend_quoted_string(&jw->json, key);\n+\tstrbuf_addch(&jw->json, ':');\n+\tif (jw->pretty)\n+\t\tstrbuf_addch(&jw->json, ' ');\n+}\n+\n+static inline void array_common(struct json_writer *jw)\n+{\n+\tassert_in_array(jw);\n+\tmaybe_add_comma(jw);\n+\n+\tif (jw->pretty)\n+\t\tstrbuf_addch(&jw->json, '\\n');\n+\tindent_pretty(jw);\n+}\n+\n+/*\n+ * Assert that the given JSON object or JSON array has been properly\n+ * terminated.  (Has closing bracket.)\n+ */\n+static inline void assert_is_terminated(const struct json_writer *jw)\n+{\n+\tif (jw->open_stack.len)\n+\t\tdie(\"json-writer: object: missing jw_end(): '%s'\",\n+\t\t    jw->json.buf);\n+}\n+\n+void jw_object_begin(struct json_writer *jw, int pretty)\n+{\n+\tbegin(jw, '{', pretty);\n+}\n+\n+void jw_object_string(struct json_writer *jw, const char *key, const char *value)\n+{\n+\tobject_common(jw, key);\n+\tappend_quoted_string(&jw->json, value);\n+}\n+\n+void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addf(&jw->json, \"%\"PRIdMAX, (intmax_t)value);\n+}\n+\n+void jw_object_double(struct json_writer *jw, const char *key, int precision,\n+\t\t      double value)\n+{\n+\tobject_common(jw, key);\n+\tfmt_double(jw, precision, value);\n+}\n+\n+void jw_object_true(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addstr(&jw->json, \"true\");\n+}\n+\n+void jw_object_false(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addstr(&jw->json, \"false\");\n+}\n+\n+void jw_object_null(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\tstrbuf_addstr(&jw->json, \"null\");\n+}\n+\n+static void increase_indent(struct strbuf *sb,\n+\t\t\t    const struct json_writer *jw,\n+\t\t\t    int indent)\n+{\n+\tint k;\n+\n+\tstrbuf_reset(sb);\n+\tfor (k = 0; k < jw->json.len; k++) {\n+\t\tchar ch = jw->json.buf[k];\n+\t\tstrbuf_addch(sb, ch);\n+\t\tif (ch == '\\n')\n+\t\t\tstrbuf_addchars(sb, ' ', indent);\n+\t}\n+}\n+\n+static void kill_indent(struct strbuf *sb,\n+\t\t\tconst struct json_writer *jw)\n+{\n+\tint k;\n+\tint eat_it = 0;\n+\n+\tstrbuf_reset(sb);\n+\tfor (k = 0; k < jw->json.len; k++) {\n+\t\tchar ch = jw->json.buf[k];\n+\t\tif (eat_it && ch == ' ')\n+\t\t\tcontinue;\n+\t\tif (ch == '\\n') {\n+\t\t\teat_it = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\teat_it = 0;\n+\t\tstrbuf_addch(sb, ch);\n+\t}\n+}\n+\n+static void append_sub_jw(struct json_writer *jw,\n+\t\t\t  const struct json_writer *value)\n+{\n+\t/*\n+\t * If both are pretty, increase the indentation of the sub_jw\n+\t * to better fit under the super.\n+\t *\n+\t * If the super is pretty, but the sub_jw is compact, leave the\n+\t * sub_jw compact.  (We don't want to parse and rebuild the sub_jw\n+\t * for this debug-ish feature.)\n+\t *\n+\t * If the super is compact, and the sub_jw is pretty, convert\n+\t * the sub_jw to compact.\n+\t *\n+\t * If both are compact, keep the sub_jw compact.\n+\t */\n+\tif (jw->pretty && jw->open_stack.len && value->pretty) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tincrease_indent(&sb, value, jw->open_stack.len * 2);\n+\t\tstrbuf_addbuf(&jw->json, &sb);\n+\t\tstrbuf_release(&sb);\n+\t\treturn;\n+\t}\n+\tif (!jw->pretty && value->pretty) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tkill_indent(&sb, value);\n+\t\tstrbuf_addbuf(&jw->json, &sb);\n+\t\tstrbuf_release(&sb);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_addbuf(&jw->json, &value->json);\n+}\n+\n+/*\n+ * Append existing (properly terminated) JSON sub-data (object or array)\n+ * as-is onto the given JSON data.\n+ */\n+void jw_object_sub_jw(struct json_writer *jw, const char *key,\n+\t\t      const struct json_writer *value)\n+{\n+\tassert_is_terminated(value);\n+\n+\tobject_common(jw, key);\n+\tappend_sub_jw(jw, value);\n+}\n+\n+void jw_object_inline_begin_object(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\n+\tjw_object_begin(jw, jw->pretty);\n+}\n+\n+void jw_object_inline_begin_array(struct json_writer *jw, const char *key)\n+{\n+\tobject_common(jw, key);\n+\n+\tjw_array_begin(jw, jw->pretty);\n+}\n+\n+void jw_array_begin(struct json_writer *jw, int pretty)\n+{\n+\tbegin(jw, '[', pretty);\n+}\n+\n+void jw_array_string(struct json_writer *jw, const char *value)\n+{\n+\tarray_common(jw);\n+\tappend_quoted_string(&jw->json, value);\n+}\n+\n+void jw_array_intmax(struct json_writer *jw, intmax_t value)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addf(&jw->json, \"%\"PRIdMAX, (intmax_t)value);\n+}\n+\n+void jw_array_double(struct json_writer *jw, int precision, double value)\n+{\n+\tarray_common(jw);\n+\tfmt_double(jw, precision, value);\n+}\n+\n+void jw_array_true(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addstr(&jw->json, \"true\");\n+}\n+\n+void jw_array_false(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addstr(&jw->json, \"false\");\n+}\n+\n+void jw_array_null(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\tstrbuf_addstr(&jw->json, \"null\");\n+}\n+\n+void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value)\n+{\n+\tassert_is_terminated(value);\n+\n+\tarray_common(jw);\n+\tappend_sub_jw(jw, value);\n+}\n+\n+void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv)\n+{\n+\tint k;\n+\n+\tfor (k = 0; k < argc; k++)\n+\t\tjw_array_string(jw, argv[k]);\n+}\n+\n+void jw_array_argv(struct json_writer *jw, const char **argv)\n+{\n+\twhile (*argv)\n+\t\tjw_array_string(jw, *argv++);\n+}\n+\n+void jw_array_inline_begin_object(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\n+\tjw_object_begin(jw, jw->pretty);\n+}\n+\n+void jw_array_inline_begin_array(struct json_writer *jw)\n+{\n+\tarray_common(jw);\n+\n+\tjw_array_begin(jw, jw->pretty);\n+}\n+\n+int jw_is_terminated(const struct json_writer *jw)\n+{\n+\treturn !jw->open_stack.len;\n+}\n+\n+void jw_end(struct json_writer *jw)\n+{\n+\tchar ch_open;\n+\tint len;\n+\n+\tif (!jw->open_stack.len)\n+\t\tdie(\"json-writer: too many jw_end(): '%s'\", jw->json.buf);\n+\n+\tlen = jw->open_stack.len - 1;\n+\tch_open = jw->open_stack.buf[len];\n+\tstrbuf_setlen(&jw->open_stack, len);\n+\n+\tif (jw->pretty)\n+\t\tstrbuf_addch(&jw->json, '\\n');\n+\tindent_pretty(jw);\n+\n+\tif (ch_open == '{')\n+\t\tstrbuf_addch(&jw->json, '}');\n+\telse\n+\t\tstrbuf_addch(&jw->json, ']');\n+}\ndiff --git a/json-writer.h b/json-writer.h\nnew file mode 100644\nindex 0000000..7d07dc6\n--- /dev/null\n+++ b/json-writer.h\n@@ -0,0 +1,91 @@\n+#ifndef JSON_WRITER_H\n+#define JSON_WRITER_H\n+\n+/*\n+ * JSON data structures are defined at:\n+ * [1] http://www.ietf.org/rfc/rfc7159.txt\n+ * [2] http://json.org/\n+ *\n+ * The JSON-writer API allows one to build JSON data structures using a\n+ * simple wrapper around a \"struct strbuf\" buffer.  It is intended as a\n+ * simple API to build output strings; it is not intended to be a general\n+ * object model for JSON data.  In particular, it does not re-order keys\n+ * in an object (dictionary), it does not de-dup keys in an object, and\n+ * it does not allow lookup or parsing of JSON data.\n+ *\n+ * All string values (both keys and string r-values) are properly quoted\n+ * and escaped if they contain special characters.\n+ *\n+ * These routines create compact JSON data (with no unnecessary whitespace,\n+ * newlines, or indenting).  If you get an unexpected response, verify\n+ * that you're not expecting a pretty JSON string.\n+ *\n+ * Both \"JSON objects\" (aka sets of k/v pairs) and \"JSON array\" can be\n+ * constructed using a 'begin append* end' model.\n+ *\n+ * Nested objects and arrays can either be constructed bottom up (by\n+ * creating sub object/arrays first and appending them to the super\n+ * object/array) -or- by building them inline in one pass.  This is a\n+ * personal style and/or data shape choice.\n+ *\n+ * See t/helper/test-json-writer.c for various usage examples.\n+ *\n+ * LIMITATIONS:\n+ * ============\n+ *\n+ * The JSON specification [1,2] defines string values as Unicode data\n+ * and probably UTF-8 encoded.  The current json-writer API does not\n+ * enforce this and will write any string as received.  However, it will\n+ * properly quote and backslash-escape them as necessary.  It is up to\n+ * the caller to UTF-8 encode their strings *before* passing them to this\n+ * API.  This layer should not have to try to guess the encoding or locale\n+ * of the given strings.\n+ */\n+\n+struct json_writer\n+{\n+\tstruct strbuf json;\n+\tstruct strbuf open_stack;\n+\tint first;\n+\tint pretty;\n+};\n+\n+#define JSON_WRITER_INIT { STRBUF_INIT, STRBUF_INIT, 0, 0 }\n+\n+void jw_init(struct json_writer *jw);\n+void jw_release(struct json_writer *jw);\n+\n+void jw_object_begin(struct json_writer *jw, int pretty);\n+void jw_array_begin(struct json_writer *jw, int pretty);\n+\n+void jw_object_string(struct json_writer *jw, const char *key,\n+\t\t      const char *value);\n+void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value);\n+void jw_object_double(struct json_writer *jw, const char *key, int precision,\n+\t\t      double value);\n+void jw_object_true(struct json_writer *jw, const char *key);\n+void jw_object_false(struct json_writer *jw, const char *key);\n+void jw_object_null(struct json_writer *jw, const char *key);\n+void jw_object_sub_jw(struct json_writer *jw, const char *key,\n+\t\t      const struct json_writer *value);\n+\n+void jw_object_inline_begin_object(struct json_writer *jw, const char *key);\n+void jw_object_inline_begin_array(struct json_writer *jw, const char *key);\n+\n+void jw_array_string(struct json_writer *jw, const char *value);\n+void jw_array_intmax(struct json_writer *jw, intmax_t value);\n+void jw_array_double(struct json_writer *jw, int precision, double value);\n+void jw_array_true(struct json_writer *jw);\n+void jw_array_false(struct json_writer *jw);\n+void jw_array_null(struct json_writer *jw);\n+void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value);\n+void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv);\n+void jw_array_argv(struct json_writer *jw, const char **argv);\n+\n+void jw_array_inline_begin_object(struct json_writer *jw);\n+void jw_array_inline_begin_array(struct json_writer *jw);\n+\n+int jw_is_terminated(const struct json_writer *jw);\n+void jw_end(struct json_writer *jw);\n+\n+#endif /* JSON_WRITER_H */\ndiff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c\nnew file mode 100644\nindex 0000000..5bc117c\n--- /dev/null\n+++ b/t/helper/test-json-writer.c\n@@ -0,0 +1,572 @@\n+#include \"cache.h\"\n+#include \"json-writer.h\"\n+\n+static const char *expect_obj1 = \"{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true}\";\n+static const char *expect_obj2 = \"{\\\"a\\\":-1,\\\"b\\\":2147483647,\\\"c\\\":0}\";\n+static const char *expect_obj3 = \"{\\\"a\\\":0,\\\"b\\\":4294967295,\\\"c\\\":9223372036854775807}\";\n+static const char *expect_obj4 = \"{\\\"t\\\":true,\\\"f\\\":false,\\\"n\\\":null}\";\n+static const char *expect_obj5 = \"{\\\"abc\\\\tdef\\\":\\\"abc\\\\\\\\def\\\"}\";\n+static const char *expect_obj6 = \"{\\\"a\\\":3.14}\";\n+\n+static const char *pretty_obj1 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"a\\\": \\\"abc\\\",\\n\"\n+\t\t\t\t  \"  \\\"b\\\": 42,\\n\"\n+\t\t\t\t  \"  \\\"c\\\": true\\n\"\n+\t\t\t\t  \"}\");\n+static const char *pretty_obj2 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"a\\\": -1,\\n\"\n+\t\t\t\t  \"  \\\"b\\\": 2147483647,\\n\"\n+\t\t\t\t  \"  \\\"c\\\": 0\\n\"\n+\t\t\t\t  \"}\");\n+static const char *pretty_obj3 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"a\\\": 0,\\n\"\n+\t\t\t\t  \"  \\\"b\\\": 4294967295,\\n\"\n+\t\t\t\t  \"  \\\"c\\\": 9223372036854775807\\n\"\n+\t\t\t\t  \"}\");\n+static const char *pretty_obj4 = (\"{\\n\"\n+\t\t\t\t  \"  \\\"t\\\": true,\\n\"\n+\t\t\t\t  \"  \\\"f\\\": false,\\n\"\n+\t\t\t\t  \"  \\\"n\\\": null\\n\"\n+\t\t\t\t  \"}\");\n+\n+static struct json_writer obj1 = JSON_WRITER_INIT;\n+static struct json_writer obj2 = JSON_WRITER_INIT;\n+static struct json_writer obj3 = JSON_WRITER_INIT;\n+static struct json_writer obj4 = JSON_WRITER_INIT;\n+static struct json_writer obj5 = JSON_WRITER_INIT;\n+static struct json_writer obj6 = JSON_WRITER_INIT;\n+\n+static void make_obj1(int pretty)\n+{\n+\tjw_object_begin(&obj1, pretty);\n+\t{\n+\t\tjw_object_string(&obj1, \"a\", \"abc\");\n+\t\tjw_object_intmax(&obj1, \"b\", 42);\n+\t\tjw_object_true(&obj1, \"c\");\n+\t}\n+\tjw_end(&obj1);\n+}\n+\n+static void make_obj2(int pretty)\n+{\n+\tjw_object_begin(&obj2, pretty);\n+\t{\n+\t\tjw_object_intmax(&obj2, \"a\", -1);\n+\t\tjw_object_intmax(&obj2, \"b\", 0x7fffffff);\n+\t\tjw_object_intmax(&obj2, \"c\", 0);\n+\t}\n+\tjw_end(&obj2);\n+}\n+\n+static void make_obj3(int pretty)\n+{\n+\tjw_object_begin(&obj3, pretty);\n+\t{\n+\t\tjw_object_intmax(&obj3, \"a\", 0);\n+\t\tjw_object_intmax(&obj3, \"b\", 0xffffffff);\n+\t\tjw_object_intmax(&obj3, \"c\", 0x7fffffffffffffffULL);\n+\t}\n+\tjw_end(&obj3);\n+}\n+\n+static void make_obj4(int pretty)\n+{\n+\tjw_object_begin(&obj4, pretty);\n+\t{\n+\t\tjw_object_true(&obj4, \"t\");\n+\t\tjw_object_false(&obj4, \"f\");\n+\t\tjw_object_null(&obj4, \"n\");\n+\t}\n+\tjw_end(&obj4);\n+}\n+\n+static void make_obj5(int pretty)\n+{\n+\tjw_object_begin(&obj5, pretty);\n+\t{\n+\t\tjw_object_string(&obj5, \"abc\" \"\\x09\" \"def\", \"abc\" \"\\\\\" \"def\");\n+\t}\n+\tjw_end(&obj5);\n+}\n+\n+static void make_obj6(int pretty)\n+{\n+\tjw_object_begin(&obj6, pretty);\n+\t{\n+\t\tjw_object_double(&obj6, \"a\", 2, 3.14159);\n+\t}\n+\tjw_end(&obj6);\n+}\n+\n+static const char *expect_arr1 = \"[\\\"abc\\\",42,true]\";\n+static const char *expect_arr2 = \"[-1,2147483647,0]\";\n+static const char *expect_arr3 = \"[0,4294967295,9223372036854775807]\";\n+static const char *expect_arr4 = \"[true,false,null]\";\n+\n+static const char *pretty_arr1 = (\"[\\n\"\n+\t\t\t\t  \"  \\\"abc\\\",\\n\"\n+\t\t\t\t  \"  42,\\n\"\n+\t\t\t\t  \"  true\\n\"\n+\t\t\t\t  \"]\");\n+static const char *pretty_arr2 = (\"[\\n\"\n+\t\t\t\t  \"  -1,\\n\"\n+\t\t\t\t  \"  2147483647,\\n\"\n+\t\t\t\t  \"  0\\n\"\n+\t\t\t\t  \"]\");\n+static const char *pretty_arr3 = (\"[\\n\"\n+\t\t\t\t  \"  0,\\n\"\n+\t\t\t\t  \"  4294967295,\\n\"\n+\t\t\t\t  \"  9223372036854775807\\n\"\n+\t\t\t\t  \"]\");\n+static const char *pretty_arr4 = (\"[\\n\"\n+\t\t\t\t  \"  true,\\n\"\n+\t\t\t\t  \"  false,\\n\"\n+\t\t\t\t  \"  null\\n\"\n+\t\t\t\t  \"]\");\n+\n+static struct json_writer arr1 = JSON_WRITER_INIT;\n+static struct json_writer arr2 = JSON_WRITER_INIT;\n+static struct json_writer arr3 = JSON_WRITER_INIT;\n+static struct json_writer arr4 = JSON_WRITER_INIT;\n+\n+static void make_arr1(int pretty)\n+{\n+\tjw_array_begin(&arr1, pretty);\n+\t{\n+\t\tjw_array_string(&arr1, \"abc\");\n+\t\tjw_array_intmax(&arr1, 42);\n+\t\tjw_array_true(&arr1);\n+\t}\n+\tjw_end(&arr1);\n+}\n+\n+static void make_arr2(int pretty)\n+{\n+\tjw_array_begin(&arr2, pretty);\n+\t{\n+\t\tjw_array_intmax(&arr2, -1);\n+\t\tjw_array_intmax(&arr2, 0x7fffffff);\n+\t\tjw_array_intmax(&arr2, 0);\n+\t}\n+\tjw_end(&arr2);\n+}\n+\n+static void make_arr3(int pretty)\n+{\n+\tjw_array_begin(&arr3, pretty);\n+\t{\n+\t\tjw_array_intmax(&arr3, 0);\n+\t\tjw_array_intmax(&arr3, 0xffffffff);\n+\t\tjw_array_intmax(&arr3, 0x7fffffffffffffffULL);\n+\t}\n+\tjw_end(&arr3);\n+}\n+\n+static void make_arr4(int pretty)\n+{\n+\tjw_array_begin(&arr4, pretty);\n+\t{\n+\t\tjw_array_true(&arr4);\n+\t\tjw_array_false(&arr4);\n+\t\tjw_array_null(&arr4);\n+\t}\n+\tjw_end(&arr4);\n+}\n+\n+static char *expect_nest1 =\n+\t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n+\n+static struct json_writer nest1 = JSON_WRITER_INIT;\n+\n+static void make_nest1(int pretty)\n+{\n+\tjw_object_begin(&nest1, pretty);\n+\t{\n+\t\tjw_object_sub_jw(&nest1, \"obj1\", &obj1);\n+\t\tjw_object_sub_jw(&nest1, \"arr1\", &arr1);\n+\t}\n+\tjw_end(&nest1);\n+}\n+\n+static char *expect_inline1 =\n+\t\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\\"arr1\\\":[\\\"abc\\\",42,true]}\";\n+\n+static char *pretty_inline1 =\n+\t(\"{\\n\"\n+\t \"  \\\"obj1\\\": {\\n\"\n+\t \"    \\\"a\\\": \\\"abc\\\",\\n\"\n+\t \"    \\\"b\\\": 42,\\n\"\n+\t \"    \\\"c\\\": true\\n\"\n+\t \"  },\\n\"\n+\t \"  \\\"arr1\\\": [\\n\"\n+\t \"    \\\"abc\\\",\\n\"\n+\t \"    42,\\n\"\n+\t \"    true\\n\"\n+\t \"  ]\\n\"\n+\t \"}\");\n+\n+static struct json_writer inline1 = JSON_WRITER_INIT;\n+\n+static void make_inline1(int pretty)\n+{\n+\tjw_object_begin(&inline1, pretty);\n+\t{\n+\t\tjw_object_inline_begin_object(&inline1, \"obj1\");\n+\t\t{\n+\t\t\tjw_object_string(&inline1, \"a\", \"abc\");\n+\t\t\tjw_object_intmax(&inline1, \"b\", 42);\n+\t\t\tjw_object_true(&inline1, \"c\");\n+\t\t}\n+\t\tjw_end(&inline1);\n+\t\tjw_object_inline_begin_array(&inline1, \"arr1\");\n+\t\t{\n+\t\t\tjw_array_string(&inline1, \"abc\");\n+\t\t\tjw_array_intmax(&inline1, 42);\n+\t\t\tjw_array_true(&inline1);\n+\t\t}\n+\t\tjw_end(&inline1);\n+\t}\n+\tjw_end(&inline1);\n+}\n+\n+static char *expect_inline2 =\n+\t\"[[1,2],[3,4],{\\\"a\\\":\\\"abc\\\"}]\";\n+\n+static char *pretty_inline2 =\n+\t(\"[\\n\"\n+\t \"  [\\n\"\n+\t \"    1,\\n\"\n+\t \"    2\\n\"\n+\t \"  ],\\n\"\n+\t \"  [\\n\"\n+\t \"    3,\\n\"\n+\t \"    4\\n\"\n+\t \"  ],\\n\"\n+\t \"  {\\n\"\n+\t \"    \\\"a\\\": \\\"abc\\\"\\n\"\n+\t \"  }\\n\"\n+\t \"]\");\n+\n+static struct json_writer inline2 = JSON_WRITER_INIT;\n+\n+static void make_inline2(int pretty)\n+{\n+\tjw_array_begin(&inline2, pretty);\n+\t{\n+\t\tjw_array_inline_begin_array(&inline2);\n+\t\t{\n+\t\t\tjw_array_intmax(&inline2, 1);\n+\t\t\tjw_array_intmax(&inline2, 2);\n+\t\t}\n+\t\tjw_end(&inline2);\n+\t\tjw_array_inline_begin_array(&inline2);\n+\t\t{\n+\t\t\tjw_array_intmax(&inline2, 3);\n+\t\t\tjw_array_intmax(&inline2, 4);\n+\t\t}\n+\t\tjw_end(&inline2);\n+\t\tjw_array_inline_begin_object(&inline2);\n+\t\t{\n+\t\t\tjw_object_string(&inline2, \"a\", \"abc\");\n+\t\t}\n+\t\tjw_end(&inline2);\n+\t}\n+\tjw_end(&inline2);\n+}\n+\n+/*\n+ * When super is compact, we expect subs to be compacted (even if originally\n+ * pretty).\n+ */\n+static const char *expect_mixed1 =\n+\t(\"{\\\"obj1\\\":{\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\"\n+\t \"\\\"arr1\\\":[\\\"abc\\\",42,true]}\");\n+\n+/*\n+ * When super is pretty, a compact sub (obj1) is kept compact and a pretty\n+ * sub (arr1) is re-indented.\n+ */\n+static const char *pretty_mixed1 =\n+\t(\"{\\n\"\n+\t \"  \\\"obj1\\\": {\\\"a\\\":\\\"abc\\\",\\\"b\\\":42,\\\"c\\\":true},\\n\"\n+\t \"  \\\"arr1\\\": [\\n\"\n+\t \"    \\\"abc\\\",\\n\"\n+\t \"    42,\\n\"\n+\t \"    true\\n\"\n+\t \"  ]\\n\"\n+\t \"}\");\n+\n+static struct json_writer mixed1 = JSON_WRITER_INIT;\n+\n+static void make_mixed1(int pretty)\n+{\n+\tjw_init(&obj1);\n+\tjw_init(&arr1);\n+\n+\tmake_obj1(0); /* obj1 is compact */\n+\tmake_arr1(1); /* arr1 is pretty */\n+\n+\tjw_object_begin(&mixed1, pretty);\n+\t{\n+\t\tjw_object_sub_jw(&mixed1, \"obj1\", &obj1);\n+\t\tjw_object_sub_jw(&mixed1, \"arr1\", &arr1);\n+\t}\n+\tjw_end(&mixed1);\n+}\n+\n+static void cmp(const char *test, const struct json_writer *jw, const char *exp)\n+{\n+\tif (!strcmp(jw->json.buf, exp))\n+\t\treturn;\n+\n+\tprintf(\"error[%s]: observed '%s' expected '%s'\\n\",\n+\t       test, jw->json.buf, exp);\n+\texit(1);\n+}\n+\n+#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0)\n+#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0)\n+\n+/*\n+ * Run some basic regression tests with some known patterns.\n+ * These tests also demonstrate how to use the jw_ API.\n+ */\n+static int unit_tests(void)\n+{\n+\t/* comptact (canonical) forms */\n+\tt(obj1);\n+\tt(obj2);\n+\tt(obj3);\n+\tt(obj4);\n+\tt(obj5);\n+\tt(obj6);\n+\n+\tt(arr1);\n+\tt(arr2);\n+\tt(arr3);\n+\tt(arr4);\n+\n+\tt(nest1);\n+\n+\tt(inline1);\n+\tt(inline2);\n+\n+\tjw_init(&obj1);\n+\tjw_init(&obj2);\n+\tjw_init(&obj3);\n+\tjw_init(&obj4);\n+\n+\tjw_init(&arr1);\n+\tjw_init(&arr2);\n+\tjw_init(&arr3);\n+\tjw_init(&arr4);\n+\n+\tjw_init(&inline1);\n+\tjw_init(&inline2);\n+\n+\t/* pretty forms */\n+\tp(obj1);\n+\tp(obj2);\n+\tp(obj3);\n+\tp(obj4);\n+\n+\tp(arr1);\n+\tp(arr2);\n+\tp(arr3);\n+\tp(arr4);\n+\n+\tp(inline1);\n+\tp(inline2);\n+\n+\t/* mixed forms */\n+\tt(mixed1);\n+\tjw_init(&mixed1);\n+\tp(mixed1);\n+\n+\treturn 0;\n+}\n+\n+#define STMT(s) do { s } while (0)\n+\n+#define PARAM(tok, lbl, p) \\\n+\tSTMT( if (!(p) || (*(p) == '@')) \\\n+\t\tdie(\"token '%s' requires '%s' parameter, but saw: '%s'\", \\\n+\t\t    tok, lbl, p); )\n+\n+#define KEY() \\\n+\tSTMT( key = argv[++k]; \\\n+\t      PARAM(a_k, \"key\", key); )\n+\n+#define VAL() \\\n+\tSTMT( val = argv[++k]; \\\n+\t      PARAM(a_k, \"val\", val); )\n+\n+#define PRECISION() \\\n+\tSTMT( const char *s = argv[++k]; \\\n+\t      PARAM(a_k, \"precision\", s); \\\n+\t      precision = strtol(s, &endptr, 10); \\\n+\t      if (*endptr || errno == ERANGE) \\\n+\t\tdie(\"invalid '%s' precision: '%s'\", a_k, s); )\n+\n+#define VAL_INTMAX() \\\n+\tSTMT( VAL(); \\\n+\t      v_int = strtol(val, &endptr, 10); \\\n+\t      if (*endptr || errno == ERANGE) \\\n+\t\tdie(\"invalid '%s' value: '%s'\",\ta_k, val); )\n+\n+#define VAL_DOUBLE() \\\n+\tSTMT( VAL(); \\\n+\t      v_double = strtod(val, &endptr); \\\n+\t      if (*endptr || errno == ERANGE) \\\n+\t\tdie(\"invalid '%s' value: '%s'\",\ta_k, val); )\n+\n+static int pretty;\n+\n+static inline int scripted(int argc, const char **argv)\n+{\n+\tstruct json_writer jw = JSON_WRITER_INIT;\n+\tint k;\n+\n+\tif (!strcmp(argv[0], \"@object\"))\n+\t\tjw_object_begin(&jw, pretty);\n+\telse if (!strcmp(argv[0], \"@array\"))\n+\t\tjw_array_begin(&jw, pretty);\n+\telse\n+\t\tdie(\"expected '@object' or '@array': '%s'\", argv[0]);\n+\n+\tfor (k = 1; k < argc; k++) {\n+\t\tconst char *a_k = argv[k];\n+\t\tconst char *key;\n+\t\tconst char *val;\n+\t\tchar *endptr;\n+\t\tint precision;\n+\t\tintmax_t v_int;\n+\t\tdouble v_double;\n+\n+\t\tif (!strcmp(a_k, \"@end\")) {\n+\t\t\tjw_end(&jw);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!strcmp(a_k, \"@object-string\")) {\n+\t\t\tKEY();\n+\t\t\tVAL();\n+\t\t\tjw_object_string(&jw, key, val);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-int\")) {\n+\t\t\tKEY();\n+\t\t\tVAL_INTMAX();\n+\t\t\tjw_object_intmax(&jw, key, v_int);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-double\")) {\n+\t\t\tKEY();\n+\t\t\tPRECISION();\n+\t\t\tVAL_DOUBLE();\n+\t\t\tjw_object_double(&jw, key, precision, v_double);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-true\")) {\n+\t\t\tKEY();\n+\t\t\tjw_object_true(&jw, key);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-false\")) {\n+\t\t\tKEY();\n+\t\t\tjw_object_false(&jw, key);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-null\")) {\n+\t\t\tKEY();\n+\t\t\tjw_object_null(&jw, key);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-object\")) {\n+\t\t\tKEY();\n+\t\t\tjw_object_inline_begin_object(&jw, key);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@object-array\")) {\n+\t\t\tKEY();\n+\t\t\tjw_object_inline_begin_array(&jw, key);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (!strcmp(a_k, \"@array-string\")) {\n+\t\t\tVAL();\n+\t\t\tjw_array_string(&jw, val);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-int\")) {\n+\t\t\tVAL_INTMAX();\n+\t\t\tjw_array_intmax(&jw, v_int);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-double\")) {\n+\t\t\tPRECISION();\n+\t\t\tVAL_DOUBLE();\n+\t\t\tjw_array_double(&jw, precision, v_double);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-true\")) {\n+\t\t\tjw_array_true(&jw);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-false\")) {\n+\t\t\tjw_array_false(&jw);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-null\")) {\n+\t\t\tjw_array_null(&jw);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-object\")) {\n+\t\t\tjw_array_inline_begin_object(&jw);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!strcmp(a_k, \"@array-array\")) {\n+\t\t\tjw_array_inline_begin_array(&jw);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tdie(\"unrecognized token: '%s'\", a_k);\n+\t}\n+\n+\tif (!jw_is_terminated(&jw))\n+\t\tdie(\"json not terminated: '%s'\", jw.json.buf);\n+\n+\tprintf(\"%s\\n\", jw.json.buf);\n+\n+\tstrbuf_release(&jw.json);\n+\treturn 0;\n+}\n+\n+static inline int my_usage(void)\n+{\n+\tdie(\"usage: '-u' | '[-p] @object ... @end' | '[-p] @array ... @end'\");\n+}\n+\n+int cmd_main(int argc, const char **argv)\n+{\n+\tif (argc <= 1)\n+\t\treturn my_usage();\n+\n+\targc--;\n+\targv++;\n+\n+\tif (argv[0][0] == '-') {\n+\t\tif (!strcmp(argv[0], \"-u\") || !strcmp(argv[0], \"--unit\"))\n+\t\t\treturn unit_tests();\n+\n+\t\tif (!strcmp(argv[0], \"-p\") || !strcmp(argv[0], \"--pretty\")) {\n+\t\t\tpretty = 1;\n+\t\t\targc--;\n+\t\t\targv++;\n+\t\t\tif (argc < 1)\n+\t\t\t\treturn my_usage();\n+\t\t}\n+\t}\n+\n+\treturn scripted(argc, argv);\n+}\ndiff --git a/t/t0019-json-writer.sh b/t/t0019-json-writer.sh\nnew file mode 100755\nindex 0000000..a04c055\n--- /dev/null\n+++ b/t/t0019-json-writer.sh\n@@ -0,0 +1,253 @@\n+#!/bin/sh\n+\n+test_description='test json-writer JSON generation'\n+. ./test-lib.sh\n+\n+test_expect_success 'unit test of json-writer routines' '\n+\ttest-json-writer -u\n+'\n+\n+test_expect_success 'trivial object' '\n+\tcat >expect <<-\\EOF &&\n+\t{}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'trivial array' '\n+\tcat >expect <<-\\EOF &&\n+\t[]\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@array \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'simple object' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"c\":3.14,\"d\":true,\"e\":false,\"f\":null}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-string a abc \\\n+\t\t\t@object-int b 42 \\\n+\t\t\t@object-double c 2 3.140 \\\n+\t\t\t@object-true d \\\n+\t\t\t@object-false e \\\n+\t\t\t@object-null f \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'simple array' '\n+\tcat >expect <<-\\EOF &&\n+\t[\"abc\",42,3.14,true,false,null]\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@array \\\n+\t\t\t@array-string abc \\\n+\t\t\t@array-int 42 \\\n+\t\t\t@array-double 2 3.140 \\\n+\t\t\t@array-true \\\n+\t\t\t@array-false \\\n+\t\t\t@array-null \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'escape quoting string' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\\\\def\"}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-string a abc\\\\def \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'escape quoting string 2' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\\\"def\"}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-string a abc\\\"def \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline object' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"sub1\":{\"c\":3.14,\"d\":true,\"sub2\":{\"e\":false,\"f\":null}}}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-string a abc \\\n+\t\t\t@object-int b 42 \\\n+\t\t\t@object-object \"sub1\" \\\n+\t\t\t\t@object-double c 2 3.140 \\\n+\t\t\t\t@object-true d \\\n+\t\t\t\t@object-object \"sub2\" \\\n+\t\t\t\t\t@object-false e \\\n+\t\t\t\t\t@object-null f \\\n+\t\t\t\t@end \\\n+\t\t\t@end \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline array' '\n+\tcat >expect <<-\\EOF &&\n+\t[\"abc\",42,[3.14,true,[false,null]]]\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@array \\\n+\t\t\t@array-string abc \\\n+\t\t\t@array-int 42 \\\n+\t\t\t@array-array \\\n+\t\t\t\t@array-double 2 3.140 \\\n+\t\t\t\t@array-true \\\n+\t\t\t\t@array-array \\\n+\t\t\t\t\t@array-false \\\n+\t\t\t\t\t@array-null \\\n+\t\t\t\t@end \\\n+\t\t\t@end \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline object and array' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"sub1\":{\"c\":3.14,\"d\":true,\"sub2\":[false,null]}}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-string a abc \\\n+\t\t\t@object-int b 42 \\\n+\t\t\t@object-object \"sub1\" \\\n+\t\t\t\t@object-double c 2 3.140 \\\n+\t\t\t\t@object-true d \\\n+\t\t\t\t@object-array \"sub2\" \\\n+\t\t\t\t\t@array-false \\\n+\t\t\t\t\t@array-null \\\n+\t\t\t\t@end \\\n+\t\t\t@end \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'nested inline object and array 2' '\n+\tcat >expect <<-\\EOF &&\n+\t{\"a\":\"abc\",\"b\":42,\"sub1\":{\"c\":3.14,\"d\":true,\"sub2\":[false,{\"g\":0,\"h\":1},null]}}\n+\tEOF\n+\ttest-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-string a abc \\\n+\t\t\t@object-int b 42 \\\n+\t\t\t@object-object \"sub1\" \\\n+\t\t\t\t@object-double c 2 3.140 \\\n+\t\t\t\t@object-true d \\\n+\t\t\t\t@object-array \"sub2\" \\\n+\t\t\t\t\t@array-false \\\n+\t\t\t\t\t@array-object \\\n+\t\t\t\t\t\t@object-int g 0 \\\n+\t\t\t\t\t\t@object-int h 1 \\\n+\t\t\t\t\t@end \\\n+\t\t\t\t\t@array-null \\\n+\t\t\t\t@end \\\n+\t\t\t@end \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'pretty nested inline object and array 2' '\n+\tcat >expect <<EOF &&\n+{\n+  \"a\": \"abc\",\n+  \"b\": 42,\n+  \"sub1\": {\n+    \"c\": 3.14,\n+    \"d\": true,\n+    \"sub2\": [\n+      false,\n+      {\n+        \"g\": 0,\n+        \"h\": 1\n+      },\n+      null\n+    ]\n+  }\n+}\n+EOF\n+\ttest-json-writer >actual \\\n+\t\t--pretty \\\n+\t\t@object \\\n+\t\t\t@object-string a abc \\\n+\t\t\t@object-int b 42 \\\n+\t\t\t@object-object \"sub1\" \\\n+\t\t\t\t@object-double c 2 3.140 \\\n+\t\t\t\t@object-true d \\\n+\t\t\t\t@object-array \"sub2\" \\\n+\t\t\t\t\t@array-false \\\n+\t\t\t\t\t@array-object \\\n+\t\t\t\t\t\t@object-int g 0 \\\n+\t\t\t\t\t\t@object-int h 1 \\\n+\t\t\t\t\t@end \\\n+\t\t\t\t\t@array-null \\\n+\t\t\t\t@end \\\n+\t\t\t@end \\\n+\t\t@end &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'bogus: array element in object' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@array-string abc \\\n+\t\t@end\n+'\n+\n+test_expect_success 'bogus: object element in array' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@array \\\n+\t\t\t@object-string a abc \\\n+\t\t@end\n+'\n+\n+test_expect_success 'bogus: unterminated child' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-object \"sub1\" \\\n+\t\t\t@end\n+'\n+\n+test_expect_success 'bogus: unterminted top level' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@object\n+'\n+\n+test_expect_success 'bogus: first term' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@object-int a 0\n+'\n+\n+test_expect_success 'bogus: missing val param' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-int a \\\n+\t\t@end\n+'\n+\n+test_expect_success 'bogus: extra token after val param' '\n+\ttest_must_fail test-json-writer >actual \\\n+\t\t@object \\\n+\t\t\t@object-int a 0 1 \\\n+\t\t@end\n+'\n+\n+test_done\n-- \n2.9.3\n\n"},{"id":"343179","messageId":"CAKk8ispn6kYQUqPbMc74HfdZ6hj6BwtgytgD5guLLfauspDKuA@mail.gmail.com","threadId":"48168","inReplyTo":"20180327210233.29076-2-git@jeffhostetler.com","subject":"Re: [PATCH v5] json_writer: new routines to create data in JSON format","fromName":"Wink Saville","fromEmail":"wink@saville.com","sentAt":"2018-03-27T22:03:54Z","receivedAt":"2018-03-27T22:04:22Z","isPatch":true,"sender":{"key":"wink@saville.com","avatar":"https://avatars.githubusercontent.com/u/1024284?v=4"},"body":"On Tue, Mar 27, 2018 at 2:02 PM <git@jeffhostetler.com> wrote:\n>\n> From: Jeff Hostetler <jeffhost@microsoft.com>\n>\n> Add a series of jw_ routines and \"struct json_writer\" structure to compose\n> JSON data.  The resulting string data can then be output by commands wanting\n> to support a JSON output format.\n\n<snip>\n\n>\n>\n>\n>  void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value)\n> +{\n> +       object_common(jw, key);\n> +       strbuf_addf(&jw->json, \"%\"PRIdMAX, (intmax_t)value);\n\nCast is not necessary.\n\n> +}\n> +\n\n<snip>\n\n>\n> +void jw_array_intmax(struct json_writer *jw, intmax_t value)\n> +{\n> +       array_common(jw);\n> +       strbuf_addf(&jw->json, \"%\"PRIdMAX, (intmax_t)value);\n\nCast is not necessary\n\n>\n> +}\n> +\n\n<snip>\n\n-- Wink\n"}]}