#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "json.h" /*---------------------------------------------------------------------------- * Memory utils */ static char * xstrdup(const char *str) { char *s = strdup(str); if (s == NULL) err(1, "libjson"); return s; } static void * xcalloc(size_t number, size_t size) { void *ptr = calloc(number, size); if (ptr == NULL) err(1, "libjson"); return ptr; } /*---------------------------------------------------------------------------- * Buffer */ #define MAX(a,b) (a) < (b) ? (b) : (a) struct buffer { size_t cap; size_t size; char *data; }; static void buffer_init(struct buffer *buffer) { buffer->cap = BUFSIZ; buffer->size = 0; buffer->data = xcalloc(BUFSIZ, sizeof(char)); } static void buffer_reset(struct buffer *buffer) { buffer->size = 0; if (buffer->data) *buffer->data = '\0'; } static void buffer_append(struct buffer *buffer, const char *str, size_t size) { if (buffer->cap - buffer->size <= size) { buffer->cap += MAX(BUFSIZ, size + 1 - (buffer->cap - buffer->size)); buffer->data = realloc(buffer->data, buffer->cap); if (buffer->data == NULL) err(1, "libjson"); } memcpy(buffer->data + buffer->size, str, size); buffer->size += size; buffer->data[buffer->size] = '\0'; } static void buffer_append_str(struct buffer *buffer, char *str) { buffer_append(buffer, str, strlen(str)); } static inline void buffer_append_char(struct buffer *buffer, const char c) { buffer_append(buffer, &c, 1); } static void buffer_append_dbl_quote(struct buffer *buffer, char *str) { buffer_append_str(buffer, "\""); for (; *str; str++) { if (*str == '"' || *str == '\\') { buffer_append_str(buffer, "\\"); buffer_append(buffer, str, 1); } else if (*str == '\b') buffer_append_str(buffer, "\\b"); else if (*str == '\f') buffer_append_str(buffer, "\\f"); else if (*str == '\n') buffer_append_str(buffer, "\\n"); else if (*str == '\r') buffer_append_str(buffer, "\\r"); else if (*str == '\t') buffer_append_str(buffer, "\\t"); else buffer_append(buffer, str, 1); } buffer_append_str(buffer, "\""); } static void buffer_append_sprintf(struct buffer *buffer, const char *format, ...) { va_list argp; char *str = NULL; int res; va_start(argp, format); res = vasprintf(&str, format, argp); if (res == -1) err(1, "libjson"); va_end(argp); buffer_append_str(buffer, str); free(str); } /*---------------------------------------------------------------------------- * Misc */ static char * slurp_file(const char *filename) { struct buffer buffer; buffer_init(&buffer); FILE *file = fopen(filename, "r"); if (file == NULL) return NULL; char buf[BUFSIZ]; size_t nread; while ((nread = fread(buf, sizeof(char), BUFSIZ, file)) > 0) buffer_append(&buffer, buf, nread); fclose(file); return buffer.data; } /*---------------------------------------------------------------------------- * unicode / utf8 */ static bool parse_hex(const char *str, uint32_t *codepoint) { *codepoint = 0; for (size_t i = 0; i < 4; i++) { uint8_t byte = str[i]; if (byte >= '0' && byte <= '9') byte = byte - '0'; else if (byte >= 'a' && byte <= 'f') byte = byte - 'a' + 10; else if (byte >= 'A' && byte <= 'F') byte = byte - 'A' + 10; else return false; *codepoint = (*codepoint << 4) + byte; } return true; } static int utf8_encode(uint32_t codepoint, char *utf8) { if (codepoint <= 0x7F) { /* Plain ASCII */ utf8[0] = (char) codepoint; return 1; } else if (codepoint <= 0x07FF) { /* 2-byte unicode */ utf8[0] = (char) (((codepoint >> 6) & 0x1F) | 0xC0); utf8[1] = (char) (((codepoint >> 0) & 0x3F) | 0x80); return 2; } else if (codepoint <= 0xFFFF) { /* 3-byte unicode */ utf8[0] = (char) (((codepoint >> 12) & 0x0F) | 0xE0); utf8[1] = (char) (((codepoint >> 6) & 0x3F) | 0x80); utf8[2] = (char) (((codepoint >> 0) & 0x3F) | 0x80); return 3; } else if (codepoint <= 0x10FFFF) { /* 4-byte unicode */ utf8[0] = (char) (((codepoint >> 18) & 0x07) | 0xF0); utf8[1] = (char) (((codepoint >> 12) & 0x3F) | 0x80); utf8[2] = (char) (((codepoint >> 6) & 0x3F) | 0x80); utf8[3] = (char) (((codepoint >> 0) & 0x3F) | 0x80); return 4; } else { /* error - use replacement character */ utf8[0] = (char) 0xEF; utf8[1] = (char) 0xBF; utf8[2] = (char) 0xBD; return 3; } } /*---------------------------------------------------------------------------- * Json structure */ enum json_type { JSON_NULL, JSON_BOOL, JSON_NUMBER, JSON_STRING, JSON_ARRAY, JSON_OBJECT }; struct json { enum json_type type; char *key; union { bool boolean; double number; char *string; STAILQ_HEAD(jsons, json) childs; } value; STAILQ_ENTRY(json) next; struct json *parent; }; static struct json* json_new(enum json_type type) { struct json *json = xcalloc(1, sizeof(struct json)); json->type = type; json->key = NULL; json->parent = NULL; return json; } struct json * json_null(void) { return json_new(JSON_NULL); } struct json * json_bool(bool value) { struct json *json = json_new(JSON_BOOL); json->value.boolean = value; return json; } struct json * json_nbr(double value) { struct json *json = json_new(JSON_NUMBER); json->value.number = value; return json; } static struct json * json_str_raw(const char *value, bool alloc) { value = value ? value : ""; struct json *json = json_new(JSON_STRING); json->value.string = alloc ? xstrdup(value) : (char *) value; return json; } struct json * json_str(const char *value) { return json_str_raw(value, true); } static struct json * json_str_noalloc(const char *value) { return json_str_raw(value, false); } struct json * json_arr(void) { struct json *json = json_new(JSON_ARRAY); STAILQ_INIT(&json->value.childs); return json; } struct json * json_arr_add(struct json *array, struct json *child) { if (array == NULL) array = json_arr(); else assert(array->type == JSON_ARRAY); STAILQ_INSERT_TAIL(&array->value.childs, child, next); child->parent = array; return array; } struct json * json_arr_addv(struct json *array, ...) { va_list argp; va_start(argp, array); struct json *child; while ((child = va_arg(argp, struct json *))) array = json_arr_add(array, child); va_end(argp); return array; } struct json * json_obj(void) { struct json *json = json_new(JSON_OBJECT); STAILQ_INIT(&json->value.childs); return json; } struct json * json_obj_add_raw(struct json *object, const char *key, struct json *child, bool parse_key) { if (object == NULL) object = json_obj(); else assert(object->type == JSON_OBJECT); child->key = parse_key ? xstrdup(key) : (char *) key; assert(child->key != NULL); STAILQ_INSERT_TAIL(&object->value.childs, child, next); child->parent = object; return object; } struct json * json_obj_add(struct json *object, const char *key, struct json *child) { return json_obj_add_raw(object, key, child, true); } static struct json * json_obj_add_noalloc_key(struct json *object, const char *key, struct json *child) { return json_obj_add_raw(object, key, child, false); } struct json * json_obj_addv(struct json *object, ...) { va_list argp; va_start(argp, object); char *key; while ((key = va_arg(argp, char *))) { struct json *child = va_arg(argp, struct json *); object = json_obj_add(object, key, child); } va_end(argp); return object; } static size_t json_count_childs(struct json *json) { size_t count = 0; struct json *child; switch (json->type) { case JSON_NULL: case JSON_BOOL: case JSON_NUMBER: case JSON_STRING: break; case JSON_ARRAY: case JSON_OBJECT: STAILQ_FOREACH(child, &json->value.childs, next) count++; break; } return count; } size_t json_arr_size(struct json *array) { return json_count_childs(array); } size_t json_obj_size(struct json *object) { return json_count_childs(object); } void json_free(struct json *json) { if (json == NULL) return; struct json *child; switch (json->type) { case JSON_STRING: free(json->value.string); break; case JSON_ARRAY: case JSON_OBJECT: while (! STAILQ_EMPTY(&json->value.childs)) { child = STAILQ_FIRST(&json->value.childs); STAILQ_REMOVE_HEAD(&json->value.childs, next); json_free(child); } break; default: break; } free(json->key); free(json); } /*----------------------------------------------------------------------------- * json render / print / pretty print */ static void json_buffer(struct buffer *buffer, struct json *json) { struct json *child; bool first; switch (json->type) { case JSON_NULL: buffer_append_str(buffer, "null"); break; case JSON_BOOL: buffer_append_str(buffer, json->value.boolean ? "true" : "false"); break; case JSON_NUMBER: buffer_append_sprintf(buffer, "%.*g", DBL_DIG, json->value.number); break; case JSON_STRING: buffer_append_dbl_quote(buffer, json->value.string); break; case JSON_ARRAY: buffer_append_str(buffer, "["); first = true; STAILQ_FOREACH(child, &json->value.childs, next) { if (! first) buffer_append_str(buffer, ","); json_buffer(buffer, child); first = false; } buffer_append_str(buffer, "]"); break; case JSON_OBJECT: buffer_append_str(buffer, "{"); first = true; STAILQ_FOREACH(child, &json->value.childs, next) { if (! first) buffer_append_str(buffer, ","); buffer_append_dbl_quote(buffer, child->key); buffer_append_str(buffer, ":"); json_buffer(buffer, child); first = false; } buffer_append_str(buffer, "}"); break; } } char * json_render(struct json *json) { struct buffer buffer; buffer_init(&buffer); json_buffer(&buffer, json); return buffer.data; } void json_print(struct json *json) { char *str = json_render(json); if (str) puts(str); free(str); } static void json_buffer_pretty(struct buffer *buffer, struct json *json, int depth, bool indent_first) { struct json *child; bool first; if (indent_first) buffer_append_sprintf(buffer, "%*s", depth*2, ""); switch (json->type) { case JSON_NULL: buffer_append_str(buffer, "null"); break; case JSON_BOOL: buffer_append_str(buffer, json->value.boolean ? "true" : "false"); break; case JSON_NUMBER: buffer_append_sprintf(buffer, "%.*g", DBL_DIG, json->value.number); break; case JSON_STRING: buffer_append_dbl_quote(buffer, json->value.string); break; case JSON_ARRAY: buffer_append_str(buffer, "["); first = true; STAILQ_FOREACH(child, &json->value.childs, next) { if (first) buffer_append_str(buffer, "\n"); else buffer_append_str(buffer, ",\n"); json_buffer_pretty(buffer, child, depth + 1, true); first = false; } buffer_append_sprintf(buffer, "\n%*s]", depth*2, ""); break; case JSON_OBJECT: buffer_append_str(buffer, "{"); first = true; STAILQ_FOREACH(child, &json->value.childs, next) { if (first) buffer_append_str(buffer, "\n"); else buffer_append_str(buffer, ",\n"); buffer_append_sprintf(buffer, "%*s", (depth+1)*2, ""); buffer_append_dbl_quote(buffer, child->key); buffer_append_str(buffer, ": "); json_buffer_pretty(buffer, child, depth + 1, false); first = false; } buffer_append_sprintf(buffer, "\n%*s}", depth*2, ""); break; } } char * json_render_pretty(struct json *json) { struct buffer buffer; buffer_init(&buffer); json_buffer_pretty(&buffer, json, 0, false); return buffer.data; } void json_print_pretty(struct json *json) { char *str = json_render_pretty(json); if (str) puts(str); free(str); } /*----------------------------------------------------------------------------- * JSON getters */ bool json_is_null(struct json *json) { return json && json->type == JSON_NULL; } bool json_is_bool(struct json *json) { return json && json->type == JSON_BOOL; } bool json_is_nbr(struct json *json) { return json && json->type == JSON_NUMBER; } bool json_is_str(struct json *json) { return json && json->type == JSON_STRING; } bool json_is_arr(struct json *json) { return json && json->type == JSON_ARRAY; } bool json_is_obj(struct json *json) { return json && json->type == JSON_OBJECT; } bool json_get_value_bool(struct json *json) { assert(json->type == JSON_BOOL); return json->value.boolean; } bool json_get_value_bool_dflt(struct json *json, bool dflt) { return json_is_bool(json) ? json_get_value_bool(json) : dflt; } double json_get_value_nbr(struct json *json) { assert(json->type == JSON_NUMBER); return json->value.number; } double json_get_value_nbr_dflt(struct json *json, double dflt) { return json_is_nbr(json) ? json_get_value_nbr(json) : dflt; } char * json_get_value_str(struct json *json) { assert(json->type == JSON_STRING); return json->value.string; } char * json_get_value_str_dflt(struct json *json, char *dflt) { return json_is_str(json) ? json_get_value_str(json) : dflt; } struct json * json_arr_get(struct json *json, size_t index) { assert(json->type == JSON_ARRAY); struct json *child; size_t i = 0; STAILQ_FOREACH(child, &json->value.childs, next) { if (i == index) return child; i++; } return NULL; } struct json * json_obj_get(struct json *json, const char *key) { assert(json->type == JSON_OBJECT); struct json *child; STAILQ_FOREACH(child, &json->value.childs, next) { if (strcmp(child->key, key) == 0) return child; } return NULL; } static bool getv_arg_is_index(const char *str, size_t *index) { size_t str_len = strlen(str); if (str[0] == '\0' || str[0] != '[' || str[str_len - 1] != ']') return false; int nparsed; if (sscanf(str, "[%zu]%n", index, &nparsed) != 1) return false; if ((size_t) nparsed != str_len) return false; return true; } struct json * json_getv(struct json *json, ...) { va_list argp; va_start(argp, json); struct json *child = NULL; char *key = NULL; while ((key = va_arg(argp, char *)) != NULL) { size_t index; if (getv_arg_is_index(key, &index)) { if (json->type != JSON_ARRAY) goto notfound; child = json_arr_get(json, index); } else { if (json->type != JSON_OBJECT) goto notfound; child = json_obj_get(json, key); } if (child == NULL) goto notfound; else json = child; } va_end(argp); return child; notfound: va_end(argp); return NULL; } struct json * json_parent(struct json *json) { return json->parent; } /*----------------------------------------------------------------------------- * json iterator */ struct json_iter json_iter_init(struct json *json) { return (struct json_iter) { .count = 0, .parent = json, .current_child = NULL }; } struct json * json_iter_next(struct json_iter *iter) { if (iter->count == 0) { switch (iter->parent->type) { case JSON_NULL: case JSON_BOOL: case JSON_NUMBER: case JSON_STRING: iter->current_child = iter->parent; iter->count++; break; case JSON_ARRAY: case JSON_OBJECT: iter->current_child = STAILQ_FIRST(&iter->parent->value.childs); if (iter->current_child) iter->count++; break; } } else { switch (iter->parent->type) { case JSON_NULL: case JSON_BOOL: case JSON_NUMBER: case JSON_STRING: iter->current_child = NULL; break; case JSON_ARRAY: case JSON_OBJECT: iter->current_child = STAILQ_NEXT(iter->current_child, next); if (iter->current_child) iter->count++; break; } } return iter->current_child; } size_t json_iter_count(struct json_iter *iter) { return iter->count; } /*----------------------------------------------------------------------------- * json parsing */ enum json_errcode { JSON_ERR_NONE, JSON_ERR_PARSING, JSON_ERR_READ_FILE }; struct json_parser { enum json_errcode errcode; const char *pos; size_t line; size_t col; struct buffer buf; struct buffer errmsg; }; static void json_parser_reset(struct json_parser *parser) { parser->pos = NULL; parser->line = 1; parser->col = 1; parser->errcode = JSON_ERR_NONE; buffer_reset(&parser->buf); buffer_reset(&parser->errmsg); } struct json_parser * json_parser_new(void) { struct json_parser *parser = xcalloc(1, sizeof(struct json_parser)); buffer_init(&parser->buf); buffer_init(&parser->errmsg); json_parser_reset(parser); return parser; } char * json_parser_error_msg(struct json_parser *parser) { buffer_reset(&parser->errmsg); switch (parser->errcode) { case JSON_ERR_NONE: buffer_append_str(&parser->errmsg, "no error"); break; case JSON_ERR_PARSING: buffer_append_sprintf(&parser->errmsg, "parse error at line %ld column %ld", parser->line, parser->col); break; case JSON_ERR_READ_FILE: buffer_append_str(&parser->errmsg, "unable to read input file"); break; } return parser->errmsg.data; } void json_parser_free(struct json_parser *parser) { free(parser->buf.data); free(parser->errmsg.data); free(parser); } static void json_parser_skip(struct json_parser *parser, size_t count) { bool stop = false; while (! stop && count-- > 0) { switch (*parser->pos) { case '\n': parser->line++; parser->col = 1; parser->pos++; break; case '\0': stop = true; break; default: parser->col++; parser->pos++; break; } } } static void json_parser_skip_spaces(struct json_parser *parser) { while (*parser->pos == ' ' || *parser->pos == '\r' || *parser->pos == '\n' || *parser->pos == '\t') json_parser_skip(parser, 1); } static bool json_parser_consume(struct json_parser *parser, const char *prefix) { size_t prefix_len = strlen(prefix); if (strncmp(parser->pos, prefix, prefix_len) != 0) return false; json_parser_skip(parser, prefix_len); return true; } static struct json * json_parser_parse_null(struct json_parser *parser) { if (strncmp(parser->pos, "null", 4) == 0) { json_parser_skip(parser, 4); return json_null(); } else { return NULL; } } static struct json * json_parser_parse_bool(struct json_parser *parser) { if (strncmp(parser->pos, "true", 4) == 0) { json_parser_skip(parser, 4); return json_bool(true); } else if (strncmp(parser->pos, "false", 5) == 0) { json_parser_skip(parser, 5); return json_bool(false); } else { return NULL; } } static struct json * json_parser_parse_nbr(struct json_parser *parser) { int nread; double number; if (sscanf(parser->pos, "%lf%n", &number, &nread) != 1) return NULL; json_parser_skip(parser, nread); return json_nbr(number); } static char * json_parser_parse_string_key(struct json_parser *parser) { if (! json_parser_consume(parser, "\"")) return NULL; buffer_reset(&parser->buf); uint32_t hex_value; for (; *parser->pos != '\0' && *parser->pos != '"'; json_parser_skip(parser, 1)) { if (*parser->pos == '\\') { switch (*(parser->pos + 1)) { case '"': case '\\': case '/': buffer_append_char(&parser->buf, *(parser->pos + 1)); json_parser_skip(parser, 1); break; case 'b': buffer_append_char(&parser->buf, '\b'); json_parser_skip(parser, 1); break; case 'f': buffer_append_char(&parser->buf, '\f'); json_parser_skip(parser, 1); break; case 'n': buffer_append_char(&parser->buf, '\n'); json_parser_skip(parser, 1); break; case 'r': buffer_append_char(&parser->buf, '\r'); json_parser_skip(parser, 1); break; case 't': buffer_append_char(&parser->buf, '\t'); json_parser_skip(parser, 1); break; case 'u': if (! parse_hex(parser->pos + 2, &hex_value)) return NULL; json_parser_skip(parser, 5); char utf8[5] = {0}; utf8_encode(hex_value, utf8); buffer_append_str(&parser->buf, utf8); break; default: return NULL; } } else { buffer_append_char(&parser->buf, *parser->pos); } } if (*parser->pos == '"') { json_parser_skip(parser, 1); return xstrdup(parser->buf.data); } return NULL; } static struct json * json_parser_parse_string(struct json_parser *parser) { char *str = json_parser_parse_string_key(parser); if (str == NULL) return NULL; return json_str_noalloc(str); } static struct json *json_parser_parse_array(struct json_parser *parser); static struct json *json_parser_parse_object(struct json_parser *parser); static struct json * json_parser_parse_child(struct json_parser *parser) { struct json * (*parsers[]) (struct json_parser *) = { json_parser_parse_null, json_parser_parse_bool, json_parser_parse_nbr, json_parser_parse_string, json_parser_parse_array, json_parser_parse_object, NULL }; struct json *json = NULL; struct json * (**p) (struct json_parser *); for (p = parsers; *p != NULL; p++) { json = (*p)(parser); if (json) break; } return json; } static struct json * json_parser_parse_array(struct json_parser *parser) { if (! json_parser_consume(parser, "[")) return NULL; struct json *array = json_arr(); bool first = true; for (;;) { json_parser_skip_spaces(parser); if (*parser->pos == ']') { json_parser_skip(parser, 1); break; } if (! first && ! json_parser_consume(parser, ",")) goto error; json_parser_skip_spaces(parser); struct json *child = json_parser_parse_child(parser); if (child == NULL) goto error; json_arr_add(array, child); first = false; } return array; error: json_free(array); return NULL; } static struct json * json_parser_parse_object(struct json_parser *parser) { if (! json_parser_consume(parser, "{")) return NULL; struct json *object = json_obj(); bool first = true; for (;;) { json_parser_skip_spaces(parser); if (*parser->pos == '}') { json_parser_skip(parser, 1); break; } if (! first && ! json_parser_consume(parser, ",")) goto error; json_parser_skip_spaces(parser); char *key = json_parser_parse_string_key(parser); if (key == NULL) goto error; json_parser_skip_spaces(parser); if (! json_parser_consume(parser, ":")) { free(key); goto error; } json_parser_skip_spaces(parser); struct json *child = json_parser_parse_child(parser); if (child == NULL) { free(key); goto error; } json_obj_add_noalloc_key(object, key, child); first = false; } return object; error: json_free(object); return NULL; } struct json * json_parser_parse_str(struct json_parser *parser, const char *str) { parser->pos = str; parser->col = 1; parser->line = 1; json_parser_skip_spaces(parser); struct json *json = json_parser_parse_child(parser); if (json == NULL) { parser->errcode = JSON_ERR_PARSING; return NULL; } json_parser_skip_spaces(parser); if (*parser->pos != '\0') { parser->errcode = JSON_ERR_PARSING; json_free(json); return NULL; } return json; } struct json * json_parser_parse_file(struct json_parser *parser, const char *filename) { char *data = slurp_file(filename); if (data == NULL) { parser->errcode = JSON_ERR_READ_FILE; return NULL; } struct json *json = json_parser_parse_str(parser, data); free(data); return json; } struct json * json_parse_str(const char *str) { struct json_parser *p = json_parser_new(); struct json *json = json_parser_parse_str(p, str); json_parser_free(p); return json; } struct json * json_parse_file(const char *filename) { struct json_parser *p = json_parser_new(); struct json *json = json_parser_parse_file(p, filename); json_parser_free(p); return json; }