json_writer: new routines to create JSON data

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>

Jeff Hostetler committed Jul 13, 2018 at 16:54 UTC 75459410edd8d3bf38a2f1ad785f54b97770b324
8 files changed +1471
Makefile
+2
@@ -709,6 +709,7 @@ TEST_BUILTINS_OBJS += test-example-decorate.o
709 TEST_BUILTINS_OBJS += test-genrandom.o
710 TEST_BUILTINS_OBJS += test-hashmap.o
711 TEST_BUILTINS_OBJS += test-index-version.o
712 +TEST_BUILTINS_OBJS += test-json-writer.o
713 TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
714 TEST_BUILTINS_OBJS += test-match-trees.o
715 TEST_BUILTINS_OBJS += test-mergesort.o
@@ -871,6 +872,7 @@ LIB_OBJS += hashmap.o
872 LIB_OBJS += help.o
873 LIB_OBJS += hex.o
874 LIB_OBJS += ident.o
875 +LIB_OBJS += json-writer.o
876 LIB_OBJS += kwset.o
877 LIB_OBJS += levenshtein.o
878 LIB_OBJS += line-log.o
json-writer.c new
+414
@@ -0,0 +1,414 @@
1 +#include "cache.h"
2 +#include "json-writer.h"
3 +
4 +void jw_init(struct json_writer *jw)
5 +{
6 + strbuf_init(&jw->json, 0);
7 + strbuf_init(&jw->open_stack, 0);
8 + jw->need_comma = 0;
9 + jw->pretty = 0;
10 +}
11 +
12 +void jw_release(struct json_writer *jw)
13 +{
14 + strbuf_release(&jw->json);
15 + strbuf_release(&jw->open_stack);
16 +}
17 +
18 +/*
19 + * Append JSON-quoted version of the given string to 'out'.
20 + */
21 +static void append_quoted_string(struct strbuf *out, const char *in)
22 +{
23 + unsigned char c;
24 +
25 + strbuf_addch(out, '"');
26 + while ((c = *in++) != '\0') {
27 + if (c == '"')
28 + strbuf_addstr(out, "\\\"");
29 + else if (c == '\\')
30 + strbuf_addstr(out, "\\\\");
31 + else if (c == '\n')
32 + strbuf_addstr(out, "\\n");
33 + else if (c == '\r')
34 + strbuf_addstr(out, "\\r");
35 + else if (c == '\t')
36 + strbuf_addstr(out, "\\t");
37 + else if (c == '\f')
38 + strbuf_addstr(out, "\\f");
39 + else if (c == '\b')
40 + strbuf_addstr(out, "\\b");
41 + else if (c < 0x20)
42 + strbuf_addf(out, "\\u%04x", c);
43 + else
44 + strbuf_addch(out, c);
45 + }
46 + strbuf_addch(out, '"');
47 +}
48 +
49 +static void indent_pretty(struct json_writer *jw)
50 +{
51 + int k;
52 +
53 + for (k = 0; k < jw->open_stack.len; k++)
54 + strbuf_addstr(&jw->json, " ");
55 +}
56 +
57 +/*
58 + * Begin an object or array (either top-level or nested within the currently
59 + * open object or array).
60 + */
61 +static void begin(struct json_writer *jw, char ch_open, int pretty)
62 +{
63 + jw->pretty = pretty;
64 +
65 + strbuf_addch(&jw->json, ch_open);
66 +
67 + strbuf_addch(&jw->open_stack, ch_open);
68 + jw->need_comma = 0;
69 +}
70 +
71 +/*
72 + * Assert that the top of the open-stack is an object.
73 + */
74 +static void assert_in_object(const struct json_writer *jw, const char *key)
75 +{
76 + if (!jw->open_stack.len)
77 + BUG("json-writer: object: missing jw_object_begin(): '%s'", key);
78 + if (jw->open_stack.buf[jw->open_stack.len - 1] != '{')
79 + BUG("json-writer: object: not in object: '%s'", key);
80 +}
81 +
82 +/*
83 + * Assert that the top of the open-stack is an array.
84 + */
85 +static void assert_in_array(const struct json_writer *jw)
86 +{
87 + if (!jw->open_stack.len)
88 + BUG("json-writer: array: missing jw_array_begin()");
89 + if (jw->open_stack.buf[jw->open_stack.len - 1] != '[')
90 + BUG("json-writer: array: not in array");
91 +}
92 +
93 +/*
94 + * Add comma if we have already seen a member at this level.
95 + */
96 +static void maybe_add_comma(struct json_writer *jw)
97 +{
98 + if (jw->need_comma)
99 + strbuf_addch(&jw->json, ',');
100 + else
101 + jw->need_comma = 1;
102 +}
103 +
104 +static void fmt_double(struct json_writer *jw, int precision,
105 + double value)
106 +{
107 + if (precision < 0) {
108 + strbuf_addf(&jw->json, "%f", value);
109 + } else {
110 + struct strbuf fmt = STRBUF_INIT;
111 + strbuf_addf(&fmt, "%%.%df", precision);
112 + strbuf_addf(&jw->json, fmt.buf, value);
113 + strbuf_release(&fmt);
114 + }
115 +}
116 +
117 +static void object_common(struct json_writer *jw, const char *key)
118 +{
119 + assert_in_object(jw, key);
120 + maybe_add_comma(jw);
121 +
122 + if (jw->pretty) {
123 + strbuf_addch(&jw->json, '\n');
124 + indent_pretty(jw);
125 + }
126 +
127 + append_quoted_string(&jw->json, key);
128 + strbuf_addch(&jw->json, ':');
129 + if (jw->pretty)
130 + strbuf_addch(&jw->json, ' ');
131 +}
132 +
133 +static void array_common(struct json_writer *jw)
134 +{
135 + assert_in_array(jw);
136 + maybe_add_comma(jw);
137 +
138 + if (jw->pretty) {
139 + strbuf_addch(&jw->json, '\n');
140 + indent_pretty(jw);
141 + }
142 +}
143 +
144 +/*
145 + * Assert that the given JSON object or JSON array has been properly
146 + * terminated. (Has closing bracket.)
147 + */
148 +static void assert_is_terminated(const struct json_writer *jw)
149 +{
150 + if (jw->open_stack.len)
151 + BUG("json-writer: object: missing jw_end(): '%s'",
152 + jw->json.buf);
153 +}
154 +
155 +void jw_object_begin(struct json_writer *jw, int pretty)
156 +{
157 + begin(jw, '{', pretty);
158 +}
159 +
160 +void jw_object_string(struct json_writer *jw, const char *key, const char *value)
161 +{
162 + object_common(jw, key);
163 + append_quoted_string(&jw->json, value);
164 +}
165 +
166 +void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value)
167 +{
168 + object_common(jw, key);
169 + strbuf_addf(&jw->json, "%"PRIdMAX, value);
170 +}
171 +
172 +void jw_object_double(struct json_writer *jw, const char *key, int precision,
173 + double value)
174 +{
175 + object_common(jw, key);
176 + fmt_double(jw, precision, value);
177 +}
178 +
179 +void jw_object_true(struct json_writer *jw, const char *key)
180 +{
181 + object_common(jw, key);
182 + strbuf_addstr(&jw->json, "true");
183 +}
184 +
185 +void jw_object_false(struct json_writer *jw, const char *key)
186 +{
187 + object_common(jw, key);
188 + strbuf_addstr(&jw->json, "false");
189 +}
190 +
191 +void jw_object_bool(struct json_writer *jw, const char *key, int value)
192 +{
193 + if (value)
194 + jw_object_true(jw, key);
195 + else
196 + jw_object_false(jw, key);
197 +}
198 +
199 +void jw_object_null(struct json_writer *jw, const char *key)
200 +{
201 + object_common(jw, key);
202 + strbuf_addstr(&jw->json, "null");
203 +}
204 +
205 +static void increase_indent(struct strbuf *sb,
206 + const struct json_writer *jw,
207 + int indent)
208 +{
209 + int k;
210 +
211 + strbuf_reset(sb);
212 + for (k = 0; k < jw->json.len; k++) {
213 + char ch = jw->json.buf[k];
214 + strbuf_addch(sb, ch);
215 + if (ch == '\n')
216 + strbuf_addchars(sb, ' ', indent);
217 + }
218 +}
219 +
220 +static void kill_indent(struct strbuf *sb,
221 + const struct json_writer *jw)
222 +{
223 + int k;
224 + int eat_it = 0;
225 +
226 + strbuf_reset(sb);
227 + for (k = 0; k < jw->json.len; k++) {
228 + char ch = jw->json.buf[k];
229 + if (eat_it && ch == ' ')
230 + continue;
231 + if (ch == '\n') {
232 + eat_it = 1;
233 + continue;
234 + }
235 + eat_it = 0;
236 + strbuf_addch(sb, ch);
237 + }
238 +}
239 +
240 +static void append_sub_jw(struct json_writer *jw,
241 + const struct json_writer *value)
242 +{
243 + /*
244 + * If both are pretty, increase the indentation of the sub_jw
245 + * to better fit under the super.
246 + *
247 + * If the super is pretty, but the sub_jw is compact, leave the
248 + * sub_jw compact. (We don't want to parse and rebuild the sub_jw
249 + * for this debug-ish feature.)
250 + *
251 + * If the super is compact, and the sub_jw is pretty, convert
252 + * the sub_jw to compact.
253 + *
254 + * If both are compact, keep the sub_jw compact.
255 + */
256 + if (jw->pretty && jw->open_stack.len && value->pretty) {
257 + struct strbuf sb = STRBUF_INIT;
258 + increase_indent(&sb, value, jw->open_stack.len * 2);
259 + strbuf_addbuf(&jw->json, &sb);
260 + strbuf_release(&sb);
261 + return;
262 + }
263 + if (!jw->pretty && value->pretty) {
264 + struct strbuf sb = STRBUF_INIT;
265 + kill_indent(&sb, value);
266 + strbuf_addbuf(&jw->json, &sb);
267 + strbuf_release(&sb);
268 + return;
269 + }
270 +
271 + strbuf_addbuf(&jw->json, &value->json);
272 +}
273 +
274 +/*
275 + * Append existing (properly terminated) JSON sub-data (object or array)
276 + * as-is onto the given JSON data.
277 + */
278 +void jw_object_sub_jw(struct json_writer *jw, const char *key,
279 + const struct json_writer *value)
280 +{
281 + assert_is_terminated(value);
282 +
283 + object_common(jw, key);
284 + append_sub_jw(jw, value);
285 +}
286 +
287 +void jw_object_inline_begin_object(struct json_writer *jw, const char *key)
288 +{
289 + object_common(jw, key);
290 +
291 + jw_object_begin(jw, jw->pretty);
292 +}
293 +
294 +void jw_object_inline_begin_array(struct json_writer *jw, const char *key)
295 +{
296 + object_common(jw, key);
297 +
298 + jw_array_begin(jw, jw->pretty);
299 +}
300 +
301 +void jw_array_begin(struct json_writer *jw, int pretty)
302 +{
303 + begin(jw, '[', pretty);
304 +}
305 +
306 +void jw_array_string(struct json_writer *jw, const char *value)
307 +{
308 + array_common(jw);
309 + append_quoted_string(&jw->json, value);
310 +}
311 +
312 +void jw_array_intmax(struct json_writer *jw, intmax_t value)
313 +{
314 + array_common(jw);
315 + strbuf_addf(&jw->json, "%"PRIdMAX, value);
316 +}
317 +
318 +void jw_array_double(struct json_writer *jw, int precision, double value)
319 +{
320 + array_common(jw);
321 + fmt_double(jw, precision, value);
322 +}
323 +
324 +void jw_array_true(struct json_writer *jw)
325 +{
326 + array_common(jw);
327 + strbuf_addstr(&jw->json, "true");
328 +}
329 +
330 +void jw_array_false(struct json_writer *jw)
331 +{
332 + array_common(jw);
333 + strbuf_addstr(&jw->json, "false");
334 +}
335 +
336 +void jw_array_bool(struct json_writer *jw, int value)
337 +{
338 + if (value)
339 + jw_array_true(jw);
340 + else
341 + jw_array_false(jw);
342 +}
343 +
344 +void jw_array_null(struct json_writer *jw)
345 +{
346 + array_common(jw);
347 + strbuf_addstr(&jw->json, "null");
348 +}
349 +
350 +void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value)
351 +{
352 + assert_is_terminated(value);
353 +
354 + array_common(jw);
355 + append_sub_jw(jw, value);
356 +}
357 +
358 +void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv)
359 +{
360 + int k;
361 +
362 + for (k = 0; k < argc; k++)
363 + jw_array_string(jw, argv[k]);
364 +}
365 +
366 +void jw_array_argv(struct json_writer *jw, const char **argv)
367 +{
368 + while (*argv)
369 + jw_array_string(jw, *argv++);
370 +}
371 +
372 +void jw_array_inline_begin_object(struct json_writer *jw)
373 +{
374 + array_common(jw);
375 +
376 + jw_object_begin(jw, jw->pretty);
377 +}
378 +
379 +void jw_array_inline_begin_array(struct json_writer *jw)
380 +{
381 + array_common(jw);
382 +
383 + jw_array_begin(jw, jw->pretty);
384 +}
385 +
386 +int jw_is_terminated(const struct json_writer *jw)
387 +{
388 + return !jw->open_stack.len;
389 +}
390 +
391 +void jw_end(struct json_writer *jw)
392 +{
393 + char ch_open;
394 + int len;
395 +
396 + if (!jw->open_stack.len)
397 + BUG("json-writer: too many jw_end(): '%s'", jw->json.buf);
398 +
399 + len = jw->open_stack.len - 1;
400 + ch_open = jw->open_stack.buf[len];
401 +
402 + strbuf_setlen(&jw->open_stack, len);
403 + jw->need_comma = 1;
404 +
405 + if (jw->pretty) {
406 + strbuf_addch(&jw->json, '\n');
407 + indent_pretty(jw);
408 + }
409 +
410 + if (ch_open == '{')
411 + strbuf_addch(&jw->json, '}');
412 + else
413 + strbuf_addch(&jw->json, ']');
414 +}
json-writer.h new
+105
@@ -0,0 +1,105 @@
1 +#ifndef JSON_WRITER_H
2 +#define JSON_WRITER_H
3 +
4 +/*
5 + * JSON data structures are defined at:
6 + * [1] http://www.ietf.org/rfc/rfc7159.txt
7 + * [2] http://json.org/
8 + *
9 + * The JSON-writer API allows one to build JSON data structures using a
10 + * simple wrapper around a "struct strbuf" buffer. It is intended as a
11 + * simple API to build output strings; it is not intended to be a general
12 + * object model for JSON data. In particular, it does not re-order keys
13 + * in an object (dictionary), it does not de-dup keys in an object, and
14 + * it does not allow lookup or parsing of JSON data.
15 + *
16 + * All string values (both keys and string r-values) are properly quoted
17 + * and escaped if they contain special characters.
18 + *
19 + * These routines create compact JSON data (with no unnecessary whitespace,
20 + * newlines, or indenting). If you get an unexpected response, verify
21 + * that you're not expecting a pretty JSON string.
22 + *
23 + * Both "JSON objects" (aka sets of k/v pairs) and "JSON array" can be
24 + * constructed using a 'begin append* end' model.
25 + *
26 + * Nested objects and arrays can either be constructed bottom up (by
27 + * creating sub object/arrays first and appending them to the super
28 + * object/array) -or- by building them inline in one pass. This is a
29 + * personal style and/or data shape choice.
30 + *
31 + * See t/helper/test-json-writer.c for various usage examples.
32 + *
33 + * LIMITATIONS:
34 + * ============
35 + *
36 + * The JSON specification [1,2] defines string values as Unicode data
37 + * and probably UTF-8 encoded. The current json-writer API does not
38 + * enforce this and will write any string as received. However, it will
39 + * properly quote and backslash-escape them as necessary. It is up to
40 + * the caller to UTF-8 encode their strings *before* passing them to this
41 + * API. This layer should not have to try to guess the encoding or locale
42 + * of the given strings.
43 + */
44 +
45 +struct json_writer
46 +{
47 + /*
48 + * Buffer of the in-progress JSON currently being composed.
49 + */
50 + struct strbuf json;
51 +
52 + /*
53 + * Simple stack of the currently open array and object forms.
54 + * This is a string of '{' and '[' characters indicating the
55 + * currently unterminated forms. This is used to ensure the
56 + * properly closing character is used when popping a level and
57 + * to know when the JSON is completely closed.
58 + */
59 + struct strbuf open_stack;
60 +
61 + unsigned int need_comma:1;
62 + unsigned int pretty:1;
63 +};
64 +
65 +#define JSON_WRITER_INIT { STRBUF_INIT, STRBUF_INIT, 0, 0 }
66 +
67 +void jw_init(struct json_writer *jw);
68 +void jw_release(struct json_writer *jw);
69 +
70 +void jw_object_begin(struct json_writer *jw, int pretty);
71 +void jw_array_begin(struct json_writer *jw, int pretty);
72 +
73 +void jw_object_string(struct json_writer *jw, const char *key,
74 + const char *value);
75 +void jw_object_intmax(struct json_writer *jw, const char *key, intmax_t value);
76 +void jw_object_double(struct json_writer *jw, const char *key, int precision,
77 + double value);
78 +void jw_object_true(struct json_writer *jw, const char *key);
79 +void jw_object_false(struct json_writer *jw, const char *key);
80 +void jw_object_bool(struct json_writer *jw, const char *key, int value);
81 +void jw_object_null(struct json_writer *jw, const char *key);
82 +void jw_object_sub_jw(struct json_writer *jw, const char *key,
83 + const struct json_writer *value);
84 +
85 +void jw_object_inline_begin_object(struct json_writer *jw, const char *key);
86 +void jw_object_inline_begin_array(struct json_writer *jw, const char *key);
87 +
88 +void jw_array_string(struct json_writer *jw, const char *value);
89 +void jw_array_intmax(struct json_writer *jw, intmax_t value);
90 +void jw_array_double(struct json_writer *jw, int precision, double value);
91 +void jw_array_true(struct json_writer *jw);
92 +void jw_array_false(struct json_writer *jw);
93 +void jw_array_bool(struct json_writer *jw, int value);
94 +void jw_array_null(struct json_writer *jw);
95 +void jw_array_sub_jw(struct json_writer *jw, const struct json_writer *value);
96 +void jw_array_argc_argv(struct json_writer *jw, int argc, const char **argv);
97 +void jw_array_argv(struct json_writer *jw, const char **argv);
98 +
99 +void jw_array_inline_begin_object(struct json_writer *jw);
100 +void jw_array_inline_begin_array(struct json_writer *jw);
101 +
102 +int jw_is_terminated(const struct json_writer *jw);
103 +void jw_end(struct json_writer *jw);
104 +
105 +#endif /* JSON_WRITER_H */
t/helper/test-json-writer.c new
+565
@@ -0,0 +1,565 @@
1 +#include "test-tool.h"
2 +#include "cache.h"
3 +#include "json-writer.h"
4 +
5 +static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}";
6 +static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}";
7 +static const char *expect_obj3 = "{\"a\":0,\"b\":4294967295,\"c\":9223372036854775807}";
8 +static const char *expect_obj4 = "{\"t\":true,\"f\":false,\"n\":null}";
9 +static const char *expect_obj5 = "{\"abc\\tdef\":\"abc\\\\def\"}";
10 +static const char *expect_obj6 = "{\"a\":3.14}";
11 +
12 +static const char *pretty_obj1 = ("{\n"
13 + " \"a\": \"abc\",\n"
14 + " \"b\": 42,\n"
15 + " \"c\": true\n"
16 + "}");
17 +static const char *pretty_obj2 = ("{\n"
18 + " \"a\": -1,\n"
19 + " \"b\": 2147483647,\n"
20 + " \"c\": 0\n"
21 + "}");
22 +static const char *pretty_obj3 = ("{\n"
23 + " \"a\": 0,\n"
24 + " \"b\": 4294967295,\n"
25 + " \"c\": 9223372036854775807\n"
26 + "}");
27 +static const char *pretty_obj4 = ("{\n"
28 + " \"t\": true,\n"
29 + " \"f\": false,\n"
30 + " \"n\": null\n"
31 + "}");
32 +
33 +static struct json_writer obj1 = JSON_WRITER_INIT;
34 +static struct json_writer obj2 = JSON_WRITER_INIT;
35 +static struct json_writer obj3 = JSON_WRITER_INIT;
36 +static struct json_writer obj4 = JSON_WRITER_INIT;
37 +static struct json_writer obj5 = JSON_WRITER_INIT;
38 +static struct json_writer obj6 = JSON_WRITER_INIT;
39 +
40 +static void make_obj1(int pretty)
41 +{
42 + jw_object_begin(&obj1, pretty);
43 + {
44 + jw_object_string(&obj1, "a", "abc");
45 + jw_object_intmax(&obj1, "b", 42);
46 + jw_object_true(&obj1, "c");
47 + }
48 + jw_end(&obj1);
49 +}
50 +
51 +static void make_obj2(int pretty)
52 +{
53 + jw_object_begin(&obj2, pretty);
54 + {
55 + jw_object_intmax(&obj2, "a", -1);
56 + jw_object_intmax(&obj2, "b", 0x7fffffff);
57 + jw_object_intmax(&obj2, "c", 0);
58 + }
59 + jw_end(&obj2);
60 +}
61 +
62 +static void make_obj3(int pretty)
63 +{
64 + jw_object_begin(&obj3, pretty);
65 + {
66 + jw_object_intmax(&obj3, "a", 0);
67 + jw_object_intmax(&obj3, "b", 0xffffffff);
68 + jw_object_intmax(&obj3, "c", 0x7fffffffffffffffULL);
69 + }
70 + jw_end(&obj3);
71 +}
72 +
73 +static void make_obj4(int pretty)
74 +{
75 + jw_object_begin(&obj4, pretty);
76 + {
77 + jw_object_true(&obj4, "t");
78 + jw_object_false(&obj4, "f");
79 + jw_object_null(&obj4, "n");
80 + }
81 + jw_end(&obj4);
82 +}
83 +
84 +static void make_obj5(int pretty)
85 +{
86 + jw_object_begin(&obj5, pretty);
87 + {
88 + jw_object_string(&obj5, "abc" "\x09" "def", "abc" "\\" "def");
89 + }
90 + jw_end(&obj5);
91 +}
92 +
93 +static void make_obj6(int pretty)
94 +{
95 + jw_object_begin(&obj6, pretty);
96 + {
97 + jw_object_double(&obj6, "a", 2, 3.14159);
98 + }
99 + jw_end(&obj6);
100 +}
101 +
102 +static const char *expect_arr1 = "[\"abc\",42,true]";
103 +static const char *expect_arr2 = "[-1,2147483647,0]";
104 +static const char *expect_arr3 = "[0,4294967295,9223372036854775807]";
105 +static const char *expect_arr4 = "[true,false,null]";
106 +
107 +static const char *pretty_arr1 = ("[\n"
108 + " \"abc\",\n"
109 + " 42,\n"
110 + " true\n"
111 + "]");
112 +static const char *pretty_arr2 = ("[\n"
113 + " -1,\n"
114 + " 2147483647,\n"
115 + " 0\n"
116 + "]");
117 +static const char *pretty_arr3 = ("[\n"
118 + " 0,\n"
119 + " 4294967295,\n"
120 + " 9223372036854775807\n"
121 + "]");
122 +static const char *pretty_arr4 = ("[\n"
123 + " true,\n"
124 + " false,\n"
125 + " null\n"
126 + "]");
127 +
128 +static struct json_writer arr1 = JSON_WRITER_INIT;
129 +static struct json_writer arr2 = JSON_WRITER_INIT;
130 +static struct json_writer arr3 = JSON_WRITER_INIT;
131 +static struct json_writer arr4 = JSON_WRITER_INIT;
132 +
133 +static void make_arr1(int pretty)
134 +{
135 + jw_array_begin(&arr1, pretty);
136 + {
137 + jw_array_string(&arr1, "abc");
138 + jw_array_intmax(&arr1, 42);
139 + jw_array_true(&arr1);
140 + }
141 + jw_end(&arr1);
142 +}
143 +
144 +static void make_arr2(int pretty)
145 +{
146 + jw_array_begin(&arr2, pretty);
147 + {
148 + jw_array_intmax(&arr2, -1);
149 + jw_array_intmax(&arr2, 0x7fffffff);
150 + jw_array_intmax(&arr2, 0);
151 + }
152 + jw_end(&arr2);
153 +}
154 +
155 +static void make_arr3(int pretty)
156 +{
157 + jw_array_begin(&arr3, pretty);
158 + {
159 + jw_array_intmax(&arr3, 0);
160 + jw_array_intmax(&arr3, 0xffffffff);
161 + jw_array_intmax(&arr3, 0x7fffffffffffffffULL);
162 + }
163 + jw_end(&arr3);
164 +}
165 +
166 +static void make_arr4(int pretty)
167 +{
168 + jw_array_begin(&arr4, pretty);
169 + {
170 + jw_array_true(&arr4);
171 + jw_array_false(&arr4);
172 + jw_array_null(&arr4);
173 + }
174 + jw_end(&arr4);
175 +}
176 +
177 +static char *expect_nest1 =
178 + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
179 +
180 +static struct json_writer nest1 = JSON_WRITER_INIT;
181 +
182 +static void make_nest1(int pretty)
183 +{
184 + jw_object_begin(&nest1, pretty);
185 + {
186 + jw_object_sub_jw(&nest1, "obj1", &obj1);
187 + jw_object_sub_jw(&nest1, "arr1", &arr1);
188 + }
189 + jw_end(&nest1);
190 +}
191 +
192 +static char *expect_inline1 =
193 + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}";
194 +
195 +static char *pretty_inline1 =
196 + ("{\n"
197 + " \"obj1\": {\n"
198 + " \"a\": \"abc\",\n"
199 + " \"b\": 42,\n"
200 + " \"c\": true\n"
201 + " },\n"
202 + " \"arr1\": [\n"
203 + " \"abc\",\n"
204 + " 42,\n"
205 + " true\n"
206 + " ]\n"
207 + "}");
208 +
209 +static struct json_writer inline1 = JSON_WRITER_INIT;
210 +
211 +static void make_inline1(int pretty)
212 +{
213 + jw_object_begin(&inline1, pretty);
214 + {
215 + jw_object_inline_begin_object(&inline1, "obj1");
216 + {
217 + jw_object_string(&inline1, "a", "abc");
218 + jw_object_intmax(&inline1, "b", 42);
219 + jw_object_true(&inline1, "c");
220 + }
221 + jw_end(&inline1);
222 + jw_object_inline_begin_array(&inline1, "arr1");
223 + {
224 + jw_array_string(&inline1, "abc");
225 + jw_array_intmax(&inline1, 42);
226 + jw_array_true(&inline1);
227 + }
228 + jw_end(&inline1);
229 + }
230 + jw_end(&inline1);
231 +}
232 +
233 +static char *expect_inline2 =
234 + "[[1,2],[3,4],{\"a\":\"abc\"}]";
235 +
236 +static char *pretty_inline2 =
237 + ("[\n"
238 + " [\n"
239 + " 1,\n"
240 + " 2\n"
241 + " ],\n"
242 + " [\n"
243 + " 3,\n"
244 + " 4\n"
245 + " ],\n"
246 + " {\n"
247 + " \"a\": \"abc\"\n"
248 + " }\n"
249 + "]");
250 +
251 +static struct json_writer inline2 = JSON_WRITER_INIT;
252 +
253 +static void make_inline2(int pretty)
254 +{
255 + jw_array_begin(&inline2, pretty);
256 + {
257 + jw_array_inline_begin_array(&inline2);
258 + {
259 + jw_array_intmax(&inline2, 1);
260 + jw_array_intmax(&inline2, 2);
261 + }
262 + jw_end(&inline2);
263 + jw_array_inline_begin_array(&inline2);
264 + {
265 + jw_array_intmax(&inline2, 3);
266 + jw_array_intmax(&inline2, 4);
267 + }
268 + jw_end(&inline2);
269 + jw_array_inline_begin_object(&inline2);
270 + {
271 + jw_object_string(&inline2, "a", "abc");
272 + }
273 + jw_end(&inline2);
274 + }
275 + jw_end(&inline2);
276 +}
277 +
278 +/*
279 + * When super is compact, we expect subs to be compacted (even if originally
280 + * pretty).
281 + */
282 +static const char *expect_mixed1 =
283 + ("{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},"
284 + "\"arr1\":[\"abc\",42,true]}");
285 +
286 +/*
287 + * When super is pretty, a compact sub (obj1) is kept compact and a pretty
288 + * sub (arr1) is re-indented.
289 + */
290 +static const char *pretty_mixed1 =
291 + ("{\n"
292 + " \"obj1\": {\"a\":\"abc\",\"b\":42,\"c\":true},\n"
293 + " \"arr1\": [\n"
294 + " \"abc\",\n"
295 + " 42,\n"
296 + " true\n"
297 + " ]\n"
298 + "}");
299 +
300 +static struct json_writer mixed1 = JSON_WRITER_INIT;
301 +
302 +static void make_mixed1(int pretty)
303 +{
304 + jw_init(&obj1);
305 + jw_init(&arr1);
306 +
307 + make_obj1(0); /* obj1 is compact */
308 + make_arr1(1); /* arr1 is pretty */
309 +
310 + jw_object_begin(&mixed1, pretty);
311 + {
312 + jw_object_sub_jw(&mixed1, "obj1", &obj1);
313 + jw_object_sub_jw(&mixed1, "arr1", &arr1);
314 + }
315 + jw_end(&mixed1);
316 +}
317 +
318 +static void cmp(const char *test, const struct json_writer *jw, const char *exp)
319 +{
320 + if (!strcmp(jw->json.buf, exp))
321 + return;
322 +
323 + printf("error[%s]: observed '%s' expected '%s'\n",
324 + test, jw->json.buf, exp);
325 + exit(1);
326 +}
327 +
328 +#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0)
329 +#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0)
330 +
331 +/*
332 + * Run some basic regression tests with some known patterns.
333 + * These tests also demonstrate how to use the jw_ API.
334 + */
335 +static int unit_tests(void)
336 +{
337 + /* comptact (canonical) forms */
338 + t(obj1);
339 + t(obj2);
340 + t(obj3);
341 + t(obj4);
342 + t(obj5);
343 + t(obj6);
344 +
345 + t(arr1);
346 + t(arr2);
347 + t(arr3);
348 + t(arr4);
349 +
350 + t(nest1);
351 +
352 + t(inline1);
353 + t(inline2);
354 +
355 + jw_init(&obj1);
356 + jw_init(&obj2);
357 + jw_init(&obj3);
358 + jw_init(&obj4);
359 +
360 + jw_init(&arr1);
361 + jw_init(&arr2);
362 + jw_init(&arr3);
363 + jw_init(&arr4);
364 +
365 + jw_init(&inline1);
366 + jw_init(&inline2);
367 +
368 + /* pretty forms */
369 + p(obj1);
370 + p(obj2);
371 + p(obj3);
372 + p(obj4);
373 +
374 + p(arr1);
375 + p(arr2);
376 + p(arr3);
377 + p(arr4);
378 +
379 + p(inline1);
380 + p(inline2);
381 +
382 + /* mixed forms */
383 + t(mixed1);
384 + jw_init(&mixed1);
385 + p(mixed1);
386 +
387 + return 0;
388 +}
389 +
390 +static void get_s(int line_nr, char **s_in)
391 +{
392 + *s_in = strtok(NULL, " ");
393 + if (!*s_in)
394 + die("line[%d]: expected: <s>", line_nr);
395 +}
396 +
397 +static void get_i(int line_nr, intmax_t *s_in)
398 +{
399 + char *s;
400 + char *endptr;
401 +
402 + get_s(line_nr, &s);
403 +
404 + *s_in = strtol(s, &endptr, 10);
405 + if (*endptr || errno == ERANGE)
406 + die("line[%d]: invalid integer value", line_nr);
407 +}
408 +
409 +static void get_d(int line_nr, double *s_in)
410 +{
411 + char *s;
412 + char *endptr;
413 +
414 + get_s(line_nr, &s);
415 +
416 + *s_in = strtod(s, &endptr);
417 + if (*endptr || errno == ERANGE)
418 + die("line[%d]: invalid float value", line_nr);
419 +}
420 +
421 +static int pretty;
422 +
423 +#define MAX_LINE_LENGTH (64 * 1024)
424 +
425 +static char *get_trimmed_line(char *buf, int buf_size)
426 +{
427 + int len;
428 +
429 + if (!fgets(buf, buf_size, stdin))
430 + return NULL;
431 +
432 + len = strlen(buf);
433 + while (len > 0) {
434 + char c = buf[len - 1];
435 + if (c == '\n' || c == '\r' || c == ' ' || c == '\t')
436 + buf[--len] = 0;
437 + else
438 + break;
439 + }
440 +
441 + while (*buf == ' ' || *buf == '\t')
442 + buf++;
443 +
444 + return buf;
445 +}
446 +
447 +static int scripted(void)
448 +{
449 + struct json_writer jw = JSON_WRITER_INIT;
450 + char buf[MAX_LINE_LENGTH];
451 + char *line;
452 + int line_nr = 0;
453 +
454 + line = get_trimmed_line(buf, MAX_LINE_LENGTH);
455 + if (!line)
456 + return 0;
457 +
458 + if (!strcmp(line, "object"))
459 + jw_object_begin(&jw, pretty);
460 + else if (!strcmp(line, "array"))
461 + jw_array_begin(&jw, pretty);
462 + else
463 + die("expected first line to be 'object' or 'array'");
464 +
465 + while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) {
466 + char *verb;
467 + char *key;
468 + char *s_value;
469 + intmax_t i_value;
470 + double d_value;
471 +
472 + line_nr++;
473 +
474 + verb = strtok(line, " ");
475 +
476 + if (!strcmp(verb, "end")) {
477 + jw_end(&jw);
478 + }
479 + else if (!strcmp(verb, "object-string")) {
480 + get_s(line_nr, &key);
481 + get_s(line_nr, &s_value);
482 + jw_object_string(&jw, key, s_value);
483 + }
484 + else if (!strcmp(verb, "object-int")) {
485 + get_s(line_nr, &key);
486 + get_i(line_nr, &i_value);
487 + jw_object_intmax(&jw, key, i_value);
488 + }
489 + else if (!strcmp(verb, "object-double")) {
490 + get_s(line_nr, &key);
491 + get_i(line_nr, &i_value);
492 + get_d(line_nr, &d_value);
493 + jw_object_double(&jw, key, i_value, d_value);
494 + }
495 + else if (!strcmp(verb, "object-true")) {
496 + get_s(line_nr, &key);
497 + jw_object_true(&jw, key);
498 + }
499 + else if (!strcmp(verb, "object-false")) {
500 + get_s(line_nr, &key);
501 + jw_object_false(&jw, key);
502 + }
503 + else if (!strcmp(verb, "object-null")) {
504 + get_s(line_nr, &key);
505 + jw_object_null(&jw, key);
506 + }
507 + else if (!strcmp(verb, "object-object")) {
508 + get_s(line_nr, &key);
509 + jw_object_inline_begin_object(&jw, key);
510 + }
511 + else if (!strcmp(verb, "object-array")) {
512 + get_s(line_nr, &key);
513 + jw_object_inline_begin_array(&jw, key);
514 + }
515 + else if (!strcmp(verb, "array-string")) {
516 + get_s(line_nr, &s_value);
517 + jw_array_string(&jw, s_value);
518 + }
519 + else if (!strcmp(verb, "array-int")) {
520 + get_i(line_nr, &i_value);
521 + jw_array_intmax(&jw, i_value);
522 + }
523 + else if (!strcmp(verb, "array-double")) {
524 + get_i(line_nr, &i_value);
525 + get_d(line_nr, &d_value);
526 + jw_array_double(&jw, i_value, d_value);
527 + }
528 + else if (!strcmp(verb, "array-true"))
529 + jw_array_true(&jw);
530 + else if (!strcmp(verb, "array-false"))
531 + jw_array_false(&jw);
532 + else if (!strcmp(verb, "array-null"))
533 + jw_array_null(&jw);
534 + else if (!strcmp(verb, "array-object"))
535 + jw_array_inline_begin_object(&jw);
536 + else if (!strcmp(verb, "array-array"))
537 + jw_array_inline_begin_array(&jw);
538 + else
539 + die("unrecognized token: '%s'", verb);
540 + }
541 +
542 + if (!jw_is_terminated(&jw))
543 + die("json not terminated: '%s'", jw.json.buf);
544 +
545 + printf("%s\n", jw.json.buf);
546 +
547 + strbuf_release(&jw.json);
548 + return 0;
549 +}
550 +
551 +int cmd__json_writer(int argc, const char **argv)
552 +{
553 + argc--; /* skip over "json-writer" arg */
554 + argv++;
555 +
556 + if (argc > 0 && argv[0][0] == '-') {
557 + if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--unit"))
558 + return unit_tests();
559 +
560 + if (!strcmp(argv[0], "-p") || !strcmp(argv[0], "--pretty"))
561 + pretty = 1;
562 + }
563 +
564 + return scripted();
565 +}
t/helper/test-tool.c
+1
@@ -19,6 +19,7 @@ static struct test_cmd cmds[] = {
19 { "genrandom", cmd__genrandom },
20 { "hashmap", cmd__hashmap },
21 { "index-version", cmd__index_version },
22 + { "json-writer", cmd__json_writer },
23 { "lazy-init-name-hash", cmd__lazy_init_name_hash },
24 { "match-trees", cmd__match_trees },
25 { "mergesort", cmd__mergesort },
t/helper/test-tool.h
+1
@@ -13,6 +13,7 @@ int cmd__example_decorate(int argc, const char **argv);
13 int cmd__genrandom(int argc, const char **argv);
14 int cmd__hashmap(int argc, const char **argv);
15 int cmd__index_version(int argc, const char **argv);
16 +int cmd__json_writer(int argc, const char **argv);
17 int cmd__lazy_init_name_hash(int argc, const char **argv);
18 int cmd__match_trees(int argc, const char **argv);
19 int cmd__mergesort(int argc, const char **argv);
t/t0019-json-writer.sh new
+331
@@ -0,0 +1,331 @@
1 +#!/bin/sh
2 +
3 +test_description='test json-writer JSON generation'
4 +. ./test-lib.sh
5 +
6 +test_expect_success 'unit test of json-writer routines' '
7 + test-tool json-writer -u
8 +'
9 +
10 +test_expect_success 'trivial object' '
11 + cat >expect <<-\EOF &&
12 + {}
13 + EOF
14 + cat >input <<-\EOF &&
15 + object
16 + end
17 + EOF
18 + test-tool json-writer <input >actual &&
19 + test_cmp expect actual
20 +'
21 +
22 +test_expect_success 'trivial array' '
23 + cat >expect <<-\EOF &&
24 + []
25 + EOF
26 + cat >input <<-\EOF &&
27 + array
28 + end
29 + EOF
30 + test-tool json-writer <input >actual &&
31 + test_cmp expect actual
32 +'
33 +
34 +test_expect_success 'simple object' '
35 + cat >expect <<-\EOF &&
36 + {"a":"abc","b":42,"c":3.14,"d":true,"e":false,"f":null}
37 + EOF
38 + cat >input <<-\EOF &&
39 + object
40 + object-string a abc
41 + object-int b 42
42 + object-double c 2 3.140
43 + object-true d
44 + object-false e
45 + object-null f
46 + end
47 + EOF
48 + test-tool json-writer <input >actual &&
49 + test_cmp expect actual
50 +'
51 +
52 +test_expect_success 'simple array' '
53 + cat >expect <<-\EOF &&
54 + ["abc",42,3.14,true,false,null]
55 + EOF
56 + cat >input <<-\EOF &&
57 + array
58 + array-string abc
59 + array-int 42
60 + array-double 2 3.140
61 + array-true
62 + array-false
63 + array-null
64 + end
65 + EOF
66 + test-tool json-writer <input >actual &&
67 + test_cmp expect actual
68 +'
69 +
70 +test_expect_success 'escape quoting string' '
71 + cat >expect <<-\EOF &&
72 + {"a":"abc\\def"}
73 + EOF
74 + cat >input <<-\EOF &&
75 + object
76 + object-string a abc\def
77 + end
78 + EOF
79 + test-tool json-writer <input >actual &&
80 + test_cmp expect actual
81 +'
82 +
83 +test_expect_success 'escape quoting string 2' '
84 + cat >expect <<-\EOF &&
85 + {"a":"abc\"def"}
86 + EOF
87 + cat >input <<-\EOF &&
88 + object
89 + object-string a abc"def
90 + end
91 + EOF
92 + test-tool json-writer <input >actual &&
93 + test_cmp expect actual
94 +'
95 +
96 +test_expect_success 'nested inline object' '
97 + cat >expect <<-\EOF &&
98 + {"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":{"e":false,"f":null}}}
99 + EOF
100 + cat >input <<-\EOF &&
101 + object
102 + object-string a abc
103 + object-int b 42
104 + object-object sub1
105 + object-double c 2 3.140
106 + object-true d
107 + object-object sub2
108 + object-false e
109 + object-null f
110 + end
111 + end
112 + end
113 + EOF
114 + test-tool json-writer <input >actual &&
115 + test_cmp expect actual
116 +'
117 +
118 +test_expect_success 'nested inline array' '
119 + cat >expect <<-\EOF &&
120 + ["abc",42,[3.14,true,[false,null]]]
121 + EOF
122 + cat >input <<-\EOF &&
123 + array
124 + array-string abc
125 + array-int 42
126 + array-array
127 + array-double 2 3.140
128 + array-true
129 + array-array
130 + array-false
131 + array-null
132 + end
133 + end
134 + end
135 + EOF
136 + test-tool json-writer <input >actual &&
137 + test_cmp expect actual
138 +'
139 +
140 +test_expect_success 'nested inline object and array' '
141 + cat >expect <<-\EOF &&
142 + {"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,null]}}
143 + EOF
144 + cat >input <<-\EOF &&
145 + object
146 + object-string a abc
147 + object-int b 42
148 + object-object sub1
149 + object-double c 2 3.140
150 + object-true d
151 + object-array sub2
152 + array-false
153 + array-null
154 + end
155 + end
156 + end
157 + EOF
158 + test-tool json-writer <input >actual &&
159 + test_cmp expect actual
160 +'
161 +
162 +test_expect_success 'nested inline object and array 2' '
163 + cat >expect <<-\EOF &&
164 + {"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,{"g":0,"h":1},null]}}
165 + EOF
166 + cat >input <<-\EOF &&
167 + object
168 + object-string a abc
169 + object-int b 42
170 + object-object sub1
171 + object-double c 2 3.140
172 + object-true d
173 + object-array sub2
174 + array-false
175 + array-object
176 + object-int g 0
177 + object-int h 1
178 + end
179 + array-null
180 + end
181 + end
182 + end
183 + EOF
184 + test-tool json-writer <input >actual &&
185 + test_cmp expect actual
186 +'
187 +
188 +test_expect_success 'pretty nested inline object and array 2' '
189 + sed -e "s/^|//" >expect <<-\EOF &&
190 + |{
191 + | "a": "abc",
192 + | "b": 42,
193 + | "sub1": {
194 + | "c": 3.14,
195 + | "d": true,
196 + | "sub2": [
197 + | false,
198 + | {
199 + | "g": 0,
200 + | "h": 1
201 + | },
202 + | null
203 + | ]
204 + | }
205 + |}
206 + EOF
207 + cat >input <<-\EOF &&
208 + object
209 + object-string a abc
210 + object-int b 42
211 + object-object sub1
212 + object-double c 2 3.140
213 + object-true d
214 + object-array sub2
215 + array-false
216 + array-object
217 + object-int g 0
218 + object-int h 1
219 + end
220 + array-null
221 + end
222 + end
223 + end
224 + EOF
225 + test-tool json-writer -p <input >actual &&
226 + test_cmp expect actual
227 +'
228 +
229 +test_expect_success 'inline object with no members' '
230 + cat >expect <<-\EOF &&
231 + {"a":"abc","empty":{},"b":42}
232 + EOF
233 + cat >input <<-\EOF &&
234 + object
235 + object-string a abc
236 + object-object empty
237 + end
238 + object-int b 42
239 + end
240 + EOF
241 + test-tool json-writer <input >actual &&
242 + test_cmp expect actual
243 +'
244 +
245 +test_expect_success 'inline array with no members' '
246 + cat >expect <<-\EOF &&
247 + {"a":"abc","empty":[],"b":42}
248 + EOF
249 + cat >input <<-\EOF &&
250 + object
251 + object-string a abc
252 + object-array empty
253 + end
254 + object-int b 42
255 + end
256 + EOF
257 + test-tool json-writer <input >actual &&
258 + test_cmp expect actual
259 +'
260 +
261 +test_expect_success 'larger empty example' '
262 + cat >expect <<-\EOF &&
263 + {"a":"abc","empty":[{},{},{},[],{}],"b":42}
264 + EOF
265 + cat >input <<-\EOF &&
266 + object
267 + object-string a abc
268 + object-array empty
269 + array-object
270 + end
271 + array-object
272 + end
273 + array-object
274 + end
275 + array-array
276 + end
277 + array-object
278 + end
279 + end
280 + object-int b 42
281 + end
282 + EOF
283 + test-tool json-writer <input >actual &&
284 + test_cmp expect actual
285 +'
286 +
287 +test_lazy_prereq PERLJSON '
288 + perl -MJSON -e "exit 0"
289 +'
290 +
291 +# As a sanity check, ask Perl to parse our generated JSON and recursively
292 +# dump the resulting data in sorted order. Confirm that that matches our
293 +# expectations.
294 +test_expect_success PERLJSON 'parse JSON using Perl' '
295 + cat >expect <<-\EOF &&
296 + row[0].a abc
297 + row[0].b 42
298 + row[0].sub1 hash
299 + row[0].sub1.c 3.14
300 + row[0].sub1.d 1
301 + row[0].sub1.sub2 array
302 + row[0].sub1.sub2[0] 0
303 + row[0].sub1.sub2[1] hash
304 + row[0].sub1.sub2[1].g 0
305 + row[0].sub1.sub2[1].h 1
306 + row[0].sub1.sub2[2] null
307 + EOF
308 + cat >input <<-\EOF &&
309 + object
310 + object-string a abc
311 + object-int b 42
312 + object-object sub1
313 + object-double c 2 3.140
314 + object-true d
315 + object-array sub2
316 + array-false
317 + array-object
318 + object-int g 0
319 + object-int h 1
320 + end
321 + array-null
322 + end
323 + end
324 + end
325 + EOF
326 + test-tool json-writer <input >output.json &&
327 + perl "$TEST_DIRECTORY"/t0019/parse_json.perl <output.json >actual &&
328 + test_cmp expect actual
329 +'
330 +
331 +test_done
t/t0019/parse_json.perl new
+52
@@ -0,0 +1,52 @@
1 +#!/usr/bin/perl
2 +use strict;
3 +use warnings;
4 +use JSON;
5 +
6 +sub dump_array {
7 + my ($label_in, $ary_ref) = @_;
8 + my @ary = @$ary_ref;
9 +
10 + for ( my $i = 0; $i <= $#{ $ary_ref }; $i++ )
11 + {
12 + my $label = "$label_in\[$i\]";
13 + dump_item($label, $ary[$i]);
14 + }
15 +}
16 +
17 +sub dump_hash {
18 + my ($label_in, $obj_ref) = @_;
19 + my %obj = %$obj_ref;
20 +
21 + foreach my $k (sort keys %obj) {
22 + my $label = (length($label_in) > 0) ? "$label_in.$k" : "$k";
23 + my $value = $obj{$k};
24 +
25 + dump_item($label, $value);
26 + }
27 +}
28 +
29 +sub dump_item {
30 + my ($label_in, $value) = @_;
31 + if (ref($value) eq 'ARRAY') {
32 + print "$label_in array\n";
33 + dump_array($label_in, $value);
34 + } elsif (ref($value) eq 'HASH') {
35 + print "$label_in hash\n";
36 + dump_hash($label_in, $value);
37 + } elsif (defined $value) {
38 + print "$label_in $value\n";
39 + } else {
40 + print "$label_in null\n";
41 + }
42 +}
43 +
44 +my $row = 0;
45 +while (<>) {
46 + my $data = decode_json( $_ );
47 + my $label = "row[$row]";
48 +
49 + dump_hash($label, $data);
50 + $row++;
51 +}
52 +