Explorar o código

Implement json_printf API

PUBLISHED_FROM=42625d68040d0ef73604edc2443a5c4947fd6d09
Sergey Lyubka %!s(int64=9) %!d(string=hai) anos
pai
achega
30e7957bb0
Modificáronse 4 ficheiros con 291 adicións e 237 borrados
  1. 13 34
      docs/api/intro.md
  2. 130 135
      frozen.c
  3. 58 6
      frozen.h
  4. 90 62
      unit_test.c

+ 13 - 34
docs/api/intro.md

@@ -84,42 +84,21 @@ that points to number `"1"`.
 Return: pointer to the found token, or NULL on failure.
 Return: pointer to the found token, or NULL on failure.
 
 
 ```c
 ```c
-int json_emit_long(char *buf, int buf_len, long value);
-int json_emit_double(char *buf, int buf_len, double value);
-int json_emit_quoted_str(char *buf, int buf_len, const char *str);
-int json_emit_unquoted_str(char *buf, int buf_len, const char *str);
-```
+struct json_out out = JSON_OUT_BUF(buf, len);
+struct json_out out = JSON_OUT_FILE(fp);
 
 
-These functions are used to generate JSON string. All of them accept
-a destination buffer and a value to output, and return number of bytes printed.
-Returned value can be bigger then destination buffer size, this is an
-indication of overflow. If there is no overflow, a buffer is guaranteed to
-be nul-terminated. Numbers are printed by `json_emit_double()` and
-`json_emit_int()` functions, strings are printed by `json_emit_quoted_str()`
-function. Values for `null`, `true`, `false`, and characters
-`{`, `}`, `[`, `]`, `,`, `:` are printed by
-`json_emit_raw_str()` function.
+typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap);
 
 
-```c
-    int json_emit(char *buf, int buf_len, const char *format, ...);
+int json_printf(struct json_out *, const char *fmt, ...);
+int json_vprintf(struct json_out *, const char *fmt, va_list ap);
 ```
 ```
