diff options
Diffstat (limited to 'lib/sg_json_builder.c')
-rw-r--r-- | lib/sg_json_builder.c | 999 |
1 files changed, 999 insertions, 0 deletions
diff --git a/lib/sg_json_builder.c b/lib/sg_json_builder.c new file mode 100644 index 00000000..ed2d1397 --- /dev/null +++ b/lib/sg_json_builder.c @@ -0,0 +1,999 @@ + +/* vim: set et ts=3 sw=3 sts=3 ft=c: + * + * Copyright (C) 2014 James McLaughlin. All rights reserved. + * https://github.com/udp/json-builder + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sg_json_builder.h" + +#include <string.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +/* This code was fetched from https://github.com/json-parser/json-builder + * and comes with the 2 clause BSD license (shown above) which is the same + * license that most of the rest of this package uses. */ + +#ifdef _MSC_VER + #define snprintf _snprintf +#endif + +static const json_serialize_opts default_opts = +{ + json_serialize_mode_single_line, + 0, + 3 /* indent_size */ +}; + +typedef struct json_builder_value +{ + json_value value; + + int is_builder_value; + + size_t additional_length_allocated; + size_t length_iterated; + +} json_builder_value; + +static int builderize (json_value * value) +{ + if (((json_builder_value *) value)->is_builder_value) + return 1; + + if (value->type == json_object) + { + unsigned int i; + + /* Values straight out of the parser have the names of object entries + * allocated in the same allocation as the values array itself. This is + * not desirable when manipulating values because the names would be easy + * to clobber. + */ + for (i = 0; i < value->u.object.length; ++ i) + { + json_char * name_copy; + json_object_entry * entry = &value->u.object.values [i]; + + if (! (name_copy = (json_char *) malloc ((entry->name_length + 1) * sizeof (json_char)))) + return 0; + + memcpy (name_copy, entry->name, entry->name_length + 1); + entry->name = name_copy; + } + } + + ((json_builder_value *) value)->is_builder_value = 1; + + return 1; +} + +const size_t json_builder_extra = sizeof(json_builder_value) - sizeof(json_value); + +/* These flags are set up from the opts before serializing to make the + * serializer conditions simpler. + */ +const int f_spaces_around_brackets = (1 << 0); +const int f_spaces_after_commas = (1 << 1); +const int f_spaces_after_colons = (1 << 2); +const int f_tabs = (1 << 3); + +static int get_serialize_flags (json_serialize_opts opts) +{ + int flags = 0; + + if (opts.mode == json_serialize_mode_packed) + return 0; + + if (opts.mode == json_serialize_mode_multiline) + { + if (opts.opts & json_serialize_opt_use_tabs) + flags |= f_tabs; + } + else + { + if (! (opts.opts & json_serialize_opt_pack_brackets)) + flags |= f_spaces_around_brackets; + + if (! (opts.opts & json_serialize_opt_no_space_after_comma)) + flags |= f_spaces_after_commas; + } + + if (! (opts.opts & json_serialize_opt_no_space_after_colon)) + flags |= f_spaces_after_colons; + + return flags; +} + +json_value * json_array_new (size_t length) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_array; + + if (! (value->u.array.values = (json_value **) malloc (length * sizeof (json_value *)))) + { + free (value); + return NULL; + } + + ((json_builder_value *) value)->additional_length_allocated = length; + + return value; +} + +json_value * json_array_push (json_value * array, json_value * value) +{ + assert (array->type == json_array); + + if (!builderize (array) || !builderize (value)) + return NULL; + + if (((json_builder_value *) array)->additional_length_allocated > 0) + { + -- ((json_builder_value *) array)->additional_length_allocated; + } + else + { + json_value ** values_new = (json_value **) realloc + (array->u.array.values, sizeof (json_value *) * (array->u.array.length + 1)); + + if (!values_new) + return NULL; + + array->u.array.values = values_new; + } + + array->u.array.values [array->u.array.length] = value; + ++ array->u.array.length; + + value->parent = array; + + return value; +} + +json_value * json_object_new (size_t length) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_object; + + if (! (value->u.object.values = (json_object_entry *) calloc + (length, sizeof (*value->u.object.values)))) + { + free (value); + return NULL; + } + + ((json_builder_value *) value)->additional_length_allocated = length; + + return value; +} + +json_value * json_object_push (json_value * object, + const json_char * name, + json_value * value) +{ + return json_object_push_length (object, strlen (name), name, value); +} + +json_value * json_object_push_length (json_value * object, + unsigned int name_length, const json_char * name, + json_value * value) +{ + json_char * name_copy; + + assert (object->type == json_object); + + if (! (name_copy = (json_char *) malloc ((name_length + 1) * sizeof (json_char)))) + return NULL; + + memcpy (name_copy, name, name_length * sizeof (json_char)); + name_copy [name_length] = 0; + + if (!json_object_push_nocopy (object, name_length, name_copy, value)) + { + free (name_copy); + return NULL; + } + + return value; +} + +json_value * json_object_push_nocopy (json_value * object, + unsigned int name_length, json_char * name, + json_value * value) +{ + json_object_entry * entry; + + assert (object->type == json_object); + + if (!builderize (object) || !builderize (value)) + return NULL; + + if (((json_builder_value *) object)->additional_length_allocated > 0) + { + -- ((json_builder_value *) object)->additional_length_allocated; + } + else + { + json_object_entry * values_new = (json_object_entry *) + realloc (object->u.object.values, sizeof (*object->u.object.values) + * (object->u.object.length + 1)); + + if (!values_new) + return NULL; + + object->u.object.values = values_new; + } + + entry = object->u.object.values + object->u.object.length; + + entry->name_length = name_length; + entry->name = name; + entry->value = value; + + ++ object->u.object.length; + + value->parent = object; + + return value; +} + +json_value * json_string_new (const json_char * buf) +{ + return json_string_new_length (strlen (buf), buf); +} + +json_value * json_string_new_length (unsigned int length, const json_char * buf) +{ + json_value * value; + json_char * copy = (json_char *) malloc ((length + 1) * sizeof (json_char)); + + if (!copy) + return NULL; + + memcpy (copy, buf, length * sizeof (json_char)); + copy [length] = 0; + + if (! (value = json_string_new_nocopy (length, copy))) + { + free (copy); + return NULL; + } + + return value; +} + +json_value * json_string_new_nocopy (unsigned int length, json_char * buf) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_string; + value->u.string.length = length; + value->u.string.ptr = buf; + + return value; +} + +json_value * json_integer_new (json_int_t integer) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_integer; + value->u.integer = integer; + + return value; +} + +json_value * json_double_new (double dbl) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_double; + value->u.dbl = dbl; + + return value; +} + +json_value * json_boolean_new (int b) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_boolean; + value->u.boolean = b; + + return value; +} + +json_value * json_null_new (void) +{ + json_value * value = (json_value *) calloc (1, sizeof (json_builder_value)); + + if (!value) + return NULL; + + ((json_builder_value *) value)->is_builder_value = 1; + + value->type = json_null; + + return value; +} + +void json_object_sort (json_value * object, json_value * proto) +{ + unsigned int i, out_index = 0; + + if (!builderize (object)) + return; /* TODO error */ + + assert (object->type == json_object); + assert (proto->type == json_object); + + for (i = 0; i < proto->u.object.length; ++ i) + { + unsigned int j; + json_object_entry proto_entry = proto->u.object.values [i]; + + for (j = 0; j < object->u.object.length; ++ j) + { + json_object_entry entry = object->u.object.values [j]; + + if (entry.name_length != proto_entry.name_length) + continue; + + if (memcmp (entry.name, proto_entry.name, entry.name_length) != 0) + continue; + + object->u.object.values [j] = object->u.object.values [out_index]; + object->u.object.values [out_index] = entry; + + ++ out_index; + } + } +} + +json_value * json_object_merge (json_value * objectA, json_value * objectB) +{ + unsigned int i; + + assert (objectA->type == json_object); + assert (objectB->type == json_object); + assert (objectA != objectB); + + if (!builderize (objectA) || !builderize (objectB)) + return NULL; + + if (objectB->u.object.length <= + ((json_builder_value *) objectA)->additional_length_allocated) + { + ((json_builder_value *) objectA)->additional_length_allocated + -= objectB->u.object.length; + } + else + { + json_object_entry * values_new; + + unsigned int alloc = + objectA->u.object.length + + ((json_builder_value *) objectA)->additional_length_allocated + + objectB->u.object.length; + + if (! (values_new = (json_object_entry *) + realloc (objectA->u.object.values, sizeof (json_object_entry) * alloc))) + { + return NULL; + } + + objectA->u.object.values = values_new; + } + + for (i = 0; i < objectB->u.object.length; ++ i) + { + json_object_entry * entry = &objectA->u.object.values[objectA->u.object.length + i]; + + *entry = objectB->u.object.values[i]; + entry->value->parent = objectA; + } + + objectA->u.object.length += objectB->u.object.length; + + free (objectB->u.object.values); + free (objectB); + + return objectA; +} + +static size_t measure_string (unsigned int length, + const json_char * str) +{ + unsigned int i; + size_t measured_length = 0; + + for(i = 0; i < length; ++ i) + { + json_char c = str [i]; + + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + + measured_length += 2; + break; + + default: + + ++ measured_length; + break; + }; + }; + + return measured_length; +} + +#define PRINT_ESCAPED(c) do { \ + *buf ++ = '\\'; \ + *buf ++ = (c); \ +} while(0); \ + +static size_t serialize_string (json_char * buf, + unsigned int length, + const json_char * str) +{ + json_char * orig_buf = buf; + unsigned int i; + + for(i = 0; i < length; ++ i) + { + json_char c = str [i]; + + switch (c) + { + case '"': PRINT_ESCAPED ('\"'); continue; + case '\\': PRINT_ESCAPED ('\\'); continue; + case '\b': PRINT_ESCAPED ('b'); continue; + case '\f': PRINT_ESCAPED ('f'); continue; + case '\n': PRINT_ESCAPED ('n'); continue; + case '\r': PRINT_ESCAPED ('r'); continue; + case '\t': PRINT_ESCAPED ('t'); continue; + + default: + + *buf ++ = c; + break; + }; + }; + + return buf - orig_buf; +} + +size_t json_measure (json_value * value) +{ + return json_measure_ex (value, default_opts); +} + +#define MEASURE_NEWLINE() do { \ + ++ newlines; \ + indents += depth; \ +} while(0); \ + +size_t json_measure_ex (json_value * value, json_serialize_opts opts) +{ + size_t total = 1; /* null terminator */ + size_t newlines = 0; + size_t depth = 0; + size_t indents = 0; + int flags; + int bracket_size, comma_size, colon_size; + + flags = get_serialize_flags (opts); + + /* to reduce branching + */ + bracket_size = flags & f_spaces_around_brackets ? 2 : 1; + comma_size = flags & f_spaces_after_commas ? 2 : 1; + colon_size = flags & f_spaces_after_colons ? 2 : 1; + + while (value) + { + json_int_t integer; + json_object_entry * entry; + + switch (value->type) + { + case json_array: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.array.length == 0) + { + total += 2; /* `[]` */ + break; + } + + total += bracket_size; /* `[` */ + + ++ depth; + MEASURE_NEWLINE(); /* \n after [ */ + } + + if (((json_builder_value *) value)->length_iterated == value->u.array.length) + { + -- depth; + MEASURE_NEWLINE(); + total += bracket_size; /* `]` */ + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + total += comma_size; /* `, ` */ + + MEASURE_NEWLINE(); + } + + ((json_builder_value *) value)->length_iterated++; + value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1]; + continue; + + case json_object: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.object.length == 0) + { + total += 2; /* `{}` */ + break; + } + + total += bracket_size; /* `{` */ + + ++ depth; + MEASURE_NEWLINE(); /* \n after { */ + } + + if (((json_builder_value *) value)->length_iterated == value->u.object.length) + { + -- depth; + MEASURE_NEWLINE(); + total += bracket_size; /* `}` */ + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + total += comma_size; /* `, ` */ + MEASURE_NEWLINE(); + } + + entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++); + + total += 2 + colon_size; /* `"": ` */ + total += measure_string (entry->name_length, entry->name); + + value = entry->value; + continue; + + case json_string: + + total += 2; /* `""` */ + total += measure_string (value->u.string.length, value->u.string.ptr); + break; + + case json_integer: + + integer = value->u.integer; + + if (integer < 0) + { + total += 1; /* `-` */ + integer = - integer; + } + + ++ total; /* first digit */ + + while (integer >= 10) + { + ++ total; /* another digit */ + integer /= 10; + } + + break; + + case json_double: + + total += snprintf (NULL, 0, "%g", value->u.dbl); + + /* Because sometimes we need to add ".0" if sprintf does not do it + * for us. Downside is that we allocate more bytes than strictly + * needed for serialization. + */ + total += 2; + + break; + + case json_boolean: + + total += value->u.boolean ? + 4: /* `true` */ + 5; /* `false` */ + + break; + + case json_null: + + total += 4; /* `null` */ + break; + + default: + break; + }; + + value = value->parent; + } + + if (opts.mode == json_serialize_mode_multiline) + { + total += newlines * (((opts.opts & json_serialize_opt_CRLF) ? 2 : 1) + opts.indent_size); + total += indents * opts.indent_size; + } + + return total; +} + +void json_serialize (json_char * buf, json_value * value) +{ + json_serialize_ex (buf, value, default_opts); +} + +#define PRINT_NEWLINE() do { \ + if (opts.mode == json_serialize_mode_multiline) { \ + if (opts.opts & json_serialize_opt_CRLF) \ + *buf ++ = '\r'; \ + *buf ++ = '\n'; \ + for(i = 0; i < indent; ++ i) \ + *buf ++ = indent_char; \ + } \ +} while(0); \ + +#define PRINT_OPENING_BRACKET(c) do { \ + *buf ++ = (c); \ + if (flags & f_spaces_around_brackets) \ + *buf ++ = ' '; \ +} while(0); \ + +#define PRINT_CLOSING_BRACKET(c) do { \ + if (flags & f_spaces_around_brackets) \ + *buf ++ = ' '; \ + *buf ++ = (c); \ +} while(0); \ + +void json_serialize_ex (json_char * buf, json_value * value, json_serialize_opts opts) +{ + json_int_t integer, orig_integer; + json_object_entry * entry; + json_char * ptr, * dot; + int indent = 0; + char indent_char; + int i; + int flags; + + flags = get_serialize_flags (opts); + + indent_char = flags & f_tabs ? '\t' : ' '; + + while (value) + { + switch (value->type) + { + case json_array: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.array.length == 0) + { + *buf ++ = '['; + *buf ++ = ']'; + + break; + } + + PRINT_OPENING_BRACKET ('['); + + indent += opts.indent_size; + PRINT_NEWLINE(); + } + + if (((json_builder_value *) value)->length_iterated == value->u.array.length) + { + indent -= opts.indent_size; + PRINT_NEWLINE(); + PRINT_CLOSING_BRACKET (']'); + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + *buf ++ = ','; + + if (flags & f_spaces_after_commas) + *buf ++ = ' '; + + PRINT_NEWLINE(); + } + + ((json_builder_value *) value)->length_iterated++; + value = value->u.array.values [((json_builder_value *) value)->length_iterated - 1]; + continue; + + case json_object: + + if (((json_builder_value *) value)->length_iterated == 0) + { + if (value->u.object.length == 0) + { + *buf ++ = '{'; + *buf ++ = '}'; + + break; + } + + PRINT_OPENING_BRACKET ('{'); + + indent += opts.indent_size; + PRINT_NEWLINE(); + } + + if (((json_builder_value *) value)->length_iterated == value->u.object.length) + { + indent -= opts.indent_size; + PRINT_NEWLINE(); + PRINT_CLOSING_BRACKET ('}'); + + ((json_builder_value *) value)->length_iterated = 0; + break; + } + + if (((json_builder_value *) value)->length_iterated > 0) + { + *buf ++ = ','; + + if (flags & f_spaces_after_commas) + *buf ++ = ' '; + + PRINT_NEWLINE(); + } + + entry = value->u.object.values + (((json_builder_value *) value)->length_iterated ++); + + *buf ++ = '\"'; + buf += serialize_string (buf, entry->name_length, entry->name); + *buf ++ = '\"'; + *buf ++ = ':'; + + if (flags & f_spaces_after_colons) + *buf ++ = ' '; + + value = entry->value; + continue; + + case json_string: + + *buf ++ = '\"'; + buf += serialize_string (buf, value->u.string.length, value->u.string.ptr); + *buf ++ = '\"'; + break; + + case json_integer: + + integer = value->u.integer; + + if (integer < 0) + { + *buf ++ = '-'; + integer = - integer; + } + + orig_integer = integer; + + ++ buf; + + while (integer >= 10) + { + ++ buf; + integer /= 10; + } + + integer = orig_integer; + ptr = buf; + + do + { + *-- ptr = "0123456789"[integer % 10]; + + } while ((integer /= 10) > 0); + + break; + + case json_double: + + ptr = buf; + + buf += sprintf (buf, "%g", value->u.dbl); + + if ((dot = strchr (ptr, ','))) + { + *dot = '.'; + } + else if (!strchr (ptr, '.') && !strchr (ptr, 'e')) + { + *buf ++ = '.'; + *buf ++ = '0'; + } + + break; + + case json_boolean: + + if (value->u.boolean) + { + memcpy (buf, "true", 4); + buf += 4; + } + else + { + memcpy (buf, "false", 5); + buf += 5; + } + + break; + + case json_null: + + memcpy (buf, "null", 4); + buf += 4; + break; + + default: + break; + }; + + value = value->parent; + } + + *buf = 0; +} + +void json_builder_free (json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + free (value->u.array.values); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + free (value->u.object.values); + break; + } + + -- value->u.object.length; + + if (((json_builder_value *) value)->is_builder_value) + { + /* Names are allocated separately for builder values. In parser + * values, they are part of the same allocation as the values array + * itself. + */ + free (value->u.object.values [value->u.object.length].name); + } + + value = value->u.object.values [value->u.object.length].value; + continue; + + case json_string: + + free (value->u.string.ptr); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + free (cur_value); + } +} + + + + + + |