356 lines · c
1// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)2/*3 * Simple streaming JSON writer4 *5 * This takes care of the annoying bits of JSON syntax like the commas6 * after elements7 *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 21struct 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 */29static 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 */37static 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 */47static 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 */57static 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 */90json_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 */103void 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 114void jsonw_pretty(json_writer_t *self, bool on)115{116 self->pretty = on;117}118 119void jsonw_reset(json_writer_t *self)120{121 assert(self->depth == 0);122 self->sep = '\0';123}124 125/* Basic blocks */126static 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 134static 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 */147void 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, name);153 putc(':', self->out);154 if (self->pretty)155 putc(' ', self->out);156}157 158void 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 166void 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 */177void jsonw_start_object(json_writer_t *self)178{179 jsonw_begin(self, '{');180}181 182void jsonw_end_object(json_writer_t *self)183{184 jsonw_end(self, '}');185}186 187void jsonw_start_array(json_writer_t *self)188{189 jsonw_begin(self, '[');190}191 192void jsonw_end_array(json_writer_t *self)193{194 jsonw_end(self, ']');195}196 197/* JSON value types */198void jsonw_string(json_writer_t *self, const char *value)199{200 jsonw_eor(self);201 jsonw_puts(self, value);202}203 204void jsonw_bool(json_writer_t *self, bool val)205{206 jsonw_printf(self, "%s", val ? "true" : "false");207}208 209void jsonw_null(json_writer_t *self)210{211 jsonw_printf(self, "null");212}213 214void jsonw_float_fmt(json_writer_t *self, const char *fmt, double num)215{216 jsonw_printf(self, fmt, num);217}218 219#ifdef notused220void jsonw_float(json_writer_t *self, double num)221{222 jsonw_printf(self, "%g", num);223}224#endif225 226void jsonw_hu(json_writer_t *self, unsigned short num)227{228 jsonw_printf(self, "%hu", num);229}230 231void jsonw_uint(json_writer_t *self, uint64_t num)232{233 jsonw_printf(self, "%"PRIu64, num);234}235 236void jsonw_lluint(json_writer_t *self, unsigned long long int num)237{238 jsonw_printf(self, "%llu", num);239}240 241void jsonw_int(json_writer_t *self, int64_t num)242{243 jsonw_printf(self, "%"PRId64, num);244}245 246/* Basic name/value objects */247void jsonw_string_field(json_writer_t *self, const char *prop, const char *val)248{249 jsonw_name(self, prop);250 jsonw_string(self, val);251}252 253void jsonw_bool_field(json_writer_t *self, const char *prop, bool val)254{255 jsonw_name(self, prop);256 jsonw_bool(self, val);257}258 259#ifdef notused260void 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#endif266 267void jsonw_float_field_fmt(json_writer_t *self,268 const char *prop,269 const char *fmt,270 double val)271{272 jsonw_name(self, prop);273 jsonw_float_fmt(self, fmt, val);274}275 276void jsonw_uint_field(json_writer_t *self, const char *prop, uint64_t num)277{278 jsonw_name(self, prop);279 jsonw_uint(self, num);280}281 282void jsonw_hu_field(json_writer_t *self, const char *prop, unsigned short num)283{284 jsonw_name(self, prop);285 jsonw_hu(self, num);286}287 288void jsonw_lluint_field(json_writer_t *self,289 const char *prop,290 unsigned long long int num)291{292 jsonw_name(self, prop);293 jsonw_lluint(self, num);294}295 296void jsonw_int_field(json_writer_t *self, const char *prop, int64_t num)297{298 jsonw_name(self, prop);299 jsonw_int(self, num);300}301 302void jsonw_null_field(json_writer_t *self, const char *prop)303{304 jsonw_name(self, prop);305 jsonw_null(self);306}307 308#ifdef TEST309int 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#endif356