+Generate formatted output into a given sting buffer.
+This is a superset of printf() function, with extra format specifiers:
 
 
-A convenience function that generates JSON string using formatted output.
-Characters allowed in `format` string:
-`[`, `]`, `{`, `}`, `,`, `:`, `\r`, `\n`, `\t`, ` `: these characters
-are appended to the output buffer as-is
-
-- `i`: argument must be an `long` value, outputs number  
-- `f`: argument must be a `double` value, outputs number  
-- `v`: arguments must be a `char *` value, followed by `size_t` value, outputs
-  quoted string  
-- `V`: arguments must be a `char *` value, followed by `size_t` value, outputs
-  unquoted string  
-- `s`: arguments must be a `\0`-terminated `char *` value, outputs quoted
-  string  
-- `S`: arguments must be a `\0`-terminated `char *` value, outputs unquoted
-  string  
-- `N`: outputs `null`  
-- `T`: outputs `true`  
-- `F`: outputs `false`  
+- `%B` print json boolean, `true` or `false`. Accepts an `int`.
+- `%I` print int64_t value. Accepts an `int64_t`.
+- `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
+- `%M` invokes a json_printf_callback_t function. That callback function
+ can consume more parameters.
 
 
+Return number of bytes printed.

+ 130 - 135
frozen.c

@@ -19,14 +19,25 @@
 
 
 #define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */
 #define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */
 
 
+#include "frozen.h"
+#include <ctype.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdlib.h>
 #include <string.h>
 #include <string.h>
-#include <stdarg.h>
-#include "frozen.h"
 
 
 #ifdef _WIN32
 #ifdef _WIN32
 #define snprintf _snprintf
 #define snprintf _snprintf
+#define vsnprintf _vsnprintf
+typedef _int64 int64_t;
+#define INT64_FMT "%I64"
+#define UINT64_FMT "%I64"
+#define CONSUME_ARG(ap) (void) va_arg(ap, void *);
+#define va_copy(x, y) x = y
+#else
+#define INT64_FMT "%lld"
+#define UINT64_FMT "%llu"
+#define CONSUME_ARG(ap)
 #endif
 #endif
 
 
 #ifndef FROZEN_REALLOC
 #ifndef FROZEN_REALLOC
@@ -433,160 +444,144 @@ struct json_token *find_json_token(struct json_token *toks, const char *path) {
   return 0;
   return 0;
 }
 }
 
 
-int json_emit_long(char *buf, int buf_len, long int value) {
-  char tmp[20];
-  int n = snprintf(tmp, sizeof(tmp), "%ld", value);
-  strncpy(buf, tmp, buf_len > 0 ? buf_len : 0);
-  return n;
-}
-
-int json_emit_double(char *buf, int buf_len, double value) {
-  char tmp[20];
-  int n = snprintf(tmp, sizeof(tmp), "%g", value);
-  strncpy(buf, tmp, buf_len > 0 ? buf_len : 0);
-  return n;
-}
-
-int json_emit_quoted_str(char *s, int s_len, const char *str, int len) {
-  const char *begin = s, *end = s + s_len, *str_end = str + len;
-  char ch;
-
-#define EMIT(x)          \
-  do {                   \
-    if (s < end) *s = x; \
-    s++;                 \
-  } while (0)
+static int json_encode_string(struct json_out *out, const char *p, size_t len) {
+  size_t i, cl, n = 0;
+  const char *hex_digits = "0123456789abcdef";
+  const char *specials = "btnvfr";
 
 
-  EMIT('"');
-  while (str < str_end) {
-    ch = *str++;
-    switch (ch) {
-      case '"':
-        EMIT('\\');
-        EMIT('"');
-        break;
-      case '\\':
-        EMIT('\\');
-        EMIT('\\');
-        break;
-      case '\b':
-        EMIT('\\');
-        EMIT('b');
-        break;
-      case '\f':
-        EMIT('\\');
-        EMIT('f');
-        break;
-      case '\n':
-        EMIT('\\');
-        EMIT('n');
-        break;
-      case '\r':
-        EMIT('\\');
-        EMIT('r');
-        break;
-      case '\t':
-        EMIT('\\');
-        EMIT('t');
-        break;
-      default:
-        EMIT(ch);
+  for (i = 0; i < len; i++) {
+    unsigned char ch = ((unsigned char *) p)[i];
+    if (ch == '"' || ch == '\\') {
+      n += out->printer(out, "\\", 1);
+      n += out->printer(out, p + i, 1);
+    } else if (ch >= '\b' && ch <= '\r') {
+      n += out->printer(out, "\\", 1);
+      n += out->printer(out, &specials[ch - '\b'], 1);
+    } else if (isprint(ch)) {
+      n += out->printer(out, p + i, 1);
+    } else if ((cl = get_utf8_char_len(ch)) == 1) {
+      n += out->printer(out, "\\u00", 4);
+      n += out->printer(out, &hex_digits[(ch >> 4) % 0xf], 1);
+      n += out->printer(out, &hex_digits[ch % 0xf], 1);
+    } else {
+      n += out->printer(out, p + i, cl);
+      i += cl - 1;
     }
     }
   }
   }
-  EMIT('"');
-  if (s < end) {
-    *s = '\0';
-  }
 
 
-  return s - begin;
+  return n;
 }
 }
 
 
-int json_emit_unquoted_str(char *buf, int buf_len, const char *str, int len) {
-  if (buf_len > 0 && len > 0) {
-    int n = len < buf_len ? len : buf_len;
-    memcpy(buf, str, n);
-    if (n < buf_len) {
-      buf[n] = '\0';
-    }
+int json_printer_buf(struct json_out *out, const char *buf, size_t len) {
+  size_t avail = out->u.buf.size - out->u.buf.len;
+  size_t n = len < avail ? len : avail;
+  memcpy(out->u.buf.buf + out->u.buf.len, buf, n);
+  out->u.buf.len += n;
+  if (out->u.buf.size > 0) {
+    size_t idx = out->u.buf.len;
+    if (idx >= out->u.buf.size) idx = out->u.buf.size - 1;
+    out->u.buf.buf[idx] = '\0';
   }
   }
   return len;
   return len;
 }
 }
 
 
-int json_emit_va(char *s, int s_len, const char *fmt, va_list ap) {
-  const char *end = s + s_len, *str, *orig = s;
-  size_t len;
+int json_printer_file(struct json_out *out, const char *buf, size_t len) {
+  return fwrite(buf, 1, len, out->u.fp);
+}
+
+int json_vprintf(struct json_out *out, const char *fmt, va_list xap) {
+  int len = 0;
+  const char *quote = "\"", *null = "null";
+  va_list ap;
+  va_copy(ap, xap);
 
 
   while (*fmt != '\0') {
   while (*fmt != '\0') {
-    switch (*fmt) {
-      case '[':
-      case ']':
-      case '{':
-      case '}':
-      case ',':
-      case ':':
-      case ' ':
-      case '\r':
-      case '\n':
-      case '\t':
-        if (s < end) {
-          *s = *fmt;
+    if (strchr(":, \r\n\t[]{}", *fmt) != NULL) {
+      len += out->printer(out, fmt, 1);
+      fmt++;
+    } else if (fmt[0] == '%') {
+      char buf[20];
+      size_t skip = 2;
+
+      if (fmt[1] == 'l' && fmt[2] == 'l' && (fmt[3] == 'd' || fmt[3] == 'u')) {
+        int64_t val = va_arg(ap, int64_t);
+        const char *fmt2 = fmt[3] == 'u' ? UINT64_FMT : INT64_FMT;
+        snprintf(buf, sizeof(buf), fmt2, val);
+        len += out->printer(out, buf, strlen(buf));
+      } else if (fmt[1] == 'M') {
+        json_printf_callback_t f = va_arg(ap, json_printf_callback_t);
+        len += f(out, &ap);
+      } else if (fmt[1] == 'B') {
+        int val = va_arg(ap, int);
+        const char *str = val ? "true" : "false";
+        len += out->printer(out, str, strlen(str));
+      } else if (fmt[1] == 'Q') {
+        const char *p = va_arg(ap, char *);
+        if (p == NULL) {
+          len += out->printer(out, null, 4);
+        } else {
+          len += out->printer(out, quote, 1);
+          len += json_encode_string(out, p, strlen(p));
+          len += out->printer(out, quote, 1);
         }
         }
-        s++;
-        break;
-      case 'i':
-        s += json_emit_long(s, end - s, va_arg(ap, long) );
-        break;
-      case 'f':
-        s += json_emit_double(s, end - s, va_arg(ap, double) );
-        break;
-      case 'v':
-        str = va_arg(ap, char *);
-        len = va_arg(ap, size_t);
-        s += json_emit_quoted_str(s, end - s, str, len);
-        break;
-      case 'V':
-        str = va_arg(ap, char *);
-        len = va_arg(ap, size_t);
-        s += json_emit_unquoted_str(s, end - s, str, len);
-        break;
-      case 's':
-        str = va_arg(ap, char *);
-        s += json_emit_quoted_str(s, end - s, str, strlen(str));
-        break;
-      case 'S':
-        str = va_arg(ap, char *);
-        s += json_emit_unquoted_str(s, end - s, str, strlen(str));
-        break;
-      case 'T':
-        s += json_emit_unquoted_str(s, end - s, "true", 4);
-        break;
-      case 'F':
-        s += json_emit_unquoted_str(s, end - s, "false", 5);
-        break;
-      case 'N':
-        s += json_emit_unquoted_str(s, end - s, "null", 4);
-        break;
-      default:
-        return 0;
+      } else {
+        const char *end_of_format_specifier = "sdfFgGlhu.-0123456789";
+        size_t n = strspn(fmt + 1, end_of_format_specifier);
+        char fmt2[20];
+        strncpy(fmt2, fmt, n + 1 > sizeof(fmt2) ? sizeof(fmt2) : n + 1);
+        fmt2[n + 1] = '\0';
+        vsnprintf(buf, sizeof(buf), fmt2, ap);
+        len += out->printer(out, buf, strlen(buf));
+        CONSUME_ARG(ap);
+        skip = n + 1;
+      }
+      fmt += skip;
+    } else if (is_alpha(*fmt)) {
+      len += out->printer(out, quote, 1);
+      while (is_alpha(*fmt)) {
+        len += out->printer(out, fmt, 1);
+        fmt++;
+      }
+      len += out->printer(out, quote, 1);
+    } else {
+      fmt++;
     }
     }
-    fmt++;
-  }
-
-  /* Best-effort to 0-terminate generated string */
-  if (s < end) {
-    *s = '\0';
   }
   }
+  va_end(ap);
 
 
-  return s - orig;
+  return len;
 }
 }
 
 
-int json_emit(char *buf, int buf_len, const char *fmt, ...) {
-  int len;
+int json_printf(struct json_out *out, const char *fmt, ...) {
+  int n;
   va_list ap;
   va_list ap;
-
   va_start(ap, fmt);
   va_start(ap, fmt);
-  len = json_emit_va(buf, buf_len, fmt, ap);
+  n = json_vprintf(out, fmt, ap);
   va_end(ap);
   va_end(ap);
+  return n;
+}
 
 
+int json_printf_array(struct json_out *out, va_list *ap) {
+  int len = 0;
+  char *arr = va_arg(*ap, char *);
+  size_t i, arr_size = va_arg(*ap, size_t);
+  size_t elem_size = va_arg(*ap, size_t);
+  const char *fmt = va_arg(*ap, char *);
+  len += json_printf(out, "[", 1);
+  for (i = 0; arr != NULL && i < arr_size / elem_size; i++) {
+    union {
+      int64_t i;
+      double d;
+    } val;
+    memcpy(&val, arr + i * elem_size,
+           elem_size > sizeof(val) ? sizeof(val) : elem_size);
+    if (i > 0) len += json_printf(out, ", ");
+    if (strchr(fmt, 'f') != NULL) {
+      len += json_printf(out, fmt, val.d);
+    } else {
+      len += json_printf(out, fmt, val.i);
+    }
+  }
+  len += json_printf(out, "]", 1);
   return len;
   return len;
 }
 }

+ 58 - 6
frozen.h

@@ -25,6 +25,8 @@ extern "C" {
 #endif /* __cplusplus */
 #endif /* __cplusplus */
 
 
 #include <stdarg.h>
 #include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
 
 
 enum json_type {
 enum json_type {
   JSON_TYPE_EOF = 0, /* End of parsed tokens marker */
   JSON_TYPE_EOF = 0, /* End of parsed tokens marker */
@@ -54,12 +56,62 @@ int parse_json(const char *json_string, int json_string_length,
 struct json_token *parse_json2(const char *json_string, int string_length);
 struct json_token *parse_json2(const char *json_string, int string_length);
 struct json_token *find_json_token(struct json_token *toks, const char *path);
 struct json_token *find_json_token(struct json_token *toks, const char *path);
 
 
-int json_emit_long(char *buf, int buf_len, long value);
-int json_emit_double(char *buf, int buf_len, double value);
-int json_emit_quoted_str(char *buf, int buf_len, const char *str, int len);
-int json_emit_unquoted_str(char *buf, int buf_len, const char *str, int len);
-int json_emit(char *buf, int buf_len, const char *fmt, ...);
-int json_emit_va(char *buf, int buf_len, const char *fmt, va_list);
+/*
+ * JSON generation API.
+ * struct json_out abstracts output, allowing alternative printing plugins.
+ */
+struct json_out {
+  int (*printer)(struct json_out *, const char *str, size_t len);
+  union {
+    struct {
+      char *buf;
+      size_t size;
+      size_t len;
+    } buf;
+    void *data;
+    FILE *fp;
+  } u;
+};
+
+extern int json_printer_buf(struct json_out *, const char *, size_t);
+extern int json_printer_file(struct json_out *, const char *, size_t);
+
+#define JSON_OUT_BUF(buf, len) \
+  {                            \
+    json_printer_buf, {        \
+      { buf, len, 0 }          \
+    }                          \
+  }
+#define JSON_OUT_FILE(fp)   \
+  {                         \
+    json_printer_file, {    \
+      { (void *) fp, 0, 0 } \
+    }                       \
+  }
+
+typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap);
+
+/*
+ * Generate formatted output into a given sting buffer.
+ * This is a superset of printf() function, with extra format specifiers:
+ *  - `%B` print json boolean, `true` or `false`. Accepts an `int`.
+ *  - `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
+ *  - `%M` invokes a json_printf_callback_t function. That callback function
+ *  can consume more parameters.
+ *
+ * Return number of bytes printed. If the return value is bigger then the
+ * supplied buffer, that is an indicator of overflow. In the overflow case,
+ * overflown bytes are not printed.
+ */
+int json_printf(struct json_out *, const char *fmt, ...);
+int json_vprintf(struct json_out *, const char *fmt, va_list ap);
+
+/*
+ * Helper %M callback that prints contiguous C arrays.
+ * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt
+ * Return number of bytes printed.
+ */
+int json_printf_array(struct json_out *, va_list *ap);
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 90 - 62
unit_test.c

