// Own header #include "json_utils.h" // Project headers #include "json.h" #include "string-buffer/string_buffer.h" // Standard headers #include #include #include #include static void json_write_internal(StrBuf *str_buf, json_value *json); static void write_object(StrBuf *str_buf, json_value *json) { strbuf_append_cstr(str_buf, "{ "); for (unsigned i = 0; i < json->u.object.length; i++) { strbuf_append_cstr(str_buf, "\""); strbuf_append_cstr(str_buf, json->u.object.values[i].name); strbuf_append_cstr(str_buf, "\": "); json_write_internal(str_buf, json->u.object.values[i].value); if (i < (json->u.object.length - 1)) strbuf_append_cstr(str_buf, ", "); else strbuf_append_cstr(str_buf, " "); } strbuf_append_cstr(str_buf, "}"); } static void write_array(StrBuf *str_buf, json_value *json) { strbuf_append_cstr(str_buf, "[ "); for (unsigned i = 0; i < json->u.array.length; i++) { json_write_internal(str_buf, json->u.array.values[i]); if (i < (json->u.array.length - 1)) strbuf_append_cstr(str_buf, ", "); else strbuf_append_cstr(str_buf, " "); } strbuf_append_cstr(str_buf, "]"); } static void write_integer(StrBuf *str_buf, json_value *json) { char buf[22]; snprintf(buf, sizeof(buf), "%" PRId64, json->u.integer); strbuf_append_cstr(str_buf, buf); } static void write_double(StrBuf *str_buf, json_value *json) { char buf[22]; snprintf(buf, sizeof(buf), "%.6f", json->u.dbl); strbuf_append_cstr(str_buf, buf); } static void write_string(StrBuf *str_buf, json_value *json) { strbuf_append_cstr(str_buf, "\""); strbuf_append_cstr(str_buf, json->u.string.ptr); strbuf_append_cstr(str_buf, "\""); } static void write_boolean(StrBuf *str_buf, json_value *json) { if (json->u.boolean) strbuf_append_cstr(str_buf, "true"); else strbuf_append_cstr(str_buf, "false"); } static void json_write_internal(StrBuf *str_buf, json_value *json) { switch (json->type) { case json_object: write_object(str_buf, json); break; case json_array: write_array(str_buf, json); break; case json_integer: write_integer(str_buf, json); break; case json_double: write_double(str_buf, json); break; case json_string: write_string(str_buf, json); break; case json_boolean: write_boolean(str_buf, json); break; break; default: break; } } char* json_value_2_string(json_value* json) { StrBuf *str_buf = strbuf_alloc(); json_write_internal(str_buf, json); char *rv = strbuf_to_cstr(str_buf); strbuf_free(str_buf); return rv; } static bool does_name_start_path(const char *name, const char *path) { while (*name && *path) { if (*name != *path) return false; name++; path++; } if (*name == '\0' && (*path == '\0' || *path == '/')) return true; return false; } static const char *get_next_part_of_path(const char *path) { const char *rv = strchr(path, '/'); if (!rv) return NULL; return rv + 1; } // Walks the tree looking for the json_value at the specified path. // Cases: // 1. value isn't an object. Return NULL. // 2. path is foo and not present in value. Return NULL but set parent. // 3. path is foo and is present in value. Return value and set parent. // 4. path is foo/bar and foo is not present in value. Return NULL. static json_value *find_node(json_value *value, const char *path, json_value **parent) { while (value) { if (value->type != json_object) return NULL; json_value *child_value = NULL; for (unsigned i = 0; i < value->u.object.length; i++) if (does_name_start_path(value->u.object.values[i].name, path)) child_value = value->u.object.values[i].value; const char *next_part_of_path = get_next_part_of_path(path); if (!next_part_of_path) { *parent = value; return child_value; } path = next_part_of_path; value = child_value; } return NULL; } static json_value* add_item_to_object(json_value *parent, const char *item_name) { // The memory allocation scheme of json_parse_ex is "optimized" so that // the object_entries array and the entry names are allocated in the // same block. As a result, the free we do below will free the object entry // names. So, we need to allocate a new single block of memory // big enough for: // * Our new array of object entries (one bigger than the current // array). // * Plus, all the name strings referenced by all the existing object // entries. // * Plus, our new object entry name. // Count the length of all the names size_t total_names_length = 0; for (unsigned i = 0; i < parent->u.object.length; i++) total_names_length += strlen(parent->u.object.values[i].name) + 1; size_t old_names_length = total_names_length; total_names_length += strlen(item_name) + 1; // Now we need to allocate a block of memory, where different parts of it // have different types. size_t num_bytes_needed_by_new_object_entries_array = sizeof(json_object_entry) * (parent->u.object.length + 1); void *stupid_polymorphic_block = malloc(total_names_length + num_bytes_needed_by_new_object_entries_array); // Copy in the old array contents json_object_entry *new_object_entries = stupid_polymorphic_block; memcpy(new_object_entries, parent->u.object.values, sizeof(json_object_entry) * parent->u.object.length); // Copy in the object entry names char *old_names = (char*)(parent->u.object.values + parent->u.object.length); char *new_names = (char*)stupid_polymorphic_block + num_bytes_needed_by_new_object_entries_array; memcpy(new_names, old_names, old_names_length); // Append the new name strcpy(new_names + old_names_length, item_name); // Patch up all the object entry name pointers to point into the new block char *current_string = new_names; for (unsigned i = 0; i < parent->u.object.length + 1; i++) { new_object_entries[i].name = current_string; current_string += strlen(current_string) + 1; } free(parent->u.object.values); parent->u.object.values = new_object_entries; json_object_entry *new_entry = new_object_entries + parent->u.object.length; new_entry->value = malloc(sizeof(json_value)); new_entry->value->parent = parent; new_entry->value->type = json_null; parent->u.object.length++; return new_entry->value; } static json_value* add_or_get_leaf(json_value* root, const char* path) { json_value *parent = NULL; json_value *node = find_node(root, path, &parent); if (!node && !parent) return NULL; if (!node) { if (parent->type != json_object) return NULL; // OK, item doesn't exist, but parent is an object, so we can add it. const char *item_name = get_next_part_of_path(path); if (!item_name) item_name = path; node = add_item_to_object(parent, item_name); } else { // If the node is not a leaf node, then things get too complex for the current implementation if (node->type != json_boolean && node->type != json_integer && node->type != json_double && node->type != json_string) return NULL; // OK, node exists and is a leaf. This is easy! } return node; } bool json_set_string(json_value* dst, const char* path, const char* val) { json_value* leaf = add_or_get_leaf(dst, path); if (leaf) { if (leaf->type == json_string) free(leaf->u.string.ptr); leaf->type = json_string; leaf->u.string.ptr = strdup(val); leaf->u.string.length = strlen(val); return true; } return false; } bool json_set_int(json_value* dst, const char* path, int64_t val) { json_value* leaf = add_or_get_leaf(dst, path); if (leaf) { if (leaf->type == json_string) free(leaf->u.string.ptr); leaf->type = json_integer; leaf->u.integer = val; return true; } return false; } bool json_set_double(json_value* dst, const char* path, double val) { json_value* leaf = add_or_get_leaf(dst, path); if (leaf) { if (leaf->type == json_string) free(leaf->u.string.ptr); leaf->type = json_double; leaf->u.dbl = val; return true; } return false; } bool json_set_bool(json_value* dst, const char* path, bool val) { json_value* leaf = add_or_get_leaf(dst, path); if (leaf) { if (leaf->type == json_string) free(leaf->u.string.ptr); leaf->type = json_boolean; leaf->u.boolean = val; return true; } return false; } bool json_set_json_value(json_value* dst, const char* path, json_value* src) { json_value *parent = NULL; json_value *node_to_free = find_node(dst, path, &parent); if (!parent) return false; json_value *src_deep_copy = json_deep_copy(src); json_value_free(node_to_free); const char *last_part_of_path = strrchr(path, '/') + 1; // Find the index of the pointer to the json_value that we need to replace. // Todo - maybe this loop could be moved out into a separate function // and that function could be called by find_node() for (unsigned i = 0; i < parent->u.object.length; i++) { json_object_entry *joe = parent->u.object.values + i; if (strcmp(joe->name, last_part_of_path) == 0) { joe->value = src_deep_copy; src_deep_copy->parent = parent; return true; } } // There is no json_object_entry with name == last_part_of_path, so we need // to add one. dst = add_item_to_object(parent, last_part_of_path); parent->u.object.values[parent->u.object.length - 1].value = src_deep_copy; return true; } bool json_set_list(json_value* dst, const char* path, json_value* src) { (void)dst; (void)path; (void)src; return true; } static void copy_object(json_value *src, json_value *dst) { dst->u.object.length = src->u.object.length; // Count the length of all the names size_t total_names_length = 0; for (unsigned i = 0; i < src->u.object.length; i++) total_names_length += strlen(src->u.object.values[i].name) + 1; size_t num_bytes_needed_by_new_object_entries_array = sizeof(json_object_entry) * src->u.object.length; void *stupid_polymorphic_block = malloc(total_names_length + num_bytes_needed_by_new_object_entries_array); // Copy in the old array contents json_object_entry *new_object_entries = stupid_polymorphic_block; dst->u.object.values = new_object_entries; memcpy(new_object_entries, src->u.object.values, sizeof(json_object_entry) * src->u.object.length); // Copy in the object entry names char *old_names = (char*)(src->u.object.values + src->u.object.length); char *new_names = (char*)stupid_polymorphic_block + num_bytes_needed_by_new_object_entries_array; memcpy(new_names, old_names, total_names_length); // Patch up all the object entry name pointers to point into the new block char *current_string = new_names; for (unsigned i = 0; i < src->u.object.length; i++) { new_object_entries[i].name = current_string; current_string += strlen(current_string) + 1; } // Recursively copy all the child values for (unsigned i = 0; i < src->u.object.length; i++) { new_object_entries[i].value = json_deep_copy(src->u.object.values[i].value); new_object_entries[i].value->parent = dst; } } json_value *json_deep_copy(json_value* src) { json_value *dst = calloc(1, sizeof(json_value)); dst->type = src->type; switch (src->type) { case json_object: copy_object(src, dst); break; case json_array: dst->u.array.length = src->u.array.length; dst->u.array.values = calloc(dst->u.array.length, sizeof(json_value*)); // Recursively copy all the child values for (unsigned i = 0; i < src->u.array.length; i++) { dst->u.array.values[i] = json_deep_copy(src->u.array.values[i]); dst->u.array.values[i]->parent = dst; } break; case json_integer: dst->u.integer = src->u.integer; break; case json_double: dst->u.dbl = src->u.dbl; break; case json_string: dst->u.string.ptr = strdup(src->u.string.ptr); dst->u.string.length = src->u.string.length; break; case json_boolean: dst->u.boolean = src->u.boolean; break; default: assert(0); // Should never happen break; } return dst; } bool json_cmp(json_value* v1, json_value* v2) { if (v1->type != v2->type) return false; switch (v1->type) { case json_object: if (v1->u.object.length != v2->u.object.length) return false; for (unsigned i = 0; i < v1->u.object.length; i++) { json_object_entry *e1 = v1->u.object.values + i; json_object_entry *e2 = v2->u.object.values + i; if (strcmp(e1->name, e2->name) != 0) return false; if (!json_cmp(e1->value, e2->value)) return false; } break; case json_array: if (v1->u.array.length != v2->u.array.length) return false; for (unsigned i = 0; i < v1->u.array.length; i++) { if (!json_cmp(v1->u.array.values[i], v2->u.array.values[i])) return false; } break; case json_integer: return v1->u.integer == v2->u.integer; case json_double: return fabs(v1->u.dbl - v2->u.dbl) < 0.0001f; case json_string: return strcmp(v1->u.string.ptr, v2->u.string.ptr) == 0; case json_boolean: return v1->u.boolean == v2->u.boolean; default: assert(0); // Should never happen break; } return true; } json_value* json_get(json_value* src, const char* path) { json_value *parent; return find_node(src, path, &parent); } char* json_get_string(json_value* src, const char* path) { json_value* v = json_get(src, path); if (v && v->type == json_string) return v->u.string.ptr; return NULL; } bool json_get_int(json_value* src, const char* path, int64_t *val) { json_value* v = json_get(src, path); if (v && v->type == json_integer) { *val = v->u.integer; return true; } return false; } bool json_get_double(json_value* src, const char* path, double *val) { json_value* v = json_get(src, path); if (v && v->type == json_double) { *val = v->u.dbl; return true; } return false; } bool json_get_bool(json_value* src, const char* path, bool *val) { json_value* v = json_get(src, path); if (v && v->type == json_boolean) { *val = (bool)v->u.boolean; return true; } return false; } json_value* json_get_deep(json_value* src, const char* path) { (void)src; (void)path; return NULL; } void json_value_print(json_value* val) { (void)val; } // Helper function. Returns the number of keys in a json_object (which is synonymous with a dictionary) // Returns <0 if object passed in is not a json_object int json_get_num_keys(json_value* src) { if (src && src->type == json_object) { return src->u.object.length; } return -1; } // Helper function. Returns the Nth key in a json_object (which is synonymous with a dictionary) // Returns NULL if object passed in is not a json_object, or if 'n' is > than the number of keys char* json_get_key(json_value* src, unsigned int n) { if (src && src->type == json_object && (n < src->u.object.length)) { return src->u.object.values[n].name; } return NULL; }