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 */
jsonw_indent(json_writer_t * self)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 */
jsonw_eol(json_writer_t * self)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 */
jsonw_eor(json_writer_t * self)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 */
jsonw_puts(json_writer_t * self,const char * str)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("\\n", self->out);
79  			break;
80  		case '"':
81  			fputs("\\\"", self->out);
82  			break;
83  		case '\'':
84  			fputs("\\\'", self->out);
85  			break;
86  		default:
87  			putc(*str, self->out);
88  		}
89  	putc('"', self->out);
90  }
91  
92  /* Create a new JSON stream */
jsonw_new(FILE * f)93  json_writer_t *jsonw_new(FILE *f)
94  {
95  	json_writer_t *self = malloc(sizeof(*self));
96  	if (self) {
97  		self->out = f;
98  		self->depth = 0;
99  		self->pretty = false;
100  		self->sep = '\0';
101  	}
102  	return self;
103  }
104  
105  /* End output to JSON stream */
jsonw_destroy(json_writer_t ** self_p)106  void jsonw_destroy(json_writer_t **self_p)
107  {
108  	json_writer_t *self = *self_p;
109  
110  	assert(self->depth == 0);
111  	fputs("\n", self->out);
112  	fflush(self->out);
113  	free(self);
114  	*self_p = NULL;
115  }
116  
jsonw_pretty(json_writer_t * self,bool on)117  void jsonw_pretty(json_writer_t *self, bool on)
118  {
119  	self->pretty = on;
120  }
121  
jsonw_reset(json_writer_t * self)122  void jsonw_reset(json_writer_t *self)
123  {
124  	assert(self->depth == 0);
125  	self->sep = '\0';
126  }
127  
128  /* Basic blocks */
jsonw_begin(json_writer_t * self,int c)129  static void jsonw_begin(json_writer_t *self, int c)
130  {
131  	jsonw_eor(self);
132  	putc(c, self->out);
133  	++self->depth;
134  	self->sep = '\0';
135  }
136  
jsonw_end(json_writer_t * self,int c)137  static void jsonw_end(json_writer_t *self, int c)
138  {
139  	assert(self->depth > 0);
140  
141  	--self->depth;
142  	if (self->sep != '\0')
143  		jsonw_eol(self);
144  	putc(c, self->out);
145  	self->sep = ',';
146  }
147  
148  
149  /* Add a JSON property name */
jsonw_name(json_writer_t * self,const char * name)150  void jsonw_name(json_writer_t *self, const char *name)
151  {
152  	jsonw_eor(self);
153  	jsonw_eol(self);
154  	self->sep = '\0';
155  	jsonw_puts(self, name);
156  	putc(':', self->out);
157  	if (self->pretty)
158  		putc(' ', self->out);
159  }
160  
jsonw_vprintf_enquote(json_writer_t * self,const char * fmt,va_list ap)161  void jsonw_vprintf_enquote(json_writer_t *self, const char *fmt, va_list ap)
162  {
163  	jsonw_eor(self);
164  	putc('"', self->out);
165  	vfprintf(self->out, fmt, ap);
166  	putc('"', self->out);
167  }
168  
jsonw_printf(json_writer_t * self,const char * fmt,...)169  void jsonw_printf(json_writer_t *self, const char *fmt, ...)
170  {
171  	va_list ap;
172  
173  	va_start(ap, fmt);
174  	jsonw_eor(self);
175  	vfprintf(self->out, fmt, ap);
176  	va_end(ap);
177  }
178  
179  /* Collections */
jsonw_start_object(json_writer_t * self)180  void jsonw_start_object(json_writer_t *self)
181  {
182  	jsonw_begin(self, '{');
183  }
184  
jsonw_end_object(json_writer_t * self)185  void jsonw_end_object(json_writer_t *self)
186  {
187  	jsonw_end(self, '}');
188  }
189  
jsonw_start_array(json_writer_t * self)190  void jsonw_start_array(json_writer_t *self)
191  {
192  	jsonw_begin(self, '[');
193  }
194  
jsonw_end_array(json_writer_t * self)195  void jsonw_end_array(json_writer_t *self)
196  {
197  	jsonw_end(self, ']');
198  }
199  
200  /* JSON value types */
jsonw_string(json_writer_t * self,const char * value)201  void jsonw_string(json_writer_t *self, const char *value)
202  {
203  	jsonw_eor(self);
204  	jsonw_puts(self, value);
205  }
206  
jsonw_bool(json_writer_t * self,bool val)207  void jsonw_bool(json_writer_t *self, bool val)
208  {
209  	jsonw_printf(self, "%s", val ? "true" : "false");
210  }
211  
jsonw_null(json_writer_t * self)212  void jsonw_null(json_writer_t *self)
213  {
214  	jsonw_printf(self, "null");
215  }
216  
jsonw_float_fmt(json_writer_t * self,const char * fmt,double num)217  void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num)
218  {
219  	jsonw_printf(self, fmt, num);
220  }
221  
222  #ifdef notused
jsonw_float(json_writer_t * self,double num)223  void jsonw_float(json_writer_t *self, double num)
224  {
225  	jsonw_printf(self, "%g", num);
226  }
227  #endif
228  
jsonw_hu(json_writer_t * self,unsigned short num)229  void jsonw_hu(json_writer_t *self, unsigned short num)
230  {
231  	jsonw_printf(self, "%hu", num);
232  }
233  
jsonw_uint(json_writer_t * self,uint64_t num)234  void jsonw_uint(json_writer_t *self, uint64_t num)
235  {
236  	jsonw_printf(self, "%"PRIu64, num);
237  }
238  
jsonw_lluint(json_writer_t * self,unsigned long long int num)239  void jsonw_lluint(json_writer_t *self, unsigned long long int num)
240  {
241  	jsonw_printf(self, "%llu", num);
242  }
243  
jsonw_int(json_writer_t * self,int64_t num)244  void jsonw_int(json_writer_t *self, int64_t num)
245  {
246  	jsonw_printf(self, "%"PRId64, num);
247  }
248  
249  /* Basic name/value objects */
jsonw_string_field(json_writer_t * self,const char * prop,const char * val)250  void jsonw_string_field(json_writer_t *self, const char *prop, const char *val)
251  {
252  	jsonw_name(self, prop);
253  	jsonw_string(self, val);
254  }
255  
jsonw_bool_field(json_writer_t * self,const char * prop,bool val)256  void jsonw_bool_field(json_writer_t *self, const char *prop, bool val)
257  {
258  	jsonw_name(self, prop);
259  	jsonw_bool(self, val);
260  }
261  
262  #ifdef notused
jsonw_float_field(json_writer_t * self,const char * prop,double val)263  void jsonw_float_field(json_writer_t *self, const char *prop, double val)
264  {
265  	jsonw_name(self, prop);
266  	jsonw_float(self, val);
267  }
268  #endif
269  
jsonw_float_field_fmt(json_writer_t * self,const char * prop,const char * fmt,double val)270  void jsonw_float_field_fmt(json_writer_t *self,
271  			   const char *prop,
272  			   const char *fmt,
273  			   double val)
274  {
275  	jsonw_name(self, prop);
276  	jsonw_float_fmt(self, fmt, val);
277  }
278  
jsonw_uint_field(json_writer_t * self,const char * prop,uint64_t num)279  void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num)
280  {
281  	jsonw_name(self, prop);
282  	jsonw_uint(self, num);
283  }
284  
jsonw_hu_field(json_writer_t * self,const char * prop,unsigned short num)285  void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num)
286  {
287  	jsonw_name(self, prop);
288  	jsonw_hu(self, num);
289  }
290  
jsonw_lluint_field(json_writer_t * self,const char * prop,unsigned long long int num)291  void jsonw_lluint_field(json_writer_t *self,
292  			const char *prop,
293  			unsigned long long int num)
294  {
295  	jsonw_name(self, prop);
296  	jsonw_lluint(self, num);
297  }
298  
jsonw_int_field(json_writer_t * self,const char * prop,int64_t num)299  void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num)
300  {
301  	jsonw_name(self, prop);
302  	jsonw_int(self, num);
303  }
304  
jsonw_null_field(json_writer_t * self,const char * prop)305  void jsonw_null_field(json_writer_t *self, const char *prop)
306  {
307  	jsonw_name(self, prop);
308  	jsonw_null(self);
309  }
310  
311  #ifdef TEST
main(int argc,char ** argv)312  int main(int argc, char **argv)
313  {
314  	json_writer_t *wr = jsonw_new(stdout);
315  
316  	jsonw_start_object(wr);
317  	jsonw_pretty(wr, true);
318  	jsonw_name(wr, "Vyatta");
319  	jsonw_start_object(wr);
320  	jsonw_string_field(wr, "url", "http://vyatta.com");
321  	jsonw_uint_field(wr, "downloads", 2000000ul);
322  	jsonw_float_field(wr, "stock", 8.16);
323  
324  	jsonw_name(wr, "ARGV");
325  	jsonw_start_array(wr);
326  	while (--argc)
327  		jsonw_string(wr, *++argv);
328  	jsonw_end_array(wr);
329  
330  	jsonw_name(wr, "empty");
331  	jsonw_start_array(wr);
332  	jsonw_end_array(wr);
333  
334  	jsonw_name(wr, "NIL");
335  	jsonw_start_object(wr);
336  	jsonw_end_object(wr);
337  
338  	jsonw_null_field(wr, "my_null");
339  
340  	jsonw_name(wr, "special chars");
341  	jsonw_start_array(wr);
342  	jsonw_string_field(wr, "slash", "/");
343  	jsonw_string_field(wr, "newline", "\n");
344  	jsonw_string_field(wr, "tab", "\t");
345  	jsonw_string_field(wr, "ff", "\f");
346  	jsonw_string_field(wr, "quote", "\"");
347  	jsonw_string_field(wr, "tick", "\'");
348  	jsonw_string_field(wr, "backslash", "\\");
349  	jsonw_end_array(wr);
350  
351  	jsonw_end_object(wr);
352  
353  	jsonw_end_object(wr);
354  	jsonw_destroy(&wr);
355  	return 0;
356  }
357  
358  #endif
359