json_out: new module for authoring JSON strings.
authorRusty Russell <rusty@rustcorp.com.au>
Tue, 21 May 2019 06:41:58 +0000 (16:11 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Wed, 22 May 2019 07:28:58 +0000 (16:58 +0930)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ccan/json_out/LICENSE [new symlink]
ccan/json_out/_info [new file with mode: 0644]
ccan/json_out/json_out.c [new file with mode: 0644]
ccan/json_out/json_out.h [new file with mode: 0644]
ccan/json_out/test/run-debugging.c [new file with mode: 0644]
ccan/json_out/test/run-move_cb.c [new file with mode: 0644]
ccan/json_out/test/run.c [new file with mode: 0644]

diff --git a/ccan/json_out/LICENSE b/ccan/json_out/LICENSE
new file mode 120000 (symlink)
index 0000000..2354d12
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/BSD-MIT
\ No newline at end of file
diff --git a/ccan/json_out/_info b/ccan/json_out/_info
new file mode 100644 (file)
index 0000000..fbb4fa5
--- /dev/null
@@ -0,0 +1,82 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * json_out - Code for creating simple JSON output.
+ *
+ * This code helps you create well-formed JSON strings.
+ *
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ * License: BSD-MIT
+ *
+ * Example:
+ *     // Given "a 1 true" outputs {"argv1":"a","argv2":1,"argv3":true}
+ *     // Print arguments as a JSON array.
+ *     #include <ccan/json_out/json_out.h>
+ *     #include <stdio.h>
+ *     #include <string.h>
+ *     #include <unistd.h>
+ *
+ *     // Simplistic test to see if str needs quotes.
+ *     static bool can_be_json_literal(const char *str)
+ *     {
+ *             char *endp;
+ *             if (strtol(str, &endp, 10) != LONG_MIN
+ *                 && endp != str
+ *                 && *endp == '\0')
+ *                     return true;
+ *             return !strcmp(str, "true")
+ *                     || !strcmp(str, "false")
+ *                     || !strcmp(str, "null");
+ *     }
+ *             
+ *     int main(int argc, char *argv[])
+ *     {
+ *             struct json_out *jout = json_out_new(NULL);
+ *             size_t len;
+ *             const char *p;
+ *
+ *             json_out_start(jout, NULL, '{');
+ *             for (int i = 1; i < argc; i++) {
+ *                     char fieldname[80];
+ *                     sprintf(fieldname, "argv%i", i);
+ *                     json_out_add(jout, fieldname,
+ *                                  !can_be_json_literal(argv[i]),
+ *                                  "%s", argv[i]);
+ *             }
+ *             json_out_end(jout, '}');
+ *             // Force appending of \n
+ *             json_out_direct(jout, 1)[0] = '\n';
+ *             json_out_finished(jout);
+ *
+ *             // Now write it out.
+ *             while ((p = json_out_contents(jout, &len)) != NULL) {
+ *                     int i = write(STDOUT_FILENO, p, len);
+ *                     if (i <= 0)
+ *                             exit(1);
+ *                     json_out_consume(jout, i);
+ *             }
+ *
+ *             tal_free(jout);
+ *             return 0;
+ *     }
+ *     
+ */
+int main(int argc, char *argv[])
+{
+       /* Expect exactly one argument */
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/compiler\n");
+               printf("ccan/json_escape\n");
+               printf("ccan/membuf\n");
+               printf("ccan/tal\n");
+               printf("ccan/typesafe_cb\n");
+               return 0;
+       }
+
+       return 1;
+}
diff --git a/ccan/json_out/json_out.c b/ccan/json_out/json_out.c
new file mode 100644 (file)
index 0000000..b8c798f
--- /dev/null
@@ -0,0 +1,292 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include <ccan/json_escape/json_escape.h>
+#include <ccan/json_out/json_out.h>
+#include <ccan/membuf/membuf.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+struct json_out {
+       /* Callback if we reallocate. */
+       void (*move_cb)(struct json_out *jout, ptrdiff_t delta, void *arg);
+       void *cb_arg;
+       
+#ifdef CCAN_JSON_OUT_DEBUG
+       /* tal_arr of types ( or [ we're enclosed in. */
+       char *wrapping;
+#endif
+       /* True if we haven't yet put an element in current wrapping */
+       bool empty;
+
+       /* Output. */
+       MEMBUF(char) outbuf;
+};
+
+/* Realloc helper for tal membufs */
+static void *membuf_tal_realloc(struct membuf *mb,
+                               void *rawelems, size_t newsize)
+{
+       char *p = rawelems;
+
+       tal_resize(&p, newsize);
+       return p;
+}
+
+struct json_out *json_out_new(const tal_t *ctx)
+{
+       struct json_out *jout = tal(ctx, struct json_out);
+
+       membuf_init(&jout->outbuf,
+                   tal_arr(jout, char, 64), 64, membuf_tal_realloc);
+#ifdef CCAN_JSON_OUT_DEBUG
+       jout->wrapping = tal_arr(jout, char, 0);
+#endif
+       jout->empty = true;
+       jout->move_cb = NULL;
+       return jout;
+}
+
+void json_out_call_on_move_(struct json_out *jout,
+                           void (*cb)(struct json_out *jout, ptrdiff_t delta,
+                                      void *arg),
+                           void *arg)
+{
+       if (cb)
+               assert(!jout->move_cb);
+       jout->move_cb = cb;
+       jout->cb_arg = arg;
+}
+
+struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src)
+{
+       size_t num_elems = membuf_num_elems(&src->outbuf);
+       char *elems = membuf_elems(&src->outbuf);
+       struct json_out *jout = tal_dup(ctx, struct json_out, src);
+       membuf_init(&jout->outbuf,
+                   tal_dup_arr(jout, char, elems, num_elems, 0),
+                   num_elems, membuf_tal_realloc);
+       membuf_added(&jout->outbuf, num_elems);
+#ifdef CCAN_JSON_OUT_DEBUG
+       jout->wrapping = tal_dup_arr(jout, char,
+                                    jout->wrapping, tal_count(jout->wrapping),
+                                    0);
+#endif
+       return jout;
+}
+
+static void indent(struct json_out *jout, char type)
+{
+#ifdef CCAN_JSON_OUT_DEBUG
+       size_t n = tal_count(jout->wrapping);
+       tal_resize(&jout->wrapping, n+1);
+       jout->wrapping[n] = type;
+#endif
+       jout->empty = true;
+}
+
+static void unindent(struct json_out *jout, char type)
+{
+#ifdef CCAN_JSON_OUT_DEBUG
+       size_t indent = tal_count(jout->wrapping);
+       assert(indent > 0);
+       /* Both [ and ] and { and } are two apart in ASCII */
+       assert(jout->wrapping[indent-1] == type - 2);
+       tal_resize(&jout->wrapping, indent-1);
+#endif
+       jout->empty = false;
+}
+
+/* Make sure jout->outbuf has room for len: return pointer */
+static char *mkroom(struct json_out *jout, size_t len)
+{
+       ptrdiff_t delta = membuf_prepare_space(&jout->outbuf, len);
+
+       if (delta && jout->move_cb)
+               jout->move_cb(jout, delta, jout->cb_arg);
+
+       return membuf_space(&jout->outbuf);
+}
+
+static void check_fieldname(const struct json_out *jout,
+                           const char *fieldname)
+{
+#ifdef CCAN_JSON_OUT_DEBUG
+       size_t n = tal_count(jout->wrapping);
+       if (n == 0)
+               /* Can't have a fieldname if not in anything! */
+               assert(!fieldname);
+       else if (jout->wrapping[n-1] == '[')
+               /* No fieldnames in arrays. */
+               assert(!fieldname);
+       else {
+               /* Must have fieldnames in objects. */
+               assert(fieldname);
+               /* We don't escape this for you */
+               assert(!json_escape_needed(fieldname, strlen(fieldname)));
+       }
+#endif
+}
+
+char *json_out_member_direct(struct json_out *jout,
+                            const char *fieldname, size_t extra)
+{
+       char *dest;
+
+       /* Prepend comma if required. */
+       if (!jout->empty)
+               extra++;
+
+       check_fieldname(jout, fieldname);
+       if (fieldname)
+               extra += 1 + strlen(fieldname) + 2;
+
+       if (!extra) {
+               dest = NULL;
+               goto out;
+       }
+
+       dest = mkroom(jout, extra);
+
+       if (!jout->empty)
+               *(dest++) = ',';
+       if (fieldname) {
+               *(dest++) = '"';
+               memcpy(dest, fieldname, strlen(fieldname));
+               dest += strlen(fieldname);
+               *(dest++) = '"';
+               *(dest++) = ':';
+       }
+       membuf_added(&jout->outbuf, extra);
+
+out:
+       jout->empty = false;
+       return dest;
+}
+
+void json_out_start(struct json_out *jout, const char *fieldname, char type)
+{
+       assert(type == '[' || type == '{');
+       json_out_member_direct(jout, fieldname, 1)[0] = type;
+       indent(jout, type);
+}
+
+void json_out_end(struct json_out *jout, char type)
+{
+       assert(type == '}' || type == ']');
+       json_out_direct(jout, 1)[0] = type;
+       unindent(jout, type);
+}
+
+void json_out_addv(struct json_out *jout,
+                  const char *fieldname,
+                  bool quote,
+                  const char *fmt,
+                  va_list ap)
+{
+       size_t fmtlen, avail;
+       va_list ap2;
+       char *dst;
+
+       json_out_member_direct(jout, fieldname, 0);
+
+       /* Make a copy in case we need it below. */
+       va_copy(ap2, ap);
+
+       /* We can use any additional space, but need room for ". */
+       avail = membuf_num_space(&jout->outbuf);
+       if (quote) {
+               if (avail < 2)
+                       avail = 0;
+               else
+                       avail -= 2;
+       }
+
+       /* Try printing in place first. */
+       dst = membuf_space(&jout->outbuf);
+       fmtlen = vsnprintf(dst + quote, avail, fmt, ap);
+
+       /* Horrible subtlety: vsnprintf *will* NUL terminate, even if it means
+        * chopping off the last character.  So if fmtlen ==
+        * membuf_num_space(&jout->outbuf), the result was truncated! */
+       if (fmtlen + (int)quote*2 >= membuf_num_space(&jout->outbuf)) {
+               /* Make room for NUL terminator, even though we don't want it */
+               dst = mkroom(jout, fmtlen + 1 + (int)quote*2);
+               vsprintf(dst + quote, fmt, ap2);
+       }
+
+       /* Of course, if we need to escape it, we have to redo it all. */
+       if (json_escape_needed(dst + quote, fmtlen)) {
+               struct json_escape *e;
+               e = json_escape_len(NULL, dst + quote, fmtlen);
+               fmtlen = strlen(e->s);
+               dst = mkroom(jout, fmtlen + (int)quote*2);
+               memcpy(dst + quote, e, fmtlen);
+               tal_free(e);
+       }
+
+       if (quote) {
+               dst[0] = '"';
+               dst[fmtlen+1] = '"';
+       }
+       membuf_added(&jout->outbuf, fmtlen + (int)quote*2);
+       va_end(ap2);
+}
+
+void json_out_add(struct json_out *jout,
+                 const char *fieldname,
+                 bool quote,
+                 const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       json_out_addv(jout, fieldname, quote, fmt, ap);
+       va_end(ap);
+}
+
+void json_out_addstr(struct json_out *jout,
+                    const char *fieldname,
+                    const char *str)
+{
+       size_t len = strlen(str);
+       char *p;
+
+       p = json_out_member_direct(jout, fieldname, len + 2);
+       p[0] = p[1+len] = '"';
+       memcpy(p+1, str, len);
+}
+
+void json_out_add_splice(struct json_out *jout,
+                        const char *fieldname,
+                        const struct json_out *src)
+{
+       const char *p;
+       size_t len;
+
+       p = json_out_contents(src, &len);
+       memcpy(json_out_member_direct(jout, fieldname, len), p, len);
+}
+
+char *json_out_direct(struct json_out *jout, size_t len)
+{
+       char *p = mkroom(jout, len);
+       membuf_added(&jout->outbuf, len);
+       return p;
+}
+
+void json_out_finished(const struct json_out *jout)
+{
+#ifdef CCAN_JSON_OUT_DEBUG
+       assert(tal_count(jout->wrapping) == 0);
+#endif
+}
+
+const char *json_out_contents(const struct json_out *jout, size_t *len)
+{
+       *len = membuf_num_elems(&jout->outbuf);
+       return *len ? membuf_elems(&jout->outbuf) : NULL;
+}
+
+void json_out_consume(struct json_out *jout, size_t len)
+{
+       membuf_consume(&jout->outbuf, len);
+}
diff --git a/ccan/json_out/json_out.h b/ccan/json_out/json_out.h
new file mode 100644 (file)
index 0000000..a3ebdb1
--- /dev/null
@@ -0,0 +1,189 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#ifndef CCAN_JSON_OUT_H
+#define CCAN_JSON_OUT_H
+#include <ccan/compiler/compiler.h>
+#include <ccan/tal/tal.h>
+#include <ccan/typesafe_cb/typesafe_cb.h>
+#include <stddef.h>
+
+struct json_out;
+
+/**
+ * json_out_new - allocate a json_out stream.
+ * @ctx: the tal_context to allocate from, or NULL
+ */
+struct json_out *json_out_new(const tal_t *ctx);
+
+/**
+ * json_out_call_on_move - callback for when buffer is reallocated.
+ * @jout: the json_out object to attach to.
+ * @cb: the callback to call.
+ * @arg: the argument to @cb (must match type).
+ *
+ * A NULL @cb disables.  You can't currently have more than one callback.
+ * The @delta argument to @cb is the difference between the old location
+ * and the new one, and is never zero.
+ */
+#define json_out_call_on_move(jout, cb, arg)                           \
+       json_out_call_on_move_((jout),                                  \
+                              typesafe_cb_preargs(void, void *,        \
+                                                  (cb), (arg),         \
+                                                  struct json_out *,   \
+                                                  ptrdiff_t),          \
+                              (arg))
+
+void json_out_call_on_move_(struct json_out *jout,
+                           void (*cb)(struct json_out *jout, ptrdiff_t delta,
+                                      void *arg),
+                           void *arg);
+
+/**
+ * json_out_dup - duplicate a json_out stream.
+ * @ctx: the tal_context to allocate from, or NULL
+ * @src: the json_out to copy.
+ */
+struct json_out *json_out_dup(const tal_t *ctx, const struct json_out *src);
+
+/**
+ * json_out_start - start an array or object.
+ * @jout: the json_out object to write into.
+ * @fieldname: the fieldname, if inside an object, or NULL if inside an array.
+ * @type: '[' or '{' to start an array or object, respectively.
+ *
+ * Literally writes '"@fieldname": @type' or '@type ' if fieldname is NULL.
+ * @fieldname must not need JSON escaping.
+ */
+void json_out_start(struct json_out *jout, const char *fieldname, char type);
+
+/**
+ * json_out_end - end an array or object.
+ * @jout: the json_out object to write into.
+ * @type: '}' or ']' to end an array or object, respectively.
+ *
+ * Literally writes ']' or '}', keeping track of whether we need to append
+ * a comma.
+ */
+void json_out_end(struct json_out *jout, char type);
+
+/**
+ * json_out_add - add a formatted member.
+ * @jout: the json_out object to write into.
+ * @fieldname: optional fieldname to prepend (must not need escaping).
+ * @quote: if true, surround fmt by " and ".
+ * @fmt...: the printf-style format
+ *
+ * If you're in an array, @fieldname must be NULL.  If you're in an
+ * object, @fieldname must be non-NULL.  This is checked if
+ * CCAN_JSON_OUT_DEBUG is defined.
+ * @fieldname must not need JSON escaping.
+ *
+ * If the resulting string requires escaping, we call json_escape().
+ */
+PRINTF_FMT(4,5)
+void json_out_add(struct json_out *jout,
+                 const char *fieldname,
+                 bool quote,
+                 const char *fmt, ...);
+
+/**
+ * json_out_addv - add a formatted member (vararg variant)
+ * @jout: the json_out object to write into.
+ * @fieldname: optional fieldname to prepend.
+ * @quote: if true, surround fmt by " and ".
+ * @fmt: the printf-style format
+ * @ap: the argument list.
+ *
+ * See json_out_add() above.
+ */
+void json_out_addv(struct json_out *jout,
+                  const char *fieldname,
+                  bool quote,
+                  const char *fmt,
+                  va_list ap);
+
+/**
+ * json_out_addstr - convenience helper to add a string field.
+ * @jout: the json_out object to write into.
+ * @fieldname: optional fieldname to prepend.
+ * @str: the string to add (must not be NULL).
+ *
+ * Equivalent to json_out_add(@jout, @fieldname, true, "%s", @str);
+ */
+void json_out_addstr(struct json_out *jout,
+                    const char *fieldname,
+                    const char *str);
+
+/**
+ * json_out_member_direct - add a field, with direct access.
+ * @jout: the json_out object to write into.
+ * @fieldname: optional fieldname to prepend.
+ * @extra: how many bytes to allocate.
+ *
+ * @fieldname must not need JSON escaping.  Returns a direct pointer into
+ * the @extra bytes.
+ *
+ * This allows you to write your own efficient type-specific helpers.
+ */
+char *json_out_member_direct(struct json_out *jout,
+                            const char *fieldname, size_t extra);
+
+/**
+ * json_out_direct - make room in output and access directly.
+ * @jout: the json_out object to write into.
+ * @len: the length to allocate.
+ *
+ * This lets you access the json_out stream directly, to save a copy,
+ * if you know exactly how much you will write.
+ *
+ * Returns a pointer to @len bytes at the end of @jout.
+ *
+ * This is dangerous, since it doesn't automatically prepend a ","
+ * like the internal logic does, but can be used (carefully) to add
+ * entire objects, or whitespace.
+ */
+char *json_out_direct(struct json_out *jout, size_t extra);
+
+/**
+ * json_out_add_splice - copy a field from another json_out.
+ * @jout: the json_out object to write into.
+ * @fieldname: optional fieldname to prepend.
+ * @src: the json_out object to copy from.
+ *
+ * This asserts that @src is well-formed (as per json_out_finished()),
+ * then places it into @jout with optional @fieldname prepended.  This
+ * can be used to assemble sub-objects for your JSON and then copy
+ * them in.
+ *
+ * Note that it will call json_out_contents(@src), so it expects that
+ * object to be unconsumed.
+ */
+void json_out_add_splice(struct json_out *jout,
+                        const char *fieldname,
+                        const struct json_out *src);
+
+/**
+ * json_out_finished - assert that the json buffer is finished.
+ * @jout: the json_out object written to.
+ *
+ * This simply causes internal assertions that all arrays and objects are
+ * finished.  It needs CCAN_JSON_OUT_DEBUG defined to have any effect.
+ */
+void json_out_finished(const struct json_out *jout);
+
+/**
+ * json_out_contents - read contents from json_out stream.
+ * @jout: the json_out object we want to read from.
+ * @len: set to the length of the buffer returned.
+ *
+ * This returns a pointer into the JSON written so far.  Returns NULL
+ * and sets @len to 0 if there's nothing left in the buffer.
+ */
+const char *json_out_contents(const struct json_out *jout, size_t *len);
+
+/**
+ * json_out_consume - discard contents from json_out stream.
+ * @jout: the json_out object we read from.
+ * @len: the length to consume (must be <= @len from json_out_contents)
+ */
+void json_out_consume(struct json_out *jout, size_t len);
+#endif /* CCAN_JSON_OUT_H */
diff --git a/ccan/json_out/test/run-debugging.c b/ccan/json_out/test/run-debugging.c
new file mode 100644 (file)
index 0000000..e239d8e
--- /dev/null
@@ -0,0 +1,2 @@
+#define CCAN_JSON_OUT_DEBUG 1
+#include "run.c"
diff --git a/ccan/json_out/test/run-move_cb.c b/ccan/json_out/test/run-move_cb.c
new file mode 100644 (file)
index 0000000..445b426
--- /dev/null
@@ -0,0 +1,48 @@
+#include <ccan/json_out/json_out.h>
+/* Include the C files directly. */
+#include <ccan/json_out/json_out.c>
+#include <ccan/tap/tap.h>
+
+static const char *ptr;
+static bool called = false;
+
+static void move_cb(struct json_out *jout, ptrdiff_t delta,
+                   struct json_out *arg)
+{
+       ptr += delta;
+       called = true;
+       ok1(arg == jout);
+}
+
+int main(void)
+{
+       const tal_t *ctx = tal(NULL, char);
+       struct json_out *jout;
+       char *p;
+       size_t len;
+
+       /* This is how many tests you plan to run */
+       plan_tests(3);
+
+       /* Test nested arrays. */
+       jout = json_out_new(ctx);
+       json_out_call_on_move(jout, move_cb, jout);
+
+       json_out_start(jout, NULL, '{');
+       ptr = json_out_contents(jout, &len);
+
+       p = json_out_member_direct(jout, "fieldname", 102);
+       p[0] = '"';
+       p[101] = '"';
+       memset(p+1, 'p', 100);
+
+       json_out_finished(jout);
+       ok1(called);
+       /* Contents should have moved correctly. */
+       ok1(json_out_contents(jout, &len) == ptr);
+
+       tal_free(ctx);
+
+       /* This exits depending on whether all tests passed */
+       return exit_status();
+}
diff --git a/ccan/json_out/test/run.c b/ccan/json_out/test/run.c
new file mode 100644 (file)
index 0000000..f6f9d41
--- /dev/null
@@ -0,0 +1,141 @@
+#include <ccan/json_out/json_out.h>
+/* Include the C files directly. */
+#include <ccan/json_out/json_out.c>
+#include <ccan/tap/tap.h>
+
+static void test_json_out_add(const tal_t *ctx,
+                             char c, bool quote, const char *escaped)
+{
+       /* 64 is the size of the initial buf, so we test that. */
+       for (size_t i = 1; i < 64; i++) {
+               struct json_out *jout;
+               char str[64 + 1];
+               const char *r;
+               size_t len;
+               char fieldname[64 + 1];
+
+               jout = json_out_new(ctx);
+               json_out_start(jout, NULL, '{');
+               memset(str, c, i);
+               str[i] = '\0';
+               memset(fieldname, 'f', i);
+               fieldname[i] = '\0';
+               json_out_add(jout, fieldname, quote, "%s", str);
+               json_out_end(jout, '}');
+               json_out_finished(jout);
+
+               r = json_out_contents(jout, &len);
+               ok1(len == strlen("{\"") + i + strlen("\":")
+                   + quote * 2 + strlen(escaped) * i + strlen("}"));
+
+               ok1(len > strlen("{\""));
+               ok1(memcmp(r, "{\"", strlen("{\"")) == 0);
+               json_out_consume(jout, strlen("{\""));
+
+               r = json_out_contents(jout, &len);
+               ok1(len > strlen(fieldname));
+               ok1(memcmp(r, fieldname, strlen(fieldname)) == 0);
+               json_out_consume(jout, strlen(fieldname));
+
+               r = json_out_contents(jout, &len);
+               ok1(len > strlen("\":"));
+               ok1(memcmp(r, "\":", strlen("\":")) == 0);
+               json_out_consume(jout, strlen("\":"));
+
+               r = json_out_contents(jout, &len);
+               if (quote) {
+                       ok1(len > 0);
+                       ok1(r[0] == '"');
+                       json_out_consume(jout, 1);
+               }
+               for (size_t n = 0; n < i; n++) {
+                       r = json_out_contents(jout, &len);
+                       ok1(len > strlen(escaped));
+                       ok1(memcmp(r, escaped, strlen(escaped)) == 0);
+                       json_out_consume(jout, strlen(escaped));
+               }
+               r = json_out_contents(jout, &len);
+               if (quote) {
+                       ok1(len > 0);
+                       ok1(r[0] == '"');
+                       json_out_consume(jout, 1);
+               }
+               r = json_out_contents(jout, &len);
+               ok1(len == 1);
+               ok1(memcmp(r, "}", 1) == 0);
+               json_out_consume(jout, 1);
+               ok1(!json_out_contents(jout, &len));
+               ok1(len == 0);
+       }
+}
+
+static void json_eq(const struct json_out *jout, const char *expect)
+{
+       size_t len;
+       const char *p;
+
+       json_out_finished(jout);
+       p = json_out_contents(jout, &len);
+       ok1(len == strlen(expect));
+       ok1(memcmp(expect, p, len) == 0);
+}
+       
+int main(void)
+{
+       const tal_t *ctx = tal(NULL, char);
+       struct json_out *jout;
+       char *p;
+
+       /* This is how many tests you plan to run */
+       plan_tests(14689);
+
+       /* Simple tests */
+       test_json_out_add(ctx, '1', false, "1");
+       test_json_out_add(ctx, 'x', true, "x");
+       test_json_out_add(ctx, '\n', true, "\\n");
+
+       /* Test nested arrays. */
+       jout = json_out_new(ctx);
+       for (size_t i = 0; i < 64; i++)
+               json_out_start(jout, NULL, '[');
+       for (size_t i = 0; i < 64; i++)
+               json_out_end(jout, ']');
+       json_eq(jout, "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]");
+
+       /* Test nested objects. */
+       jout = json_out_new(ctx);
+       json_out_start(jout, NULL, '{');
+       for (size_t i = 0; i < 63; i++)
+               json_out_start(jout, "x", '{');
+       for (size_t i = 0; i < 64; i++)
+               json_out_end(jout, '}');
+       json_eq(jout, "{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{\"x\":{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}");
+
+       jout = json_out_new(ctx);
+       json_out_start(jout, NULL, '{');
+       p = json_out_member_direct(jout, "x", 7);
+       memcpy(p, "\"hello\"", 7);
+       json_out_end(jout, '}');
+       json_eq(jout, "{\"x\":\"hello\"}");
+
+       jout = json_out_new(ctx);
+       p = json_out_direct(jout, strlen("{\"x\":\"hello\"}\n"));
+       memcpy(p, "{\"x\":\"hello\"}\n", strlen("{\"x\":\"hello\"}\n"));
+       json_eq(jout, "{\"x\":\"hello\"}\n");
+
+       jout = json_out_new(ctx);
+       json_out_start(jout, NULL, '{');
+       struct json_out *jout2 = json_out_new(ctx);
+       json_out_start(jout2, NULL, '{');
+       json_out_addstr(jout2, "x", "hello");
+       json_out_end(jout2, '}');
+       json_out_finished(jout2);
+       json_out_add_splice(jout, "inner", jout2);
+       json_out_end(jout, '}');
+       json_eq(jout, "{\"inner\":{\"x\":\"hello\"}}");
+
+       tal_free(ctx);
+
+       /* This exits depending on whether all tests passed */
+       return exit_status();
+}