| // 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 <linux/compiler.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("\\n", 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; |
| } |
| |
| /* 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 __printf(2, 0) |
| 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 __printf(2, 3) 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 |