// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
/*
* Simple streaming JSON writer
*
* This takes care of the annoying bits of JSON syntax like the commas
* after elements
*
* Authors: Stephen Hemminger <stephen@networkplumber.org>
*/
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <assert.h>
#include <malloc.h>
#include <inttypes.h>
#include <stdint.h>
#include "json_writer.h"
struct json_writer {
FILE *out;
/* output file */
unsigned depth;
/* nesting */
bool pretty;
/* optional whitepace */
char sep;
/* either nul or comma */
};
/* indentation for pretty print */
static void jsonw_indent(json_writer_t *self)
{
unsigned i;
for (i = 0; i < self->depth; ++i)
fputs(
" ", self->out);
}
/* end current line and indent if pretty printing */
static void jsonw_eol(json_writer_t *self)
{
if (!self->pretty)
return;
putc(
'\n', self->out);
jsonw_indent(self);
}
/* If current object is not empty print a comma */
static void jsonw_eor(json_writer_t *self)
{
if (self->sep !=
'\0')
putc(self->sep, self->out);
self->sep =
',';
}
/* Output JSON encoded string */
/* Handles C escapes, does not do Unicode */
static void jsonw_puts(json_writer_t *self,
const char *str)
{
putc(
'"', self->out);
for (; *str; ++str)
switch (*str) {
case '\t':
fputs(
"\\t", self->out);
break;
case '\n':
fputs(
"\\n", self->out);
break;
case '\r':
fputs(
"\\r", self->out);
break;
case '\f':
fputs(
"\\f", self->out);
break;
case '\b':
fputs(
"\\b", self->out);
break;
case '\\':
fputs(
"\\\\", self->out);
break;
case '"':
fputs(
"\\\"", self->out);
break;
default:
putc(*str, self->out);
}
putc(
'"', self->out);
}
/* Create a new JSON stream */
json_writer_t *jsonw_new(FILE *f)
{
json_writer_t *self = malloc(
sizeof(*self));
if (self) {
self->out = f;
self->depth = 0;
self->pretty =
false;
self->sep =
'\0';
}
return self;
}
/* End output to JSON stream */
void jsonw_destroy(json_writer_t **self_p)
{
json_writer_t *self = *self_p;
assert(self->depth == 0);
fputs(
"\n", self->out);
fflush(self->out);
free(self);
*self_p = NULL;
}
void jsonw_pretty(json_writer_t *self,
bool on)
{
self->pretty = on;
}
void jsonw_reset(json_writer_t *self)
{
assert(self->depth == 0);
self->sep =
'\0';
}
/* Basic blocks */
static void jsonw_begin(json_writer_t *self,
int c)
{
jsonw_eor(self);
putc(c, self->out);
++self->depth;
self->sep =
'\0';
}
static void jsonw_end(json_writer_t *self,
int c)
{
assert(self->depth > 0);
--self->depth;
if (self->sep !=
'\0')
jsonw_eol(self);
putc(c, self->out);
self->sep =
',';
}
/* Add a JSON property name */
void jsonw_name(json_writer_t *self,
const char *name)
{
jsonw_eor(self);
jsonw_eol(self);
self->sep =
'\0';
jsonw_puts(self, name);
putc(
':', self->out);
if (self->pretty)
putc(
' ', self->out);
}
void jsonw_vprintf_enquote(json_writer_t *self,
const char *fmt, va_list ap)
{
jsonw_eor(self);
putc(
'"', self->out);
vfprintf(self->out, fmt, ap);
putc(
'"', self->out);
}
void jsonw_printf(json_writer_t *self,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
jsonw_eor(self);
vfprintf(self->out, fmt, ap);
va_end(ap);
}
/* Collections */
void jsonw_start_object(json_writer_t *self)
{
jsonw_begin(self,
'{');
}
void jsonw_end_object(json_writer_t *self)
{
jsonw_end(self,
'}');
}
void jsonw_start_array(json_writer_t *self)
{
jsonw_begin(self,
'[');
}
void jsonw_end_array(json_writer_t *self)
{
jsonw_end(self,
']');
}
/* JSON value types */
void jsonw_string(json_writer_t *self,
const char *value)
{
jsonw_eor(self);
jsonw_puts(self, value);
}
void jsonw_bool(json_writer_t *self,
bool val)
{
jsonw_printf(self,
"%s", val ?
"true" :
"false");
}
void jsonw_null(json_writer_t *self)
{
jsonw_printf(self,
"null");
}
void jsonw_float_fmt(json_writer_t *self,
const char *fmt,
double num)
{
jsonw_printf(self, fmt, num);
}
#ifdef notused
void jsonw_float(json_writer_t *self,
double num)
{
jsonw_printf(self,
"%g", num);
}
#endif
void jsonw_hu(json_writer_t *self,
unsigned short num)
{
jsonw_printf(self,
"%hu", num);
}
void jsonw_uint(json_writer_t *self, uint64_t num)
{
jsonw_printf(self,
"%"PRIu64, num);
}
void jsonw_lluint(json_writer_t *self,
unsigned long long int num)
{
jsonw_printf(self,
"%llu", num);
}
void jsonw_int(json_writer_t *self, int64_t num)
{
jsonw_printf(self,
"%"PRId64, num);
}
/* Basic name/value objects */
void jsonw_string_field(json_writer_t *self,
const char *prop,
const char *val)
{
jsonw_name(self, prop);
jsonw_string(self, val);
}
void jsonw_bool_field(json_writer_t *self,
const char *prop,
bool val)
{
jsonw_name(self, prop);
jsonw_bool(self, val);
}
#ifdef notused
void jsonw_float_field(json_writer_t *self,
const char *prop,
double val)
{
jsonw_name(self, prop);
jsonw_float(self, val);
}
#endif
void jsonw_float_field_fmt(json_writer_t *self,
const char *prop,
const char *fmt,
double val)
{
jsonw_name(self, prop);
jsonw_float_fmt(self, fmt, val);
}
void jsonw_uint_field(json_writer_t *self,
const char *prop, uint64_t num)
{
jsonw_name(self, prop);
jsonw_uint(self, num);
}
void jsonw_hu_field(json_writer_t *self,
const char *prop,
unsigned short num)
{
jsonw_name(self, prop);
jsonw_hu(self, num);
}
void jsonw_lluint_field(json_writer_t *self,
const char *prop,
unsigned long long int num)
{
jsonw_name(self, prop);
jsonw_lluint(self, num);
}
void jsonw_int_field(json_writer_t *self,
const char *prop, int64_t num)
{
jsonw_name(self, prop);
jsonw_int(self, num);
}
void jsonw_null_field(json_writer_t *self,
const char *prop)
{
jsonw_name(self, prop);
jsonw_null(self);
}
#ifdef TEST
int main(
int argc,
char **argv)
{
json_writer_t *wr = jsonw_new(stdout);
jsonw_start_object(wr);
jsonw_pretty(wr,
true);
jsonw_name(wr,
"Vyatta");
jsonw_start_object(wr);
jsonw_string_field(wr,
"url",
"http://vyatta.com");
jsonw_uint_field(wr,
"downloads", 2000000ul);
jsonw_float_field(wr,
"stock", 8.16);
jsonw_name(wr,
"ARGV");
jsonw_start_array(wr);
while (--argc)
jsonw_string(wr, *++argv);
jsonw_end_array(wr);
jsonw_name(wr,
"empty");
jsonw_start_array(wr);
jsonw_end_array(wr);
jsonw_name(wr,
"NIL");
jsonw_start_object(wr);
jsonw_end_object(wr);
jsonw_null_field(wr,
"my_null");
jsonw_name(wr,
"special chars");
jsonw_start_array(wr);
jsonw_string_field(wr,
"slash",
"/");
jsonw_string_field(wr,
"newline",
"\n");
jsonw_string_field(wr,
"tab",
"\t");
jsonw_string_field(wr,
"ff",
"\f");
jsonw_string_field(wr,
"quote",
"\"");
jsonw_string_field(wr,
"tick",
"\'");
jsonw_string_field(wr,
"backslash",
"\\");
jsonw_end_array(wr);
jsonw_end_object(wr);
jsonw_end_object(wr);
jsonw_destroy(&wr);
return 0;
}
#endif