diff options
Diffstat (limited to 'src/json.c')
| -rw-r--r-- | src/json.c | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..a09556a --- /dev/null +++ b/src/json.c @@ -0,0 +1,321 @@ +// TODO pretty print json + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdint.h> +#include <string.h> + +#include "json.h" +#include "error.h" + +#define JSON_BUF_INITIAL_CAPACITY 1024 + +static void json_push_context(json_t *buf, json_context_t ctx) { + if (buf->context_depth >= JSON_MAX_DEPTH) { + // TODO handle errors better than murdering the program + error_fatal("Exceeded JSON max nesting depth"); + } + buf->context_stack[buf->context_depth] = ctx; + buf->needs_comma[buf->context_depth] = false; + buf->context_depth++; +} + +static void json_pop_context(json_t *buf) { + if (buf->context_depth <= 0) { + // TODO better error handling + error_fatal("JSON context pop"); + } + buf->context_depth--; +} + +bool json_needs_comma(json_t *buf) { + return buf->needs_comma[buf->context_depth - 1]; +} + +void json_set_needs_comma(json_t *buf) { + buf->needs_comma[buf->context_depth - 1] = true; +} + +static void json_ensure_capacity(json_t *buf, size_t needed) { + if (buf->length + needed >= buf->capacity) { + size_t new_capacity = buf->capacity += JSON_BUF_INITIAL_CAPACITY; + while (buf->length + needed >= new_capacity) { + new_capacity += JSON_BUF_INITIAL_CAPACITY; + } + + char *new_data = realloc(buf->data, new_capacity); + if (!new_data) { + error_fatal("realloc: %s", strerror(errno)); + } + + buf->data = new_data; + buf->capacity = new_capacity; + } +} + +void json_append(json_t *buf, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int needed = vsnprintf(NULL, 0, fmt, args); + va_end(args); + + if (needed < 0) { + error_fatal("vsnprintf: %s", strerror(errno)); + } + + json_ensure_capacity(buf, (size_t)needed + 1); + + va_start(args, fmt); + vsprintf(buf->data + buf->length, fmt, args); + va_end(args); + + buf->length += (size_t)needed; +} + +void json_init(json_t *buf) { + buf->capacity = JSON_BUF_INITIAL_CAPACITY; + buf->length = 0; + buf->data = malloc(buf->capacity); + if (!buf->data) { + error_fatal("malloc: %s", strerror(errno)); + } + buf->data[0] = '\0'; + buf->context_depth = 0; + memset(buf->needs_comma, 0, sizeof(buf->needs_comma)); +} + +void json_free(json_t *buf) { + if (buf->data) { + free(buf->data); + } + buf->data = NULL; + buf->length = 0; + buf->capacity = 0; + memset(buf->needs_comma, 0, sizeof(buf->needs_comma)); + buf->context_depth = 0; +} + +const char *json_get(json_t *buf) { + return buf && buf->data ? buf->data : "{}"; +} + +void json_escape_string(json_t *buf, const char *input) { + json_append(buf, "\""); + for (size_t i = 0; input[i] != '\0'; i++) { + char c = input[i]; + switch(c) { + case '\\': + json_append(buf, "\\\\"); + break; + case '"': + json_append(buf, "\\\""); + break; + case '\b': + json_append(buf, "\\b"); + break; + case '\f': + json_append(buf, "\\f"); + break; + case '\n': + json_append(buf, "\\n"); + break; + case '\r': + json_append(buf, "\\r"); + break; + case '\t': + json_append(buf, "\\t"); + break; + default: + if ((unsigned char)c < 0x20) { + json_append(buf, "\\u%04x", c); + } else { + json_append(buf, "%c", c); + } + } + } + + json_append(buf, "\""); +} + +void json_start_object(json_t *buf) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + + json_append(buf, "{"); + json_push_context(buf, JSON_CONTEXT_OBJECT); +} + +void json_end_object(json_t *buf) { + json_append(buf, "}"); + json_pop_context(buf); +} + +void json_add_string(json_t *buf, const char *key, const char *value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":"); + json_escape_string(buf, value); + //buf->needs_comma = true; +} + +void json_add_string_or_null(json_t *buf, const char *key, const char *value) { + if (value && value[0] != '\0') { + json_add_string(buf, key, value); + } else { + json_add_null(buf, key); + } +} + +void json_add_int(json_t *buf, const char *key, int value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":%d", value); +} + +void json_add_int64(json_t *buf, const char *key, int64_t value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":%lld", value); +} + +void json_add_uint64(json_t *buf, const char *key, uint64_t value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":%llu", value); +} + +void json_add_double(json_t *buf, const char *key, double value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":%f", value); +} + +void json_add_bool(json_t *buf, const char *key, bool value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":%s", value ? "true" : "false"); +} + +void json_add_null(json_t *buf, const char *key) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_escape_string(buf, key); + json_append(buf, ":null"); +} + +void json_start_array(json_t *buf) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + + json_append(buf, "["); + json_push_context(buf, JSON_CONTEXT_ARRAY); +} + +void json_end_array(json_t *buf) { + json_append(buf, "]"); + json_pop_context(buf); +} + +void json_add_array_start(json_t *buf, const char *key) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + + json_escape_string(buf, key); + json_append(buf, ":"); + json_push_context(buf, JSON_CONTEXT_ARRAY); + json_append(buf, "["); + buf->needs_comma[buf->context_depth - 1] = false; +} + +void json_array_add_string(json_t *buf, const char *value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_escape_string(buf, value); + json_set_needs_comma(buf); +} + +void json_array_add_int(json_t *buf, int value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_append(buf, "%d", value); +} + +void json_array_add_int64(json_t *buf, int64_t value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_append(buf, "%lld", value); +} + +void json_array_add_uint64(json_t *buf, uint64_t value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_append(buf, "%llu", value); +} + +void json_array_add_double(json_t *buf, double value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_append(buf, "%f", value); +} + +void json_array_add_bool(json_t *buf, bool value) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_append(buf, "%s", value ? "true" : "false"); +} + +void json_array_add_null(json_t *buf) { + if (json_needs_comma(buf)) { + json_append(buf, ","); + } + json_set_needs_comma(buf); + + json_append(buf, "null"); +} |
