1 | // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) |
2 | /* |
3 | * Simple streaming JSON writer |
4 | * |
5 | * This takes care of the annoying bits of JSON syntax like the commas |
6 | * after elements |
7 | * |
8 | * Authors: Stephen Hemminger <stephen@networkplumber.org> |
9 | */ |
10 | |
11 | #include <stdio.h> |
12 | #include <stdbool.h> |
13 | #include <stdarg.h> |
14 | #include <assert.h> |
15 | #include <malloc.h> |
16 | #include <inttypes.h> |
17 | #include <stdint.h> |
18 | |
19 | #include "json_writer.h" |
20 | |
21 | struct json_writer { |
22 | FILE *out; /* output file */ |
23 | unsigned depth; /* nesting */ |
24 | bool pretty; /* optional whitepace */ |
25 | char sep; /* either nul or comma */ |
26 | }; |
27 | |
28 | /* indentation for pretty print */ |
29 | static void jsonw_indent(json_writer_t *self) |
30 | { |
31 | unsigned i; |
32 | for (i = 0; i < self->depth; ++i) |
33 | fputs(" " , self->out); |
34 | } |
35 | |
36 | /* end current line and indent if pretty printing */ |
37 | static void jsonw_eol(json_writer_t *self) |
38 | { |
39 | if (!self->pretty) |
40 | return; |
41 | |
42 | putc('\n', self->out); |
43 | jsonw_indent(self); |
44 | } |
45 | |
46 | /* If current object is not empty print a comma */ |
47 | static void jsonw_eor(json_writer_t *self) |
48 | { |
49 | if (self->sep != '\0') |
50 | putc(self->sep, self->out); |
51 | self->sep = ','; |
52 | } |
53 | |
54 | |
55 | /* Output JSON encoded string */ |
56 | /* Handles C escapes, does not do Unicode */ |
57 | static void jsonw_puts(json_writer_t *self, const char *str) |
58 | { |
59 | putc('"', self->out); |
60 | for (; *str; ++str) |
61 | switch (*str) { |
62 | case '\t': |
63 | fputs("\\t" , self->out); |
64 | break; |
65 | case '\n': |
66 | fputs("\\n" , self->out); |
67 | break; |
68 | case '\r': |
69 | fputs("\\r" , self->out); |
70 | break; |
71 | case '\f': |
72 | fputs("\\f" , self->out); |
73 | break; |
74 | case '\b': |
75 | fputs("\\b" , self->out); |
76 | break; |
77 | case '\\': |
78 | fputs("\\\\" , self->out); |
79 | break; |
80 | case '"': |
81 | fputs("\\\"" , self->out); |
82 | break; |
83 | default: |
84 | putc(*str, self->out); |
85 | } |
86 | putc('"', self->out); |
87 | } |
88 | |
89 | /* Create a new JSON stream */ |
90 | json_writer_t *jsonw_new(FILE *f) |
91 | { |
92 | json_writer_t *self = malloc(sizeof(*self)); |
93 | if (self) { |
94 | self->out = f; |
95 | self->depth = 0; |
96 | self->pretty = false; |
97 | self->sep = '\0'; |
98 | } |
99 | return self; |
100 | } |
101 | |
102 | /* End output to JSON stream */ |
103 | void jsonw_destroy(json_writer_t **self_p) |
104 | { |
105 | json_writer_t *self = *self_p; |
106 | |
107 | assert(self->depth == 0); |
108 | fputs("\n" , self->out); |
109 | fflush(self->out); |
110 | free(self); |
111 | *self_p = NULL; |
112 | } |
113 | |
114 | void jsonw_pretty(json_writer_t *self, bool on) |
115 | { |
116 | self->pretty = on; |
117 | } |
118 | |
119 | void jsonw_reset(json_writer_t *self) |
120 | { |
121 | assert(self->depth == 0); |
122 | self->sep = '\0'; |
123 | } |
124 | |
125 | /* Basic blocks */ |
126 | static void jsonw_begin(json_writer_t *self, int c) |
127 | { |
128 | jsonw_eor(self); |
129 | putc(c, self->out); |
130 | ++self->depth; |
131 | self->sep = '\0'; |
132 | } |
133 | |
134 | static void jsonw_end(json_writer_t *self, int c) |
135 | { |
136 | assert(self->depth > 0); |
137 | |
138 | --self->depth; |
139 | if (self->sep != '\0') |
140 | jsonw_eol(self); |
141 | putc(c, self->out); |
142 | self->sep = ','; |
143 | } |
144 | |
145 | |
146 | /* Add a JSON property name */ |
147 | void jsonw_name(json_writer_t *self, const char *name) |
148 | { |
149 | jsonw_eor(self); |
150 | jsonw_eol(self); |
151 | self->sep = '\0'; |
152 | jsonw_puts(self, str: name); |
153 | putc(':', self->out); |
154 | if (self->pretty) |
155 | putc(' ', self->out); |
156 | } |
157 | |
158 | void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap) |
159 | { |
160 | jsonw_eor(self); |
161 | putc('"', self->out); |
162 | vfprintf(self->out, fmt, ap); |
163 | putc('"', self->out); |
164 | } |
165 | |
166 | void jsonw_printf(json_writer_t *self, const char *fmt, ...) |
167 | { |
168 | va_list ap; |
169 | |
170 | va_start(ap, fmt); |
171 | jsonw_eor(self); |
172 | vfprintf(self->out, fmt, ap); |
173 | va_end(ap); |
174 | } |
175 | |
176 | /* Collections */ |
177 | void jsonw_start_object(json_writer_t *self) |
178 | { |
179 | jsonw_begin(self, c: '{'); |
180 | } |
181 | |
182 | void jsonw_end_object(json_writer_t *self) |
183 | { |
184 | jsonw_end(self, c: '}'); |
185 | } |
186 | |
187 | void jsonw_start_array(json_writer_t *self) |
188 | { |
189 | jsonw_begin(self, c: '['); |
190 | } |
191 | |
192 | void jsonw_end_array(json_writer_t *self) |
193 | { |
194 | jsonw_end(self, c: ']'); |
195 | } |
196 | |
197 | /* JSON value types */ |
198 | void jsonw_string(json_writer_t *self, const char *value) |
199 | { |
200 | jsonw_eor(self); |
201 | jsonw_puts(self, str: value); |
202 | } |
203 | |
204 | void jsonw_bool(json_writer_t *self, bool val) |
205 | { |
206 | jsonw_printf(self, fmt: "%s" , val ? "true" : "false" ); |
207 | } |
208 | |
209 | void jsonw_null(json_writer_t *self) |
210 | { |
211 | jsonw_printf(self, fmt: "null" ); |
212 | } |
213 | |
214 | void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num) |
215 | { |
216 | jsonw_printf(self, fmt, num); |
217 | } |
218 | |
219 | #ifdef notused |
220 | void jsonw_float(json_writer_t *self, double num) |
221 | { |
222 | jsonw_printf(self, "%g" , num); |
223 | } |
224 | #endif |
225 | |
226 | void jsonw_hu(json_writer_t *self, unsigned short num) |
227 | { |
228 | jsonw_printf(self, fmt: "%hu" , num); |
229 | } |
230 | |
231 | void jsonw_uint(json_writer_t *self, uint64_t num) |
232 | { |
233 | jsonw_printf(self, "%" PRIu64, num); |
234 | } |
235 | |
236 | void jsonw_lluint(json_writer_t *self, unsigned long long int num) |
237 | { |
238 | jsonw_printf(self, fmt: "%llu" , num); |
239 | } |
240 | |
241 | void jsonw_int(json_writer_t *self, int64_t num) |
242 | { |
243 | jsonw_printf(self, "%" PRId64, num); |
244 | } |
245 | |
246 | /* Basic name/value objects */ |
247 | void jsonw_string_field(json_writer_t *self, const char *prop, const char *val) |
248 | { |
249 | jsonw_name(self, name: prop); |
250 | jsonw_string(self, value: val); |
251 | } |
252 | |
253 | void jsonw_bool_field(json_writer_t *self, const char *prop, bool val) |
254 | { |
255 | jsonw_name(self, name: prop); |
256 | jsonw_bool(self, val); |
257 | } |
258 | |
259 | #ifdef notused |
260 | void jsonw_float_field(json_writer_t *self, const char *prop, double val) |
261 | { |
262 | jsonw_name(self, prop); |
263 | jsonw_float(self, val); |
264 | } |
265 | #endif |
266 | |
267 | void jsonw_float_field_fmt(json_writer_t *self, |
268 | const char *prop, |
269 | const char *fmt, |
270 | double val) |
271 | { |
272 | jsonw_name(self, name: prop); |
273 | jsonw_float_fmt(self, fmt, num: val); |
274 | } |
275 | |
276 | void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num) |
277 | { |
278 | jsonw_name(self, name: prop); |
279 | jsonw_uint(self, num); |
280 | } |
281 | |
282 | void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num) |
283 | { |
284 | jsonw_name(self, name: prop); |
285 | jsonw_hu(self, num); |
286 | } |
287 | |
288 | void jsonw_lluint_field(json_writer_t *self, |
289 | const char *prop, |
290 | unsigned long long int num) |
291 | { |
292 | jsonw_name(self, name: prop); |
293 | jsonw_lluint(self, num); |
294 | } |
295 | |
296 | void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num) |
297 | { |
298 | jsonw_name(self, name: prop); |
299 | jsonw_int(self, num); |
300 | } |
301 | |
302 | void jsonw_null_field(json_writer_t *self, const char *prop) |
303 | { |
304 | jsonw_name(self, name: prop); |
305 | jsonw_null(self); |
306 | } |
307 | |
308 | #ifdef TEST |
309 | int main(int argc, char **argv) |
310 | { |
311 | json_writer_t *wr = jsonw_new(stdout); |
312 | |
313 | jsonw_start_object(wr); |
314 | jsonw_pretty(wr, true); |
315 | jsonw_name(wr, "Vyatta" ); |
316 | jsonw_start_object(wr); |
317 | jsonw_string_field(wr, "url" , "http://vyatta.com" ); |
318 | jsonw_uint_field(wr, "downloads" , 2000000ul); |
319 | jsonw_float_field(wr, "stock" , 8.16); |
320 | |
321 | jsonw_name(wr, "ARGV" ); |
322 | jsonw_start_array(wr); |
323 | while (--argc) |
324 | jsonw_string(wr, *++argv); |
325 | jsonw_end_array(wr); |
326 | |
327 | jsonw_name(wr, "empty" ); |
328 | jsonw_start_array(wr); |
329 | jsonw_end_array(wr); |
330 | |
331 | jsonw_name(wr, "NIL" ); |
332 | jsonw_start_object(wr); |
333 | jsonw_end_object(wr); |
334 | |
335 | jsonw_null_field(wr, "my_null" ); |
336 | |
337 | jsonw_name(wr, "special chars" ); |
338 | jsonw_start_array(wr); |
339 | jsonw_string_field(wr, "slash" , "/" ); |
340 | jsonw_string_field(wr, "newline" , "\n" ); |
341 | jsonw_string_field(wr, "tab" , "\t" ); |
342 | jsonw_string_field(wr, "ff" , "\f" ); |
343 | jsonw_string_field(wr, "quote" , "\"" ); |
344 | jsonw_string_field(wr, "tick" , "\'" ); |
345 | jsonw_string_field(wr, "backslash" , "\\" ); |
346 | jsonw_end_array(wr); |
347 | |
348 | jsonw_end_object(wr); |
349 | |
350 | jsonw_end_object(wr); |
351 | jsonw_destroy(&wr); |
352 | return 0; |
353 | } |
354 | |
355 | #endif |
356 | |