summaryrefslogtreecommitdiff
path: root/src/json.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/json.c')
-rw-r--r--src/json.c321
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");
+}