Browse Source
Add "struct json_writer" and a series of jw_ routines to compose JSON data into a string buffer. The resulting string may then be printed by commands wanting to support a JSON-like output format. The json_writer is limited to correctly formatting structured data for output. It does not attempt to build an object model of the JSON data. We say "JSON-like" because we do not enforce the Unicode (usually UTF-8) requirement on string fields. Internally, Git does not necessarily have Unicode/UTF-8 data for most fields, so it is currently unclear the best way to enforce that requirement. For example, on Linux pathnames can contain arbitrary 8-bit character data, so a command like "status" would not know how to encode the reported pathnames. We may want to revisit this (or double encode such strings) in the future. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: René Scharfe <l.s.r@web.de> Helped-by: Wink Saville <wink@saville.com> Helped-by: Ramsay Jones <ramsay@ramsayjones.plus.com> Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>maint


8 changed files with 1471 additions and 0 deletions
@ -0,0 +1,414 @@
@@ -0,0 +1,414 @@
|
||||
#include "cache.h" |
||||
#include "json-writer.h" |
||||
|
||||
void jw_init(struct json_writer *jw) |
||||
{ |
||||
strbuf_init(&jw->json, 0); |
||||
strbuf_init(&jw->open_stack, 0); |
||||
jw->need_comma = 0; |
||||
jw->pretty = 0; |
||||
} |
||||
|
||||
void jw_release(struct json_writer *jw) |
||||
{ |
||||
strbuf_release(&jw->json); |
||||
strbuf_release(&jw->open_stack); |
||||
} |
||||
|
||||
/* |
||||
* Append JSON-quoted version of the given string to 'out'. |
||||
*/ |
||||
static void append_quoted_string(struct strbuf *out, const char *in) |
||||
{ |
||||
unsigned char c; |
||||
|
||||
strbuf_addch(out, '"'); |
||||
while ((c = *in++) != '\0') { |
||||
if (c == '"') |
||||
strbuf_addstr(out, "\\\""); |
||||
else if (c == '\\') |
||||
strbuf_addstr(out, "\\\\"); |
||||
else if (c == '\n') |
||||
strbuf_addstr(out, "\\n"); |
||||
else if (c == '\r') |
||||
strbuf_addstr(out, "\\r"); |
||||
else if (c == '\t') |
||||
strbuf_addstr(out, "\\t"); |
||||
else if (c == '\f') |
||||
strbuf_addstr(out, "\\f"); |
||||
else if (c == '\b') |
||||
strbuf_addstr(out, "\\b"); |
||||
else if (c < 0x20) |
||||
strbuf_addf(out, "\\u%04x", c); |
||||
else |
||||
strbuf_addch(out, c); |
||||
} |
||||
strbuf_addch(out, '"'); |
||||
} |
||||
|
||||
static void indent_pretty(struct json_writer *jw) |
||||
{ |
||||
int k; |
||||
|
||||
for (k = 0; k < jw->open_stack.len; k++) |
||||
strbuf_addstr(&jw->json, " "); |
||||
} |
||||
|
||||
/* |
||||
* Begin an object or array (either top-level or nested within the currently |
||||
* open object or array). |
||||
*/ |
||||
static void begin(struct json_writer *jw, char ch_open, int pretty) |
||||
{ |
||||
jw->pretty = pretty; |
||||
|
||||
strbuf_addch(&jw->json, ch_open); |
||||
|
||||
strbuf_addch(&jw->open_stack, ch_open); |
||||
jw->need_comma = 0; |
||||
} |
||||
|
||||
/* |
||||
* Assert that the top of the open-stack is an object. |
||||
*/ |
||||
static void assert_in_object(const struct json_writer *jw, const char *key) |
||||
{ |
||||
if (!jw->open_stack.len) |
||||
BUG("json-writer: object: missing jw_object_begin(): '%s'", key); |
||||
if (jw->open_stack.buf[jw->open_stack.len - 1] != '{') |
||||
BUG("json-writer: object: not in object: '%s'", key); |
||||
} |
||||
|
||||
/* |
||||
* Assert that the top of the open-stack is an array. |
||||
*/ |
||||
static void assert_in_array(const struct json_writer *jw) |
||||
{ |
||||
if (!jw->open_stack.len) |
||||
BUG("json-writer: array: missing jw_array_begin()"); |
||||
if (jw->open_stack.buf[jw->open_stack.len - 1] != '[') |
||||
BUG("json-writer: array: not in array"); |
||||
} |
||||
|
||||
/* |
||||
* Add comma if we have already seen a member at this level. |
||||
*/ |
||||
static void maybe_add_comma(struct json_writer *jw) |
||||
{ |
||||
if (jw->need_comma) |
||||
strbuf_addch(&jw->json, ','); |
||||
else |
||||
jw->need_comma = 1; |
||||
} |
||||
|
||||
static void fmt_double(struct json_writer *jw, int precision, |
||||
double value) |
||||
{ |
||||
if (precision < 0) { |
||||
strbuf_addf(&jw->json, "%f", value); |
||||
} else { |
||||
struct strbuf fmt = STRBUF_INIT; |
||||
strbuf_addf(&fmt, "%%.%df", precision); |
||||
strbuf_addf(&jw->json, fmt.buf, value); |
||||
strbuf_release(&fmt); |
||||
} |
||||
} |
||||
|
||||
static void object_common(struct json_writer *jw, const char *key) |
||||
{ |
||||
assert_in_object(jw, key); |
||||
maybe_add_comma(jw); |
||||
|
||||
if (jw->pretty) { |
||||
strbuf_addch(&jw->json, '\n'); |
||||
indent_pretty(jw); |
||||
} |
||||
|
||||
append_quoted_string(&jw->json, key); |
||||
strbuf_addch(&jw->json, ':'); |
||||
if (jw->pretty) |
||||
strbuf_addch(&jw->json, ' '); |
||||
} |
||||
|
||||
static void array_common(struct json_writer *jw) |
||||
{ |
||||
assert_in_array(jw); |
||||
maybe_add_comma(jw); |
||||
|
||||
if (jw->pretty) { |
||||
strbuf_addch(&jw->json, '\n'); |
||||
indent_pretty(jw); |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* Assert that the given JSON object or JSON array has been properly |
||||
* terminated. (Has closing bracket.) |
||||
*/ |
||||
static void assert_is_terminated(const struct json_writer *jw) |
||||
{ |
||||
if (jw->open_stack.len) |
||||
BUG("json-writer: object: missing jw_end(): '%s'", |
||||
jw->json.buf); |
||||
} |
||||
|
||||
void jw_object_begin(struct json_writer *jw, int pretty) |
||||
{ |
||||
begin(jw, '{', pretty); |
||||
} |
||||
|
||||
void jw_object_string(struct json_writer *jw, const char *key, const char *value) |
||||
{ |
||||
object_common(jw, key); |
||||
append_quoted_string(&jw->json, value); |
||||
} |
||||
|
||||
void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value) |
||||
{ |
||||
object_common(jw, key); |
||||
strbuf_addf(&jw->json, "%"PRIdMAX, value); |
||||
} |
||||
|
||||
void jw_object_double(struct json_writer *jw, const char *key, int precision, |
||||
double value) |
||||
{ |
||||
object_common(jw, key); |
||||
fmt_double(jw, precision, value); |
||||
} |
||||
|
||||
void jw_object_true(struct json_writer *jw, const char *key) |
||||
{ |
||||
object_common(jw, key); |
||||
strbuf_addstr(&jw->json, "true"); |
||||
} |
||||
|
||||
void jw_object_false(struct json_writer *jw, const char *key) |
||||
{ |
||||
object_common(jw, key); |
||||
strbuf_addstr(&jw->json, "false"); |
||||
} |
||||
|
||||
void jw_object_bool(struct json_writer *jw, const char *key, int value) |
||||
{ |
||||
if (value) |
||||
jw_object_true(jw, key); |
||||
else |
||||
jw_object_false(jw, key); |
||||
} |
||||
|
||||
void jw_object_null(struct json_writer *jw, const char *key) |
||||
{ |
||||
object_common(jw, key); |
||||
strbuf_addstr(&jw->json, "null"); |
||||
} |
||||
|
||||
static void increase_indent(struct strbuf *sb, |
||||
const struct json_writer *jw, |
||||
int indent) |
||||
{ |
||||
int k; |
||||
|
||||
strbuf_reset(sb); |
||||
for (k = 0; k < jw->json.len; k++) { |
||||
char ch = jw->json.buf[k]; |
||||
strbuf_addch(sb, ch); |
||||
if (ch == '\n') |
||||
strbuf_addchars(sb, ' ', indent); |
||||
} |
||||
} |
||||
|
||||
static void kill_indent(struct strbuf *sb, |
||||
const struct json_writer *jw) |
||||
{ |
||||
int k; |
||||
int eat_it = 0; |
||||
|
||||
strbuf_reset(sb); |
||||
for (k = 0; k < jw->json.len; k++) { |
||||
char ch = jw->json.buf[k]; |
||||
if (eat_it && ch == ' ') |
||||
continue; |
||||
if (ch == '\n') { |
||||
eat_it = 1; |
||||
continue; |
||||
} |
||||
eat_it = 0; |
||||
strbuf_addch(sb, ch); |
||||
} |
||||
} |
||||
|
||||
static void append_sub_jw(struct json_writer *jw, |
||||
const struct json_writer *value) |
||||
{ |
||||
/* |
||||
* If both are pretty, increase the indentation of the sub_jw |
||||
* to better fit under the super. |
||||
* |
||||
* If the super is pretty, but the sub_jw is compact, leave the |
||||
* sub_jw compact. (We don't want to parse and rebuild the sub_jw |
||||
* for this debug-ish feature.) |
||||
* |
||||
* If the super is compact, and the sub_jw is pretty, convert |
||||
* the sub_jw to compact. |
||||
* |
||||
* If both are compact, keep the sub_jw compact. |
||||
*/ |
||||
if (jw->pretty && jw->open_stack.len && value->pretty) { |
||||
struct strbuf sb = STRBUF_INIT; |
||||
increase_indent(&sb, value, jw->open_stack.len * 2); |
||||
strbuf_addbuf(&jw->json, &sb); |
||||
strbuf_release(&sb); |
||||
return; |
||||
} |
||||
if (!jw->pretty && value->pretty) { |
||||
struct strbuf sb = STRBUF_INIT; |
||||
kill_indent(&sb, value); |
||||
strbuf_addbuf(&jw->json, &sb); |
||||
strbuf_release(&sb); |
||||
return; |
||||
} |
||||
|
||||
strbuf_addbuf(&jw->json, &value->json); |
||||
} |
||||
|
||||
/* |
||||
* Append existing (properly terminated) JSON sub-data (object or array) |
||||
* as-is onto the given JSON data. |
||||
*/ |
||||
void jw_object_sub_jw(struct json_writer *jw, const char *key, |
||||
const struct json_writer *value) |
||||
{ |
||||
assert_is_terminated(value); |
||||
|
||||
object_common(jw, key); |
||||
append_sub_jw(jw, value); |
||||
} |
||||
|
||||
void jw_object_inline_begin_object(struct json_writer *jw, const char *key) |
||||
{ |
||||
object_common(jw, key); |
||||
|
||||
jw_object_begin(jw, jw->pretty); |
||||
} |
||||
|
||||
void jw_object_inline_begin_array(struct json_writer *jw, const char *key) |
||||
{ |
||||
object_common(jw, key); |
||||
|
||||
jw_array_begin(jw, jw->pretty); |
||||
} |
||||
|
||||
void jw_array_begin(struct json_writer *jw, int pretty) |
||||
{ |
||||
begin(jw, '[', pretty); |
||||
} |
||||
|
||||
void jw_array_string(struct json_writer *jw, const char *value) |
||||
{ |
||||
array_common(jw); |
||||
append_quoted_string(&jw->json, value); |
||||
} |
||||
|
||||
void jw_array_intmax(struct json_writer *jw, intmax_t value) |
||||
{ |
||||
array_common(jw); |
||||
strbuf_addf(&jw->json, "%"PRIdMAX, value); |
||||
} |
||||
|
||||
void jw_array_double(struct json_writer *jw, int precision, double value) |
||||
{ |
||||
array_common(jw); |
||||
fmt_double(jw, precision, value); |
||||
} |
||||
|
||||
void jw_array_true(struct json_writer *jw) |
||||
{ |
||||
array_common(jw); |
||||
strbuf_addstr(&jw->json, "true"); |
||||
} |
||||
|
||||
void jw_array_false(struct json_writer *jw) |
||||
{ |
||||
array_common(jw); |
||||
strbuf_addstr(&jw->json, "false"); |
||||
} |
||||
|
||||
void jw_array_bool(struct json_writer *jw, int value) |
||||
{ |
||||
if (value) |
||||
jw_array_true(jw); |
||||
else |
||||
jw_array_false(jw); |
||||
} |
||||
|
||||
void jw_array_null(struct json_writer *jw) |
||||
{ |
||||
array_common(jw); |
||||
strbuf_addstr(&jw->json, "null"); |
||||
} |
||||
|
||||
void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value) |
||||
{ |
||||
assert_is_terminated(value); |
||||
|
||||
array_common(jw); |
||||
append_sub_jw(jw, value); |
||||
} |
||||
|
||||
void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv) |
||||
{ |
||||
int k; |
||||
|
||||
for (k = 0; k < argc; k++) |
||||
jw_array_string(jw, argv[k]); |
||||
} |
||||
|
||||
void jw_array_argv(struct json_writer *jw, const char **argv) |
||||
{ |
||||
while (*argv) |
||||
jw_array_string(jw, *argv++); |
||||
} |
||||
|
||||
void jw_array_inline_begin_object(struct json_writer *jw) |
||||
{ |
||||
array_common(jw); |
||||
|
||||
jw_object_begin(jw, jw->pretty); |
||||
} |
||||
|
||||
void jw_array_inline_begin_array(struct json_writer *jw) |
||||
{ |
||||
array_common(jw); |
||||
|
||||
jw_array_begin(jw, jw->pretty); |
||||
} |
||||
|
||||
int jw_is_terminated(const struct json_writer *jw) |
||||
{ |
||||
return !jw->open_stack.len; |
||||
} |
||||
|
||||
void jw_end(struct json_writer *jw) |
||||
{ |
||||
char ch_open; |
||||
int len; |
||||
|
||||
if (!jw->open_stack.len) |
||||
BUG("json-writer: too many jw_end(): '%s'", jw->json.buf); |
||||
|
||||
len = jw->open_stack.len - 1; |
||||
ch_open = jw->open_stack.buf[len]; |
||||
|
||||
strbuf_setlen(&jw->open_stack, len); |
||||
jw->need_comma = 1; |
||||
|
||||
if (jw->pretty) { |
||||
strbuf_addch(&jw->json, '\n'); |
||||
indent_pretty(jw); |
||||
} |
||||
|
||||
if (ch_open == '{') |
||||
strbuf_addch(&jw->json, '}'); |
||||
else |
||||
strbuf_addch(&jw->json, ']'); |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
#ifndef JSON_WRITER_H |
||||
#define JSON_WRITER_H |
||||
|
||||
/* |
||||
* JSON data structures are defined at: |
||||
* [1] http://www.ietf.org/rfc/rfc7159.txt |
||||
* [2] http://json.org/ |
||||
* |
||||
* The JSON-writer API allows one to build JSON data structures using a |
||||
* simple wrapper around a "struct strbuf" buffer. It is intended as a |
||||
* simple API to build output strings; it is not intended to be a general |
||||
* object model for JSON data. In particular, it does not re-order keys |
||||
* in an object (dictionary), it does not de-dup keys in an object, and |
||||
* it does not allow lookup or parsing of JSON data. |
||||
* |
||||
* All string values (both keys and string r-values) are properly quoted |
||||
* and escaped if they contain special characters. |
||||
* |
||||
* These routines create compact JSON data (with no unnecessary whitespace, |
||||
* newlines, or indenting). If you get an unexpected response, verify |
||||
* that you're not expecting a pretty JSON string. |
||||
* |
||||
* Both "JSON objects" (aka sets of k/v pairs) and "JSON array" can be |
||||
* constructed using a 'begin append* end' model. |
||||
* |
||||
* Nested objects and arrays can either be constructed bottom up (by |
||||
* creating sub object/arrays first and appending them to the super |
||||
* object/array) -or- by building them inline in one pass. This is a |
||||
* personal style and/or data shape choice. |
||||
* |
||||
* See t/helper/test-json-writer.c for various usage examples. |
||||
* |
||||
* LIMITATIONS: |
||||
* ============ |
||||
* |
||||
* The JSON specification [1,2] defines string values as Unicode data |
||||
* and probably UTF-8 encoded. The current json-writer API does not |
||||
* enforce this and will write any string as received. However, it will |
||||
* properly quote and backslash-escape them as necessary. It is up to |
||||
* the caller to UTF-8 encode their strings *before* passing them to this |
||||
* API. This layer should not have to try to guess the encoding or locale |
||||
* of the given strings. |
||||
*/ |
||||
|
||||
struct json_writer |
||||
{ |
||||
/* |
||||
* Buffer of the in-progress JSON currently being composed. |
||||
*/ |
||||
struct strbuf json; |
||||
|
||||
/* |
||||
* Simple stack of the currently open array and object forms. |
||||
* This is a string of '{' and '[' characters indicating the |
||||
* currently unterminated forms. This is used to ensure the |
||||
* properly closing character is used when popping a level and |
||||
* to know when the JSON is completely closed. |
||||
*/ |
||||
struct strbuf open_stack; |
||||
|
||||
unsigned int need_comma:1; |
||||
unsigned int pretty:1; |
||||
}; |
||||
|
||||
#define JSON_WRITER_INIT { STRBUF_INIT, STRBUF_INIT, 0, 0 } |
||||
|
||||
void jw_init(struct json_writer *jw); |
||||
void jw_release(struct json_writer *jw); |
||||
|
||||
void jw_object_begin(struct json_writer *jw, int pretty); |
||||
void jw_array_begin(struct json_writer *jw, int pretty); |
||||
|
||||
void jw_object_string(struct json_writer *jw, const char *key, |
||||
const char *value); |
||||
void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value); |
||||
void jw_object_double(struct json_writer *jw, const char *key, int precision, |
||||
double value); |
||||
void jw_object_true(struct json_writer *jw, const char *key); |
||||
void jw_object_false(struct json_writer *jw, const char *key); |
||||
void jw_object_bool(struct json_writer *jw, const char *key, int value); |
||||
void jw_object_null(struct json_writer *jw, const char *key); |
||||
void jw_object_sub_jw(struct json_writer *jw, const char *key, |
||||
const struct json_writer *value); |
||||
|
||||
void jw_object_inline_begin_object(struct json_writer *jw, const char *key); |
||||
void jw_object_inline_begin_array(struct json_writer *jw, const char *key); |
||||
|
||||
void jw_array_string(struct json_writer *jw, const char *value); |
||||
void jw_array_intmax(struct json_writer *jw, intmax_t value); |
||||
void jw_array_double(struct json_writer *jw, int precision, double value); |
||||
void jw_array_true(struct json_writer *jw); |
||||
void jw_array_false(struct json_writer *jw); |
||||
void jw_array_bool(struct json_writer *jw, int value); |
||||
void jw_array_null(struct json_writer *jw); |
||||
void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value); |
||||
void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv); |
||||
void jw_array_argv(struct json_writer *jw, const char **argv); |
||||
|
||||
void jw_array_inline_begin_object(struct json_writer *jw); |
||||
void jw_array_inline_begin_array(struct json_writer *jw); |
||||
|
||||
int jw_is_terminated(const struct json_writer *jw); |
||||
void jw_end(struct json_writer *jw); |
||||
|
||||
#endif /* JSON_WRITER_H */ |
@ -0,0 +1,565 @@
@@ -0,0 +1,565 @@
|
||||
#include "test-tool.h" |
||||
#include "cache.h" |
||||
#include "json-writer.h" |
||||
|
||||
static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}"; |
||||
static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}"; |
||||
static const char *expect_obj3 = "{\"a\":0,\"b\":4294967295,\"c\":9223372036854775807}"; |
||||
static const char *expect_obj4 = "{\"t\":true,\"f\":false,\"n\":null}"; |
||||
static const char *expect_obj5 = "{\"abc\\tdef\":\"abc\\\\def\"}"; |
||||
static const char *expect_obj6 = "{\"a\":3.14}"; |
||||
|
||||
static const char *pretty_obj1 = ("{\n" |
||||
" \"a\": \"abc\",\n" |
||||
" \"b\": 42,\n" |
||||
" \"c\": true\n" |
||||
"}"); |
||||
static const char *pretty_obj2 = ("{\n" |
||||
" \"a\": -1,\n" |
||||
" \"b\": 2147483647,\n" |
||||
" \"c\": 0\n" |
||||
"}"); |
||||
static const char *pretty_obj3 = ("{\n" |
||||
" \"a\": 0,\n" |
||||
" \"b\": 4294967295,\n" |
||||
" \"c\": 9223372036854775807\n" |
||||
"}"); |
||||
static const char *pretty_obj4 = ("{\n" |
||||
" \"t\": true,\n" |
||||
" \"f\": false,\n" |
||||
" \"n\": null\n" |
||||
"}"); |
||||
|
||||
static struct json_writer obj1 = JSON_WRITER_INIT; |
||||
static struct json_writer obj2 = JSON_WRITER_INIT; |
||||
static struct json_writer obj3 = JSON_WRITER_INIT; |
||||
static struct json_writer obj4 = JSON_WRITER_INIT; |
||||
static struct json_writer obj5 = JSON_WRITER_INIT; |
||||
static struct json_writer obj6 = JSON_WRITER_INIT; |
||||
|
||||
static void make_obj1(int pretty) |
||||
{ |
||||
jw_object_begin(&obj1, pretty); |
||||
{ |
||||
jw_object_string(&obj1, "a", "abc"); |
||||
jw_object_intmax(&obj1, "b", 42); |
||||
jw_object_true(&obj1, "c"); |
||||
} |
||||
jw_end(&obj1); |
||||
} |
||||
|
||||
static void make_obj2(int pretty) |
||||
{ |
||||
jw_object_begin(&obj2, pretty); |
||||
{ |
||||
jw_object_intmax(&obj2, "a", -1); |
||||
jw_object_intmax(&obj2, "b", 0x7fffffff); |
||||
jw_object_intmax(&obj2, "c", 0); |
||||
} |
||||
jw_end(&obj2); |
||||
} |
||||
|
||||
static void make_obj3(int pretty) |
||||
{ |
||||
jw_object_begin(&obj3, pretty); |
||||
{ |
||||
jw_object_intmax(&obj3, "a", 0); |
||||
jw_object_intmax(&obj3, "b", 0xffffffff); |
||||
jw_object_intmax(&obj3, "c", 0x7fffffffffffffffULL); |
||||
} |
||||
jw_end(&obj3); |
||||
} |
||||
|
||||
static void make_obj4(int pretty) |
||||
{ |
||||
jw_object_begin(&obj4, pretty); |
||||
{ |
||||
jw_object_true(&obj4, "t"); |
||||
jw_object_false(&obj4, "f"); |
||||
jw_object_null(&obj4, "n"); |
||||
} |
||||
jw_end(&obj4); |
||||
} |
||||
|
||||
static void make_obj5(int pretty) |
||||
{ |
||||
jw_object_begin(&obj5, pretty); |
||||
{ |
||||
jw_object_string(&obj5, "abc" "\x09" "def", "abc" "\\" "def"); |
||||
} |
||||
jw_end(&obj5); |
||||
} |
||||
|
||||
static void make_obj6(int pretty) |
||||
{ |
||||
jw_object_begin(&obj6, pretty); |
||||
{ |
||||
jw_object_double(&obj6, "a", 2, 3.14159); |
||||
} |
||||
jw_end(&obj6); |
||||
} |
||||
|
||||
static const char *expect_arr1 = "[\"abc\",42,true]"; |
||||
static const char *expect_arr2 = "[-1,2147483647,0]"; |
||||
static const char *expect_arr3 = "[0,4294967295,9223372036854775807]"; |
||||
static const char *expect_arr4 = "[true,false,null]"; |
||||
|
||||
static const char *pretty_arr1 = ("[\n" |
||||
" \"abc\",\n" |
||||
" 42,\n" |
||||
" true\n" |
||||
"]"); |
||||
static const char *pretty_arr2 = ("[\n" |
||||
" -1,\n" |
||||
" 2147483647,\n" |
||||
" 0\n" |
||||
"]"); |
||||
static const char *pretty_arr3 = ("[\n" |
||||
" 0,\n" |
||||
" 4294967295,\n" |
||||
" 9223372036854775807\n" |
||||
"]"); |
||||
static const char *pretty_arr4 = ("[\n" |
||||
" true,\n" |
||||
" false,\n" |
||||
" null\n" |
||||
"]"); |
||||
|
||||
static struct json_writer arr1 = JSON_WRITER_INIT; |
||||
static struct json_writer arr2 = JSON_WRITER_INIT; |
||||
static struct json_writer arr3 = JSON_WRITER_INIT; |
||||
static struct json_writer arr4 = JSON_WRITER_INIT; |
||||
|
||||
static void make_arr1(int pretty) |
||||
{ |
||||
jw_array_begin(&arr1, pretty); |
||||
{ |
||||
jw_array_string(&arr1, "abc"); |
||||
jw_array_intmax(&arr1, 42); |
||||
jw_array_true(&arr1); |
||||
} |
||||
jw_end(&arr1); |
||||
} |
||||
|
||||
static void make_arr2(int pretty) |
||||
{ |
||||
jw_array_begin(&arr2, pretty); |
||||
{ |
||||
jw_array_intmax(&arr2, -1); |
||||
jw_array_intmax(&arr2, 0x7fffffff); |
||||
jw_array_intmax(&arr2, 0); |
||||
} |
||||
jw_end(&arr2); |
||||
} |
||||
|
||||
static void make_arr3(int pretty) |
||||
{ |
||||
jw_array_begin(&arr3, pretty); |
||||
{ |
||||
jw_array_intmax(&arr3, 0); |
||||
jw_array_intmax(&arr3, 0xffffffff); |
||||
jw_array_intmax(&arr3, 0x7fffffffffffffffULL); |
||||
} |
||||
jw_end(&arr3); |
||||
} |
||||
|
||||
static void make_arr4(int pretty) |
||||
{ |
||||
jw_array_begin(&arr4, pretty); |
||||
{ |
||||
jw_array_true(&arr4); |
||||
jw_array_false(&arr4); |
||||
jw_array_null(&arr4); |
||||
} |
||||
jw_end(&arr4); |
||||
} |
||||
|
||||
static char *expect_nest1 = |
||||
"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; |
||||
|
||||
static struct json_writer nest1 = JSON_WRITER_INIT; |
||||
|
||||
static void make_nest1(int pretty) |
||||
{ |
||||
jw_object_begin(&nest1, pretty); |
||||
{ |
||||
jw_object_sub_jw(&nest1, "obj1", &obj1); |
||||
jw_object_sub_jw(&nest1, "arr1", &arr1); |
||||
} |
||||
jw_end(&nest1); |
||||
} |
||||
|
||||
static char *expect_inline1 = |
||||
"{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; |
||||
|
||||
static char *pretty_inline1 = |
||||
("{\n" |
||||
" \"obj1\": {\n" |
||||
" \"a\": \"abc\",\n" |
||||
" \"b\": 42,\n" |
||||
" \"c\": true\n" |
||||
" },\n" |
||||
" \"arr1\": [\n" |
||||
" \"abc\",\n" |
||||
" 42,\n" |
||||
" true\n" |
||||
" ]\n" |
||||
"}"); |
||||
|
||||
static struct json_writer inline1 = JSON_WRITER_INIT; |
||||
|
||||
static void make_inline1(int pretty) |
||||
{ |
||||
jw_object_begin(&inline1, pretty); |
||||
{ |
||||
jw_object_inline_begin_object(&inline1, "obj1"); |
||||
{ |
||||
jw_object_string(&inline1, "a", "abc"); |
||||
jw_object_intmax(&inline1, "b", 42); |
||||
jw_object_true(&inline1, "c"); |
||||
} |
||||
jw_end(&inline1); |
||||
jw_object_inline_begin_array(&inline1, "arr1"); |
||||
{ |
||||
jw_array_string(&inline1, "abc"); |
||||
jw_array_intmax(&inline1, 42); |
||||
jw_array_true(&inline1); |
||||
} |
||||
jw_end(&inline1); |
||||
} |
||||
jw_end(&inline1); |
||||
} |
||||
|
||||
static char *expect_inline2 = |
||||
"[[1,2],[3,4],{\"a\":\"abc\"}]"; |
||||
|
||||
static char *pretty_inline2 = |
||||
("[\n" |
||||
" [\n" |
||||
" 1,\n" |
||||
" 2\n" |
||||
" ],\n" |
||||
" [\n" |
||||
" 3,\n" |
||||
" 4\n" |
||||
" ],\n" |
||||
" {\n" |
||||
" \"a\": \"abc\"\n" |
||||
" }\n" |
||||
"]"); |
||||
|
||||
static struct json_writer inline2 = JSON_WRITER_INIT; |
||||
|
||||
static void make_inline2(int pretty) |
||||
{ |
||||
jw_array_begin(&inline2, pretty); |
||||
{ |
||||
jw_array_inline_begin_array(&inline2); |
||||
{ |
||||
jw_array_intmax(&inline2, 1); |
||||
jw_array_intmax(&inline2, 2); |
||||
} |
||||
jw_end(&inline2); |
||||
jw_array_inline_begin_array(&inline2); |
||||
{ |
||||
jw_array_intmax(&inline2, 3); |
||||
jw_array_intmax(&inline2, 4); |
||||
} |
||||
jw_end(&inline2); |
||||
jw_array_inline_begin_object(&inline2); |
||||
{ |
||||
jw_object_string(&inline2, "a", "abc"); |
||||
} |
||||
jw_end(&inline2); |
||||
} |
||||
jw_end(&inline2); |
||||
} |
||||
|
||||
/* |
||||
* When super is compact, we expect subs to be compacted (even if originally |
||||
* pretty). |
||||
*/ |
||||
static const char *expect_mixed1 = |
||||
("{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true}," |
||||
"\"arr1\":[\"abc\",42,true]}"); |
||||
|
||||
/* |
||||
* When super is pretty, a compact sub (obj1) is kept compact and a pretty |
||||
* sub (arr1) is re-indented. |
||||
*/ |
||||
static const char *pretty_mixed1 = |
||||
("{\n" |
||||
" \"obj1\": {\"a\":\"abc\",\"b\":42,\"c\":true},\n" |
||||
" \"arr1\": [\n" |
||||
" \"abc\",\n" |
||||
" 42,\n" |
||||
" true\n" |
||||
" ]\n" |
||||
"}"); |
||||
|
||||
static struct json_writer mixed1 = JSON_WRITER_INIT; |
||||
|
||||
static void make_mixed1(int pretty) |
||||
{ |
||||
jw_init(&obj1); |
||||
jw_init(&arr1); |
||||
|
||||
make_obj1(0); /* obj1 is compact */ |
||||
make_arr1(1); /* arr1 is pretty */ |
||||
|
||||
jw_object_begin(&mixed1, pretty); |
||||
{ |
||||
jw_object_sub_jw(&mixed1, "obj1", &obj1); |
||||
jw_object_sub_jw(&mixed1, "arr1", &arr1); |
||||
} |
||||
jw_end(&mixed1); |
||||
} |
||||
|
||||
static void cmp(const char *test, const struct json_writer *jw, const char *exp) |
||||
{ |
||||
if (!strcmp(jw->json.buf, exp)) |
||||
return; |
||||
|
||||
printf("error[%s]: observed '%s' expected '%s'\n", |
||||
test, jw->json.buf, exp); |
||||
exit(1); |
||||
} |
||||
|
||||
#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0) |
||||
#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0) |
||||
|
||||
/* |
||||
* Run some basic regression tests with some known patterns. |
||||
* These tests also demonstrate how to use the jw_ API. |
||||
*/ |
||||
static int unit_tests(void) |
||||
{ |
||||
/* comptact (canonical) forms */ |
||||
t(obj1); |
||||
t(obj2); |
||||
t(obj3); |
||||
t(obj4); |
||||
t(obj5); |
||||
t(obj6); |
||||
|
||||
t(arr1); |
||||
t(arr2); |
||||
t(arr3); |
||||
t(arr4); |
||||
|
||||
t(nest1); |
||||
|
||||
t(inline1); |
||||
t(inline2); |
||||
|
||||
jw_init(&obj1); |
||||
jw_init(&obj2); |
||||
jw_init(&obj3); |
||||
jw_init(&obj4); |
||||
|
||||
jw_init(&arr1); |
||||
jw_init(&arr2); |
||||
jw_init(&arr3); |
||||
jw_init(&arr4); |
||||
|
||||
jw_init(&inline1); |
||||
jw_init(&inline2); |
||||
|
||||
/* pretty forms */ |
||||
p(obj1); |
||||
p(obj2); |
||||
p(obj3); |
||||
p(obj4); |
||||
|
||||
p(arr1); |
||||
p(arr2); |
||||
p(arr3); |
||||
p(arr4); |
||||
|
||||
p(inline1); |
||||
p(inline2); |
||||
|
||||
/* mixed forms */ |
||||
t(mixed1); |
||||
jw_init(&mixed1); |
||||
p(mixed1); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void get_s(int line_nr, char **s_in) |
||||
{ |
||||
*s_in = strtok(NULL, " "); |
||||
if (!*s_in) |
||||
die("line[%d]: expected: <s>", line_nr); |
||||
} |
||||
|
||||
static void get_i(int line_nr, intmax_t *s_in) |
||||
{ |
||||
char *s; |
||||
char *endptr; |
||||
|
||||
get_s(line_nr, &s); |
||||
|
||||
*s_in = strtol(s, &endptr, 10); |
||||
if (*endptr || errno == ERANGE) |
||||
die("line[%d]: invalid integer value", line_nr); |
||||
} |
||||
|
||||
static void get_d(int line_nr, double *s_in) |
||||
{ |
||||
char *s; |
||||
char *endptr; |
||||
|
||||
get_s(line_nr, &s); |
||||
|
||||
*s_in = strtod(s, &endptr); |
||||
if (*endptr || errno == ERANGE) |
||||
die("line[%d]: invalid float value", line_nr); |
||||
} |
||||
|
||||
static int pretty; |
||||
|
||||
#define MAX_LINE_LENGTH (64 * 1024) |
||||
|
||||
static char *get_trimmed_line(char *buf, int buf_size) |
||||
{ |
||||
int len; |
||||
|
||||
if (!fgets(buf, buf_size, stdin)) |
||||
return NULL; |
||||
|
||||
len = strlen(buf); |
||||
while (len > 0) { |
||||
char c = buf[len - 1]; |
||||
if (c == '\n' || c == '\r' || c == ' ' || c == '\t') |
||||
buf[--len] = 0; |
||||
else |
||||
break; |
||||
} |
||||
|
||||
while (*buf == ' ' || *buf == '\t') |
||||
buf++; |
||||
|
||||
return buf; |
||||
} |
||||
|
||||
static int scripted(void) |
||||
{ |
||||
struct json_writer jw = JSON_WRITER_INIT; |
||||
char buf[MAX_LINE_LENGTH]; |
||||
char *line; |
||||
int line_nr = 0; |
||||
|
||||
line = get_trimmed_line(buf, MAX_LINE_LENGTH); |
||||
if (!line) |
||||
return 0; |
||||
|
||||
if (!strcmp(line, "object")) |
||||
jw_object_begin(&jw, pretty); |
||||
else if (!strcmp(line, "array")) |
||||
jw_array_begin(&jw, pretty); |
||||
else |
||||
die("expected first line to be 'object' or 'array'"); |
||||
|
||||
while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) { |
||||
char *verb; |
||||
char *key; |
||||
char *s_value; |
||||
intmax_t i_value; |
||||
double d_value; |
||||
|
||||
line_nr++; |
||||
|
||||
verb = strtok(line, " "); |
||||
|
||||
if (!strcmp(verb, "end")) { |
||||
jw_end(&jw); |
||||
} |
||||
else if (!strcmp(verb, "object-string")) { |
||||
get_s(line_nr, &key); |
||||
get_s(line_nr, &s_value); |
||||
jw_object_string(&jw, key, s_value); |
||||
} |
||||
else if (!strcmp(verb, "object-int")) { |
||||
get_s(line_nr, &key); |
||||
get_i(line_nr, &i_value); |
||||
jw_object_intmax(&jw, key, i_value); |
||||
} |
||||
else if (!strcmp(verb, "object-double")) { |
||||
get_s(line_nr, &key); |
||||
get_i(line_nr, &i_value); |
||||
get_d(line_nr, &d_value); |
||||
jw_object_double(&jw, key, i_value, d_value); |
||||
} |
||||
else if (!strcmp(verb, "object-true")) { |
||||
get_s(line_nr, &key); |
||||
jw_object_true(&jw, key); |
||||
} |
||||
else if (!strcmp(verb, "object-false")) { |
||||
get_s(line_nr, &key); |
||||
jw_object_false(&jw, key); |
||||
} |
||||
else if (!strcmp(verb, "object-null")) { |
||||
get_s(line_nr, &key); |
||||
jw_object_null(&jw, key); |
||||
} |
||||
else if (!strcmp(verb, "object-object")) { |
||||
get_s(line_nr, &key); |
||||
jw_object_inline_begin_object(&jw, key); |
||||
} |
||||
else if (!strcmp(verb, "object-array")) { |
||||
get_s(line_nr, &key); |
||||
jw_object_inline_begin_array(&jw, key); |
||||
} |
||||
else if (!strcmp(verb, "array-string")) { |
||||
get_s(line_nr, &s_value); |
||||
jw_array_string(&jw, s_value); |
||||
} |
||||
else if (!strcmp(verb, "array-int")) { |
||||
get_i(line_nr, &i_value); |
||||
jw_array_intmax(&jw, i_value); |
||||
} |
||||
else if (!strcmp(verb, "array-double")) { |
||||
get_i(line_nr, &i_value); |
||||
get_d(line_nr, &d_value); |
||||
jw_array_double(&jw, i_value, d_value); |
||||
} |
||||
else if (!strcmp(verb, "array-true")) |
||||
jw_array_true(&jw); |
||||
else if (!strcmp(verb, "array-false")) |
||||
jw_array_false(&jw); |
||||
else if (!strcmp(verb, "array-null")) |
||||
jw_array_null(&jw); |
||||
else if (!strcmp(verb, "array-object")) |
||||
jw_array_inline_begin_object(&jw); |
||||
else if (!strcmp(verb, "array-array")) |
||||
jw_array_inline_begin_array(&jw); |
||||
else |
||||
die("unrecognized token: '%s'", verb); |
||||
} |
||||
|
||||
if (!jw_is_terminated(&jw)) |
||||
die("json not terminated: '%s'", jw.json.buf); |
||||
|
||||
printf("%s\n", jw.json.buf); |
||||
|
||||
strbuf_release(&jw.json); |
||||
return 0; |
||||
} |
||||
|
||||
int cmd__json_writer(int argc, const char **argv) |
||||
{ |
||||
argc--; /* skip over "json-writer" arg */ |
||||
argv++; |
||||
|
||||
if (argc > 0 && argv[0][0] == '-') { |
||||
if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--unit")) |
||||
return unit_tests(); |
||||
|
||||
if (!strcmp(argv[0], "-p") || !strcmp(argv[0], "--pretty")) |
||||
pretty = 1; |
||||
} |
||||
|
||||
return scripted(); |
||||
} |
@ -0,0 +1,331 @@
@@ -0,0 +1,331 @@
|
||||
#!/bin/sh |
||||
|
||||
test_description='test json-writer JSON generation' |
||||
. ./test-lib.sh |
||||
|
||||
test_expect_success 'unit test of json-writer routines' ' |
||||
test-tool json-writer -u |
||||
' |
||||
|
||||
test_expect_success 'trivial object' ' |
||||
cat >expect <<-\EOF && |
||||
{} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'trivial array' ' |
||||
cat >expect <<-\EOF && |
||||
[] |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
array |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'simple object' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","b":42,"c":3.14,"d":true,"e":false,"f":null} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-int b 42 |
||||
object-double c 2 3.140 |
||||
object-true d |
||||
object-false e |
||||
object-null f |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'simple array' ' |
||||
cat >expect <<-\EOF && |
||||
["abc",42,3.14,true,false,null] |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
array |
||||
array-string abc |
||||
array-int 42 |
||||
array-double 2 3.140 |
||||
array-true |
||||
array-false |
||||
array-null |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'escape quoting string' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc\\def"} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc\def |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'escape quoting string 2' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc\"def"} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc"def |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'nested inline object' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":{"e":false,"f":null}}} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-int b 42 |
||||
object-object sub1 |
||||
object-double c 2 3.140 |
||||
object-true d |
||||
object-object sub2 |
||||
object-false e |
||||
object-null f |
||||
end |
||||
end |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'nested inline array' ' |
||||
cat >expect <<-\EOF && |
||||
["abc",42,[3.14,true,[false,null]]] |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
array |
||||
array-string abc |
||||
array-int 42 |
||||
array-array |
||||
array-double 2 3.140 |
||||
array-true |
||||
array-array |
||||
array-false |
||||
array-null |
||||
end |
||||
end |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'nested inline object and array' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,null]}} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-int b 42 |
||||
object-object sub1 |
||||
object-double c 2 3.140 |
||||
object-true d |
||||
object-array sub2 |
||||
array-false |
||||
array-null |
||||
end |
||||
end |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'nested inline object and array 2' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,{"g":0,"h":1},null]}} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-int b 42 |
||||
object-object sub1 |
||||
object-double c 2 3.140 |
||||
object-true d |
||||
object-array sub2 |
||||
array-false |
||||
array-object |
||||
object-int g 0 |
||||
object-int h 1 |
||||
end |
||||
array-null |
||||
end |
||||
end |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'pretty nested inline object and array 2' ' |
||||
sed -e "s/^|//" >expect <<-\EOF && |
||||
|{ |
||||
| "a": "abc", |
||||
| "b": 42, |
||||
| "sub1": { |
||||
| "c": 3.14, |
||||
| "d": true, |
||||
| "sub2": [ |
||||
| false, |
||||
| { |
||||
| "g": 0, |
||||
| "h": 1 |
||||
| }, |
||||
| null |
||||
| ] |
||||
| } |
||||
|} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-int b 42 |
||||
object-object sub1 |
||||
object-double c 2 3.140 |
||||
object-true d |
||||
object-array sub2 |
||||
array-false |
||||
array-object |
||||
object-int g 0 |
||||
object-int h 1 |
||||
end |
||||
array-null |
||||
end |
||||
end |
||||
end |
||||
EOF |
||||
test-tool json-writer -p <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'inline object with no members' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","empty":{},"b":42} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-object empty |
||||
end |
||||
object-int b 42 |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'inline array with no members' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","empty":[],"b":42} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-array empty |
||||
end |
||||
object-int b 42 |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_expect_success 'larger empty example' ' |
||||
cat >expect <<-\EOF && |
||||
{"a":"abc","empty":[{},{},{},[],{}],"b":42} |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-array empty |
||||
array-object |
||||
end |
||||
array-object |
||||
end |
||||
array-object |
||||
end |
||||
array-array |
||||
end |
||||
array-object |
||||
end |
||||
end |
||||
object-int b 42 |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_lazy_prereq PERLJSON ' |
||||
perl -MJSON -e "exit 0" |
||||
' |
||||
|
||||
# As a sanity check, ask Perl to parse our generated JSON and recursively |
||||
# dump the resulting data in sorted order. Confirm that that matches our |
||||
# expectations. |
||||
test_expect_success PERLJSON 'parse JSON using Perl' ' |
||||
cat >expect <<-\EOF && |
||||
row[0].a abc |
||||
row[0].b 42 |
||||
row[0].sub1 hash |
||||
row[0].sub1.c 3.14 |
||||
row[0].sub1.d 1 |
||||
row[0].sub1.sub2 array |
||||
row[0].sub1.sub2[0] 0 |
||||
row[0].sub1.sub2[1] hash |
||||
row[0].sub1.sub2[1].g 0 |
||||
row[0].sub1.sub2[1].h 1 |
||||
row[0].sub1.sub2[2] null |
||||
EOF |
||||
cat >input <<-\EOF && |
||||
object |
||||
object-string a abc |
||||
object-int b 42 |
||||
object-object sub1 |
||||
object-double c 2 3.140 |
||||
object-true d |
||||
object-array sub2 |
||||
array-false |
||||
array-object |
||||
object-int g 0 |
||||
object-int h 1 |
||||
end |
||||
array-null |
||||
end |
||||
end |
||||
end |
||||
EOF |
||||
test-tool json-writer <input >output.json && |
||||
perl "$TEST_DIRECTORY"/t0019/parse_json.perl <output.json >actual && |
||||
test_cmp expect actual |
||||
' |
||||
|
||||
test_done |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/perl |
||||
use strict; |
||||
use warnings; |
||||
use JSON; |
||||
|
||||
sub dump_array { |
||||
my ($label_in, $ary_ref) = @_; |
||||
my @ary = @$ary_ref; |
||||
|
||||
for ( my $i = 0; $i <= $#{ $ary_ref }; $i++ ) |
||||
{ |
||||
my $label = "$label_in\[$i\]"; |
||||
dump_item($label, $ary[$i]); |
||||
} |
||||
} |
||||
|
||||
sub dump_hash { |
||||
my ($label_in, $obj_ref) = @_; |
||||
my %obj = %$obj_ref; |
||||
|
||||
foreach my $k (sort keys %obj) { |
||||
my $label = (length($label_in) > 0) ? "$label_in.$k" : "$k"; |
||||
my $value = $obj{$k}; |
||||
|
||||
dump_item($label, $value); |
||||
} |
||||
} |
||||
|
||||
sub dump_item { |
||||
my ($label_in, $value) = @_; |
||||
if (ref($value) eq 'ARRAY') { |
||||
print "$label_in array\n"; |
||||
dump_array($label_in, $value); |
||||
} elsif (ref($value) eq 'HASH') { |
||||
print "$label_in hash\n"; |
||||
dump_hash($label_in, $value); |
||||
} elsif (defined $value) { |
||||
print "$label_in $value\n"; |
||||
} else { |
||||
print "$label_in null\n"; |
||||
} |
||||
} |
||||
|
||||
my $row = 0; |
||||
while (<>) { |
||||
my $data = decode_json( $_ ); |
||||
my $label = "row[$row]"; |
||||
|
||||
dump_hash($label, $data); |
||||
$row++; |
||||
} |
||||
|
Loading…
Reference in new issue