json_dumper: support 'null' json object.
[metze/wireshark/wip.git] / wsutil / json_dumper.c
1 /* wsjson.h
2  * Routines for serializing data as JSON.
3  *
4  * Copyright 2018, Peter Wu <peter@lekensteyn.nl>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13 #include "json_dumper.h"
14
15 /*
16  * json_dumper.state[current_depth] describes a nested element:
17  * - type: none/object/array/value
18  * - has_name: Whether the object member name was set.
19  */
20 enum json_dumper_element_type {
21     JSON_DUMPER_TYPE_NONE = 0,
22     JSON_DUMPER_TYPE_VALUE = 1,
23     JSON_DUMPER_TYPE_OBJECT = 2,
24     JSON_DUMPER_TYPE_ARRAY = 3,
25 };
26 #define JSON_DUMPER_TYPE(state)         ((enum json_dumper_element_type)((state) & 3))
27 #define JSON_DUMPER_HAS_NAME            (1 << 2)
28
29 #define JSON_DUMPER_FLAGS_ERROR     (1 << 16)   /* Output flag: an error occurred. */
30
31 enum json_dumper_change {
32     JSON_DUMPER_BEGIN,
33     JSON_DUMPER_END,
34     JSON_DUMPER_SET_NAME,
35     JSON_DUMPER_SET_VALUE,
36     JSON_DUMPER_FINISH,
37 };
38
39 static void
40 json_puts_string(FILE *fp, const char *str)
41 {
42     if (!str) {
43         fputs("null", fp);
44         return;
45     }
46
47     static const char json_cntrl[0x20][6] = {
48         "u0000", "u0001", "u0002", "u0003", "u0004", "u0005", "u0006", "u0007", "b",     "t",     "n",     "u000b", "f",     "r",     "u000e", "u000f",
49         "u0010", "u0011", "u0012", "u0013", "u0014", "u0015", "u0016", "u0017", "u0018", "u0019", "u001a", "u001b", "u001c", "u001d", "u001e", "u001f"
50     };
51
52     fputc('"', fp);
53     for (int i = 0; str[i]; i++) {
54         if ((guint)str[i] < 0x20) {
55             fputc('\\', fp);
56             fputs(json_cntrl[(guint)str[i]], fp);
57         } else {
58             if (str[i] == '\\' || str[i] == '"') {
59                 fputc('\\', fp);
60             }
61             fputc(str[i], fp);
62         }
63     }
64     fputc('"', fp);
65 }
66
67 /**
68  * Checks that the dumper state is valid for a new change. Any error will be
69  * sticky and prevent further dumps from succeeding.
70  */
71 static gboolean
72 json_dumper_check_state(json_dumper *dumper, enum json_dumper_change change, enum json_dumper_element_type type)
73 {
74     if ((dumper->flags & JSON_DUMPER_FLAGS_ERROR)) {
75         return FALSE;
76     }
77
78     int depth = dumper->current_depth;
79     if (depth < 0 || depth >= JSON_DUMPER_MAX_DEPTH) {
80         /* Corrupted state, no point in continuing. */
81         dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
82         return FALSE;
83     }
84
85     guint8 prev_state = depth > 0 ? dumper->state[depth - 1] : 0;
86     enum json_dumper_element_type prev_type = JSON_DUMPER_TYPE(prev_state);
87
88     gboolean ok = FALSE;
89     switch (change) {
90         case JSON_DUMPER_BEGIN:
91             ok = depth + 1 < JSON_DUMPER_MAX_DEPTH;
92             break;
93         case JSON_DUMPER_END:
94             ok = prev_type == type && !(prev_state & JSON_DUMPER_HAS_NAME);
95             break;
96         case JSON_DUMPER_SET_NAME:
97             /* An object name can only be set once before a value is set. */
98             ok = prev_type == JSON_DUMPER_TYPE_OBJECT && !(prev_state & JSON_DUMPER_HAS_NAME);
99             break;
100         case JSON_DUMPER_SET_VALUE:
101             if (prev_type == JSON_DUMPER_TYPE_OBJECT) {
102                 ok = (prev_state & JSON_DUMPER_HAS_NAME);
103             } else if (prev_type == JSON_DUMPER_TYPE_ARRAY) {
104                 ok = TRUE;
105             } else {
106                 ok = JSON_DUMPER_TYPE(dumper->state[depth]) == JSON_DUMPER_TYPE_NONE;
107             }
108             break;
109         case JSON_DUMPER_FINISH:
110             ok = depth == 0;
111             break;
112     }
113     if (!ok) {
114         dumper->flags |= JSON_DUMPER_FLAGS_ERROR;
115     }
116     return ok;
117 }
118
119 static void
120 print_newline_indent(const json_dumper *dumper, int depth)
121 {
122     if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
123         fputc('\n', dumper->output_file);
124         for (int i = 0; i < depth; i++) {
125             fputs("  ", dumper->output_file);
126         }
127     }
128 }
129
130 /**
131  * Prints commas, newlines and indentation (if necessary). Used for array
132  * values, object names and normal values (strings, etc.).
133  */
134 static void
135 prepare_token(json_dumper *dumper)
136 {
137     if (dumper->current_depth == 0) {
138         // not part of an array or object.
139         return;
140     }
141     guint8 prev_state = dumper->state[dumper->current_depth - 1];
142
143     // While processing the object value, reset the key state as it is consumed.
144     dumper->state[dumper->current_depth - 1] &= ~JSON_DUMPER_HAS_NAME;
145
146     switch (JSON_DUMPER_TYPE(prev_state)) {
147         case JSON_DUMPER_TYPE_OBJECT:
148             if ((prev_state & JSON_DUMPER_HAS_NAME)) {
149                 // Object key already set, value follows. No indentation needed.
150                 return;
151             }
152             break;
153         case JSON_DUMPER_TYPE_ARRAY:
154             break;
155         default:
156             // Initial values do not need indentation.
157             return;
158     }
159
160     if (dumper->state[dumper->current_depth]) {
161         fputc(',', dumper->output_file);
162     }
163     print_newline_indent(dumper, dumper->current_depth);
164 }
165
166 /**
167  * Common code to close an object/array, printing a closing character (and if
168  * necessary, it is preceded by newline and indentation).
169  */
170 static void
171 finish_token(const json_dumper *dumper, char close_char)
172 {
173     // if the object/array was non-empty, add a newline and indentation.
174     if (dumper->state[dumper->current_depth]) {
175         print_newline_indent(dumper, dumper->current_depth - 1);
176     }
177     fputc(close_char, dumper->output_file);
178 }
179
180 void
181 json_dumper_begin_object(json_dumper *dumper)
182 {
183     if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_OBJECT)) {
184         return;
185     }
186
187     prepare_token(dumper);
188     fputc('{', dumper->output_file);
189
190     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_OBJECT;
191     ++dumper->current_depth;
192     dumper->state[dumper->current_depth] = 0;
193 }
194
195 void
196 json_dumper_set_member_name(json_dumper *dumper, const char *name)
197 {
198     if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_NAME, JSON_DUMPER_TYPE_NONE)) {
199         return;
200     }
201
202     prepare_token(dumper);
203     json_puts_string(dumper->output_file, name);
204     fputc(':', dumper->output_file);
205     if ((dumper->flags & JSON_DUMPER_FLAGS_PRETTY_PRINT)) {
206         fputc(' ', dumper->output_file);
207     }
208
209     dumper->state[dumper->current_depth - 1] |= JSON_DUMPER_HAS_NAME;
210 }
211
212 void
213 json_dumper_end_object(json_dumper *dumper)
214 {
215     if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_OBJECT)) {
216         return;
217     }
218
219     finish_token(dumper, '}');
220
221     --dumper->current_depth;
222 }
223
224 void
225 json_dumper_begin_array(json_dumper *dumper)
226 {
227     if (!json_dumper_check_state(dumper, JSON_DUMPER_BEGIN, JSON_DUMPER_TYPE_ARRAY)) {
228         return;
229     }
230
231     prepare_token(dumper);
232     fputc('[', dumper->output_file);
233
234     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_ARRAY;
235     ++dumper->current_depth;
236     dumper->state[dumper->current_depth] = 0;
237 }
238
239 void
240 json_dumper_end_array(json_dumper *dumper)
241 {
242     if (!json_dumper_check_state(dumper, JSON_DUMPER_END, JSON_DUMPER_TYPE_ARRAY)) {
243         return;
244     }
245
246     finish_token(dumper, ']');
247
248     --dumper->current_depth;
249 }
250
251 void
252 json_dumper_value_string(json_dumper *dumper, const char *value)
253 {
254     if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
255         return;
256     }
257
258     prepare_token(dumper);
259     json_puts_string(dumper->output_file, value);
260
261     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
262 }
263
264 void
265 json_dumper_value_anyf(json_dumper *dumper, const char *format, ...)
266 {
267     va_list ap;
268     if (!json_dumper_check_state(dumper, JSON_DUMPER_SET_VALUE, JSON_DUMPER_TYPE_VALUE)) {
269         return;
270     }
271
272     prepare_token(dumper);
273     va_start(ap, format);
274     vfprintf(dumper->output_file, format, ap);
275     va_end(ap);
276
277     dumper->state[dumper->current_depth] = JSON_DUMPER_TYPE_VALUE;
278 }
279
280 gboolean
281 json_dumper_finish(json_dumper *dumper)
282 {
283     if (!json_dumper_check_state(dumper, JSON_DUMPER_FINISH, JSON_DUMPER_TYPE_NONE)) {
284         return FALSE;
285     }
286
287     fputc('\n', dumper->output_file);
288     dumper->state[0] = 0;
289     return TRUE;
290 }