@@ -28,8 +28,8 @@
 
 
 #include "frozen.c"
 #include "frozen.c"
 
 
-#include <stdlib.h>
 #include <stdio.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <string.h>
 
 
 #define FAIL(str, line)                           \
 #define FAIL(str, line)                           \
@@ -66,14 +66,30 @@ static const char *test_errors(void) {
   struct json_token ar[100];
   struct json_token ar[100];
   int size = ARRAY_SIZE(ar);
   int size = ARRAY_SIZE(ar);
   static const char *invalid_tests[] = {
   static const char *invalid_tests[] = {
-      "1", "a:3", "\x01", "{:", " { 1", "{a:\"\n\"}", "{a:1x}", "{a:1e}",
-      "{a:.1}", "{a:0.}", "{a:0.e}", "{a:0.e1}", "{a:0.1e}", "{a:\"\\u\" } ",
-      "{a:\"\\yx\"}", "{a:\"\\u111r\"}", NULL};
-  static const char *incomplete_tests[] = {
-      "", " \r\n\t", "{", " { a", "{a:", "{a:\"", " { a : \"xx", "{a:12",
-      "{a:\"\\uf", "{a:\"\\uff", "{a:\"\\ufff", "{a:\"\\uffff",
-      "{a:\"\\uffff\"", "{a:\"\\uffff\" ,", "{a:n", "{a:nu", "{a:nul",
-      "{a:null", NULL};
+      "1",        "a:3",           "\x01",         "{:",
+      " { 1",     "{a:\"\n\"}",    "{a:1x}",       "{a:1e}",
+      "{a:.1}",   "{a:0.}",        "{a:0.e}",      "{a:0.e1}",
+      "{a:0.1e}", "{a:\"\\u\" } ", "{a:\"\\yx\"}", "{a:\"\\u111r\"}",
+      NULL};
+  static const char *incomplete_tests[] = {"",
+                                           " \r\n\t",
+                                           "{",
+                                           " { a",
+                                           "{a:",
+                                           "{a:\"",
+                                           " { a : \"xx",
+                                           "{a:12",
+                                           "{a:\"\\uf",
+                                           "{a:\"\\uff",
+                                           "{a:\"\\ufff",
+                                           "{a:\"\\uffff",
+                                           "{a:\"\\uffff\"",
+                                           "{a:\"\\uffff\" ,",
+                                           "{a:n",
+                                           "{a:nu",
+                                           "{a:nul",
+                                           "{a:null",
+                                           NULL};
   static const struct {
   static const struct {
     const char *str;
     const char *str;
     int expected_len;
     int expected_len;
@@ -192,56 +208,6 @@ static const char *test_config(void) {
   return NULL;
   return NULL;
 }
 }
 
 
-static const char *test_emit_overflow(void) {
-  char buf[1000];
-
-  memset(buf, 0, sizeof(buf));
-  ASSERT(json_emit_unquoted_str(buf, 0, "hi", 2) == 2);
-  ASSERT(json_emit_quoted_str(buf, 0, "hi", 2) == 4);
-  ASSERT(buf[0] == '\0');
-
-  return NULL;
-}
-
-static const char *test_emit_escapes(void) {
-  const char *s4 = "\"\\\"\\\\\\b\\f\\n\\r\\t\"";
-  char buf[1000];
-  ASSERT(json_emit_quoted_str(buf, sizeof(buf), "\"\\\b\f\n\r\t", 7) > 0);
-  ASSERT(strcmp(buf, s4) == 0);
-  return NULL;
-}
-
-static const char *test_emit(void) {
-  char buf[1000], *p = buf;
-  const char *s5 = "{\"foo\":[-123,1.23,true]}";
-  const char *s6 = "{\"foo\":[-7,true, false,null]}";
-
-  p += json_emit_unquoted_str(p, &buf[sizeof(buf)] - p, "{", 1);
-  p += json_emit_quoted_str(p, &buf[sizeof(buf)] - p, "foo", 3);
-  p += json_emit_unquoted_str(p, &buf[sizeof(buf)] - p, ":[", 2);
-  p += json_emit_long(p, &buf[sizeof(buf)] - p, -123);
-  p += json_emit_unquoted_str(p, &buf[sizeof(buf)] - p, ",", 1);
-  p += json_emit_double(p, &buf[sizeof(buf)] - p, 1.23);
-  p += json_emit_unquoted_str(p, &buf[sizeof(buf)] - p, ",", 1);
-  p += json_emit_unquoted_str(p, &buf[sizeof(buf)] - p, "true", 4);
-  p += json_emit_unquoted_str(p, &buf[sizeof(buf)] - p, "]}", 2);
-
-  ASSERT(strcmp(buf, s5) == 0);
-  ASSERT(p < &buf[sizeof(buf)]);
-
-  ASSERT(json_emit(buf, sizeof(buf), "{v:[i,f,V]}", "foo", 3, (long) -123, 1.23,
-                   "true", 4) > 0);
-  ASSERT(strcmp(buf, s5) == 0);
-
-  ASSERT(json_emit(buf, 4, "{S:i}", "a", 12345) > 4);
-  ASSERT(json_emit(buf, sizeof(buf), "{S:d}", "a", 12345) == 0);
-
-  ASSERT(json_emit(buf, sizeof(buf), "{s:[i,T, F,N]}", "foo", (long) -7) > 0);
-  ASSERT(strcmp(buf, s6) == 0);
-
-  return NULL;
-}
-
 static const char *test_nested(void) {
 static const char *test_nested(void) {
   struct json_token ar[100];
   struct json_token ar[100];
   const char *s = "{ a : [ [1, 2, { b : 2 } ] ] }";
   const char *s = "{ a : [ [1, 2, { b : 2 } ] ] }";
@@ -272,14 +238,76 @@ static const char *test_realloc(void) {
   return NULL;
   return NULL;
 }
 }
 
 
+static const char *test_json_printf(void) {
+  char buf[200] = "";
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    const char *result = "{\"foo\": 123, \"x\": [false, true], \"y\": \"hi\"}";
+    json_printf(&out, "{%Q: %d, x: [%B, %B], y: %Q}", "foo", 123, 0, -1, "hi");
+    ASSERT(strcmp(buf, result) == 0);
+  }
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    int arr[] = {-2387, 943478};
+    json_printf(&out, "%M", json_printf_array, arr, sizeof(arr), sizeof(arr[0]),
+                "%d");
+    ASSERT(strcmp(buf, "[-2387, 943478]") == 0);
+  }
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    double arr[] = {9.32156, 3.1415926};
+    json_printf(&out, "%M", json_printf_array, arr, sizeof(arr), sizeof(arr[0]),
+                "%.2lf");
+    ASSERT(strcmp(buf, "[9.32, 3.14]") == 0);
+  }
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    unsigned short arr[] = {65535, 777};
+    const char *result = "{\"a\": [-1, 777], \"b\": 37}";
+    json_printf(&out, "{a: %M, b: %d}", json_printf_array, arr, sizeof(arr),
+                sizeof(arr[0]), "%hd", 37);
+    printf("==> [%s]\n", buf);
+    ASSERT(strcmp(buf, result) == 0);
+  }
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    const char *result = "{\"a\": \"\\\"\\\\\\r\\nя\\t\\u0002\"}";
+    json_printf(&out, "{a: %Q}", "\"\\\r\nя\t\x02");
+    printf("==> [%s]\n", buf);
+    ASSERT(strcmp(buf, result) == 0);
+  }
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    const char *arr[] = {"hi", "there", NULL};
+    const char *result = "[\"hi\", \"there\", null]";
+    json_printf(&out, "%M", json_printf_array, arr, sizeof(arr), sizeof(arr[0]),
+                "%Q");
+    ASSERT(strcmp(buf, result) == 0);
+  }
+
+  {
+    struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
+    out.u.buf.size = 3;
+    memset(buf, 0, sizeof(buf));
+    ASSERT(json_printf(&out, "{%d}", 123) == 5);
+    ASSERT(memcmp(buf, "{1\x00\x00\x00", 5) == 0);
+  }
+
+  return NULL;
+}
+
 static const char *run_all_tests(void) {
 static const char *run_all_tests(void) {
   RUN_TEST(test_errors);
   RUN_TEST(test_errors);
   RUN_TEST(test_config);
   RUN_TEST(test_config);
-  RUN_TEST(test_emit);
-  RUN_TEST(test_emit_escapes);
-  RUN_TEST(test_emit_overflow);
   RUN_TEST(test_nested);
   RUN_TEST(test_nested);
   RUN_TEST(test_realloc);
   RUN_TEST(test_realloc);
+  RUN_TEST(test_json_printf);
   return NULL;
   return NULL;
 }
 }