--- /dev/null
+../../licenses/BSD-MIT
\ No newline at end of file
--- /dev/null
+#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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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 */
--- /dev/null
+#define CCAN_JSON_OUT_DEBUG 1
+#include "run.c"
--- /dev/null
+#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();
+}
--- /dev/null
+#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();
+}