diff --git a/json_object.c b/json_object.c index 8e52fc1..750f76e 100644 --- a/json_object.c +++ b/json_object.c @@ -196,8 +196,6 @@ int fjson_object_put(struct fjson_object *jso) const int cnt = ATOMIC_DEC_AND_FETCH(&jso->_ref_count, &jso->_mut_ref_count); if(cnt > 0) return 0; - if (jso->_user_delete) - jso->_user_delete(jso, jso->_userdata); jso->_delete(jso); return 1; } @@ -243,61 +241,6 @@ enum fjson_type fjson_object_get_type(struct fjson_object *jso) return jso->o_type; } -/* set a custom conversion to string */ - -void fjson_object_set_serializer(fjson_object *jso, - fjson_object_to_json_string_fn to_string_func, - void *userdata, - fjson_object_delete_fn *user_delete) -{ - // First, clean up any previously existing user info - if (jso->_user_delete) - { - jso->_user_delete(jso, jso->_userdata); - } - jso->_userdata = NULL; - jso->_user_delete = NULL; - - if (to_string_func == NULL) - { - // Reset to the standard serialization function - switch(jso->o_type) - { - case fjson_type_null: - jso->_to_json_string = NULL; - break; - case fjson_type_boolean: - jso->_to_json_string = &fjson_object_boolean_to_json_string; - break; - case fjson_type_double: - jso->_to_json_string = &fjson_object_double_to_json_string; - break; - case fjson_type_int: - jso->_to_json_string = &fjson_object_int_to_json_string; - break; - case fjson_type_object: - jso->_to_json_string = &fjson_object_object_to_json_string; - break; - case fjson_type_array: - jso->_to_json_string = &fjson_object_array_to_json_string; - break; - case fjson_type_string: - jso->_to_json_string = &fjson_object_string_to_json_string; - break; - default: - /* this should NOT HAPPEN! */ - jso->_to_json_string = NULL; - break; - } - return; - } - - jso->_to_json_string = to_string_func; - jso->_userdata = userdata; - jso->_user_delete = user_delete; -} - - /* extended conversion to string */ const char* fjson_object_to_json_string_ext(struct fjson_object *jso, int flags) @@ -341,8 +284,8 @@ static void indent(struct printbuf *pb, int level, int flags) /* fjson_object_object */ static int fjson_object_object_to_json_string(struct fjson_object* jso, - struct printbuf *pb, - int level, + struct printbuf *pb, + int level, int flags) { struct fjson_object *val; @@ -581,8 +524,8 @@ void fjson_object_object_del(struct fjson_object* jso, const char *key) /* fjson_object_boolean */ static int fjson_object_boolean_to_json_string(struct fjson_object* jso, - struct printbuf *pb, - int __attribute__((unused)) level, + struct printbuf *pb, + int __attribute__((unused)) level, int __attribute__((unused)) flags) { if (jso->o.c_boolean) @@ -613,7 +556,7 @@ fjson_bool fjson_object_get_boolean(struct fjson_object *jso) case fjson_type_int: return (jso->o.c_int64 != 0); case fjson_type_double: - return (jso->o.c_double != 0); + return (jso->o.c_double.value != 0); case fjson_type_string: return (jso->o.c_string.len != 0); case fjson_type_null: @@ -676,7 +619,7 @@ int32_t fjson_object_get_int(struct fjson_object *jso) else return (int32_t)cint64; case fjson_type_double: - return (int32_t)jso->o.c_double; + return (int32_t)jso->o.c_double.value; case fjson_type_boolean: return jso->o.c_boolean; case fjson_type_null: @@ -709,7 +652,7 @@ int64_t fjson_object_get_int64(struct fjson_object *jso) case fjson_type_int: return jso->o.c_int64; case fjson_type_double: - return (int64_t)jso->o.c_double; + return (int64_t)jso->o.c_double.value; case fjson_type_boolean: return jso->o.c_boolean; case fjson_type_string: @@ -727,29 +670,35 @@ int64_t fjson_object_get_int64(struct fjson_object *jso) /* fjson_object_double */ static int fjson_object_double_to_json_string(struct fjson_object* jso, - struct printbuf *pb, - int __attribute__((unused)) level, + struct printbuf *pb, + int __attribute__((unused)) level, int __attribute__((unused)) flags) { char buf[128], *p, *q; int size; double dummy; /* needed for modf() */ + + if (jso->o.c_double.source) { + printbuf_memappend_no_nul(pb, jso->o.c_double.source, strlen(jso->o.c_double.source)); + return 0; /* we need to keep compatible with the API */ + } + /* Although JSON RFC does not support * NaN or Infinity as numeric values * ECMA 262 section 9.8.1 defines * how to handle these cases as strings */ - if(isnan(jso->o.c_double)) + if(isnan(jso->o.c_double.value)) size = snprintf(buf, sizeof(buf), "NaN"); - else if(isinf(jso->o.c_double)) - if(jso->o.c_double > 0) + else if(isinf(jso->o.c_double.value)) + if(jso->o.c_double.value > 0) size = snprintf(buf, sizeof(buf), "Infinity"); else size = snprintf(buf, sizeof(buf), "-Infinity"); else size = snprintf(buf, sizeof(buf), - (modf(jso->o.c_double, &dummy)==0)?"%.17g.0":"%.17g", - jso->o.c_double); + (modf(jso->o.c_double.value, &dummy)==0)?"%.17g.0":"%.17g", + jso->o.c_double.value); p = strchr(buf, ','); if (p) { @@ -771,13 +720,20 @@ static int fjson_object_double_to_json_string(struct fjson_object* jso, return 0; /* we need to keep compatible with the API */ } +static void fjson_object_double_delete(struct fjson_object *jso) +{ + free(jso->o.c_double.source); + fjson_object_generic_delete(jso); +} + struct fjson_object* fjson_object_new_double(double d) { struct fjson_object *jso = fjson_object_new(fjson_type_double); if (!jso) return NULL; jso->_to_json_string = &fjson_object_double_to_json_string; - jso->o.c_double = d; + jso->o.c_double.value = d; + jso->o.c_double.source = NULL; return jso; } @@ -787,31 +743,17 @@ struct fjson_object* fjson_object_new_double_s(double d, const char *ds) if (!jso) return NULL; - char *new_ds = strdup(ds); - if (!new_ds) + jso->o.c_double.source = strdup(ds); + if (!jso->o.c_double.source) { fjson_object_generic_delete(jso); errno = ENOMEM; return NULL; } - fjson_object_set_serializer(jso, fjson_object_userdata_to_json_string, - new_ds, fjson_object_free_userdata); + jso->_delete = &fjson_object_double_delete; return jso; } -int fjson_object_userdata_to_json_string(struct fjson_object *jso, - struct printbuf *pb, int __attribute__((unused)) level, int __attribute__((unused)) flags) -{ - int userdata_len = strlen((const char *)jso->_userdata); - printbuf_memappend_no_nul(pb, (const char *)jso->_userdata, userdata_len); - return 0; /* we need to keep compatible with the API */ -} - -void fjson_object_free_userdata(struct fjson_object __attribute__((unused)) *jso, void *userdata) -{ - free(userdata); -} - double fjson_object_get_double(struct fjson_object *jso) { double cdouble; @@ -820,7 +762,7 @@ double fjson_object_get_double(struct fjson_object *jso) if(!jso) return 0.0; switch(jso->o_type) { case fjson_type_double: - return jso->o.c_double; + return jso->o.c_double.value; case fjson_type_int: return jso->o.c_int64; case fjson_type_boolean: @@ -868,9 +810,9 @@ double fjson_object_get_double(struct fjson_object *jso) /* fjson_object_string */ static int fjson_object_string_to_json_string(struct fjson_object* jso, - struct printbuf *pb, - int __attribute__((unused)) level, - int __attribute__((unused)) flags) + struct printbuf *pb, + int __attribute__((unused)) level, + int __attribute__((unused)) flags) { printbuf_memappend_char(pb, '\"'); fjson_escape_str(pb, get_string_component(jso)); @@ -1062,13 +1004,13 @@ int fjson_object_array_add(struct fjson_object *jso,struct fjson_object *val) } int fjson_object_array_put_idx(struct fjson_object *jso, int idx, - struct fjson_object *val) + struct fjson_object *val) { return array_list_put_idx(jso->o.c_array, idx, val); } struct fjson_object* fjson_object_array_get_idx(struct fjson_object *jso, - int idx) + int idx) { return (struct fjson_object*)array_list_get_idx(jso->o.c_array, idx); } diff --git a/json_object.h b/json_object.h index 77be04d..1d354a7 100644 --- a/json_object.h +++ b/json_object.h @@ -133,19 +133,6 @@ typedef struct fjson_tokener fjson_tokener; */ typedef size_t (fjson_write_fn)(void *ptr, const char *buffer, size_t size); -/** - * Type of custom user delete functions. See fjson_object_set_serializer. - */ -typedef void (fjson_object_delete_fn)(struct fjson_object *jso, void *userdata); - -/** - * Type of a custom serialization function. See fjson_object_set_serializer. - */ -typedef int (fjson_object_to_json_string_fn)(struct fjson_object *jso, - struct printbuf *pb, - int level, - int flags); - /* supported object types */ typedef enum fjson_type { @@ -209,8 +196,25 @@ extern int fjson_object_is_type(struct fjson_object *obj, enum fjson_type type); */ extern enum fjson_type fjson_object_get_type(struct fjson_object *obj); +/** + * Get the size of the json string if it was dumped + * @param obj object to calculate the size of + * @returns the size of the json string + */ +extern size_t fjson_object_size(struct fjson_object *obj); -/** Dump object to a user-supplied function. +/** + * Extended version of the above function that accept a flags parameter identical + * to the fjson_object_dump_ext() function that you can use the specify how to + * format the string for which the size is calculated + * @param obj the object to calculate the size of + * @param flags extra flags + * @return size_t + */ +extern size_t fjson_object_size_ext(struct fjson_object *obj, int flags); + +/** + * Dump object to a user-supplied function. * Equivalent to fjson_object_write_ext(obj, FJSON_TO_STRING_SPACED, func, ptr) * @param obj object to be written * @param func your function that will be called to write the data @@ -230,6 +234,28 @@ extern size_t fjson_object_dump(struct fjson_object *obj, fjson_write_fn *func, */ extern size_t fjson_object_dump_ext(struct fjson_object *obj, int flags, fjson_write_fn *func, void *ptr); +/** + * Dump function that uses a user-supplied temporary buffer for dumping the + * json. Both the above declared fjson_object_dump() and fjson_object_dump_ext() + * functions uses an internal buffer of 128 bytes that is first filled before + * the user-supplied function is called. This buffer prevents that many calls + * to the callback function are done for single quotes, comma's and curly + * braces. All these calls are first buffered and grouped into a single call + * to the user space function. However, since the buffer limit is somewhat + * arbitrary, you can also use this fjson_object_dump_buffered() function to + * use your own temporary buffer. Note that the buffer might be completely + * overwritten during the call to this function, and that the contents of the + * buffer are undefined after the call. + * @param obj object to be written + * @param flags extra flags + * @param temp your temporary buffer that is used to group calls + * @param size size of your temporary buffer + * @param func your function that will be called to write the data + * @param ptr pointer that will be passed as first argument to your function + */ +extern size_t fjson_object_dump_buffered(struct fjson_object *obj, int flags, char *temp, +size_t size, fjson_write_fn *func, void *ptr); + /** * Write the json tree to a file * Equivalent to fjson_object_write_ext(obj, FJSON_TO_STRING_SPACED, fp) @@ -268,57 +294,6 @@ extern const char* fjson_object_to_json_string(struct fjson_object *obj); extern const char* fjson_object_to_json_string_ext(struct fjson_object *obj, int flags); -/** - * Set a custom serialization function to be used when this particular object - * is converted to a string by fjson_object_to_json_string. - * - * If a custom serializer is already set on this object, any existing - * user_delete function is called before the new one is set. - * - * If to_string_func is NULL, the other parameters are ignored - * and the default behaviour is reset. - * - * The userdata parameter is optional and may be passed as NULL. If provided, - * it is passed to to_string_func as-is. This parameter may be NULL even - * if user_delete is non-NULL. - * - * The user_delete parameter is optional and may be passed as NULL, even if - * the userdata parameter is non-NULL. It will be called just before the - * fjson_object is deleted, after it's reference count goes to zero - * (see fjson_object_put()). - * If this is not provided, it is up to the caller to free the userdata at - * an appropriate time. (i.e. after the fjson_object is deleted) - * - * @param jso the object to customize - * @param to_string_func the custom serialization function - * @param userdata an optional opaque cookie - * @param user_delete an optional function from freeing userdata - */ -extern void fjson_object_set_serializer(fjson_object *jso, - fjson_object_to_json_string_fn to_string_func, - void *userdata, - fjson_object_delete_fn *user_delete); - -/** - * Simply call free on the userdata pointer. - * Can be used with fjson_object_set_serializer(). - * - * @param jso unused - * @param userdata the pointer that is passed to free(). - */ -fjson_object_delete_fn fjson_object_free_userdata; - -/** - * Copy the jso->_userdata string over to pb as-is. - * Can be used with fjson_object_set_serializer(). - * - * @param jso The object whose _userdata is used. - * @param pb The destination buffer. - * @param level Ignored. - * @param flags Ignored. - */ -fjson_object_to_json_string_fn fjson_object_userdata_to_json_string; - /* object type methods */ @@ -504,7 +479,7 @@ extern int fjson_object_array_add(struct fjson_object *obj, * @param val the fjson_object to be added */ extern int fjson_object_array_put_idx(struct fjson_object *obj, int idx, - struct fjson_object *val); + struct fjson_object *val); /** Get the element at specificed index of the array (a fjson_object of type fjson_type_array) * @param obj the fjson_object instance @@ -512,7 +487,7 @@ extern int fjson_object_array_put_idx(struct fjson_object *obj, int idx, * @returns the fjson_object at the specified index (or NULL) */ extern struct fjson_object* fjson_object_array_get_idx(struct fjson_object *obj, - int idx); + int idx); /* fjson_bool type methods */ @@ -596,7 +571,7 @@ extern struct fjson_object* fjson_object_new_double(double d); /** * Create a new fjson_object of type fjson_type_double, using - * the exact serialized representation of the value. + * the exact representation of the value. * * This allows for numbers that would otherwise get displayed * inefficiently (e.g. 12.3 => "12.300000000000001") to be @@ -605,13 +580,6 @@ extern struct fjson_object* fjson_object_new_double(double d); * Note: this is used by fjson_tokener_parse_ex() to allow for * an exact re-serialization of a parsed object. * - * An equivalent sequence of calls is: - * @code - * jso = fjson_object_new_double(d); - * fjson_object_set_serializer(d, fjson_object_userdata_to_json_string, - * strdup(ds), fjson_object_free_userdata) - * @endcode - * * @param d the numeric value of the double. * @param ds the string representation of the double. This will be copied. */ diff --git a/json_object_private.h b/json_object_private.h index f62e6ba..a225d3a 100644 --- a/json_object_private.h +++ b/json_object_private.h @@ -19,7 +19,14 @@ extern "C" { #define LEN_DIRECT_STRING_DATA 32 /**< how many bytes are directly stored in fjson_object for strings? */ +/** + * Type of the delete and serialization functions. + */ typedef void (fjson_object_private_delete_fn)(struct fjson_object *o); +typedef int (fjson_object_to_json_string_fn)(struct fjson_object *jso, + struct printbuf *pb, + int level, + int flags); struct _fjson_child { /** @@ -50,7 +57,10 @@ struct fjson_object struct printbuf *_pb; union data { fjson_bool c_boolean; - double c_double; + struct { + double value; + char *source; + } c_double; int64_t c_int64; struct { int nelem; @@ -70,8 +80,6 @@ struct fjson_object int len; } c_string; } o; - fjson_object_delete_fn *_user_delete; - void *_userdata; DEF_ATOMIC_HELPER_MUT(_mut_ref_count) }; diff --git a/json_print.c b/json_print.c index 1895143..653eaee 100644 --- a/json_print.c +++ b/json_print.c @@ -20,6 +20,12 @@ #include #include +#ifdef HAVE_STDARG_H +# include +#else /* !HAVE_STDARG_H */ +# error Not enough var arg support! +#endif /* HAVE_STDARG_H */ + #include "json_object.h" #include "json_object_private.h" #include "json_object_iterator.h" @@ -29,9 +35,185 @@ # error You do not have snprintf on your system. #endif /* HAVE_SNPRINTF */ +#if !defined(HAVE_VASPRINTF) +/* CAW: compliant version of vasprintf */ +/* Note: on OpenCSW, we have vasprintf() inside the headers, but not inside the lib. + * So we need to use a different name, else we get issues with redefinitions. We + * we solve this by using the macro below, which just renames the function BUT + * does not affect the (variadic) arguments. + * rgerhards, 2017-04-11 + */ +#define vasprintf rs_vasprintf +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +static int rs_vasprintf(char **buf, const char *fmt, va_list ap) +{ + int chars; + char *b; + static char _T_emptybuffer = '\0'; + + if(!buf) { return -1; } + + /* CAW: RAWR! We have to hope to god here that vsnprintf doesn't overwrite + our buffer like on some 64bit sun systems.... but hey, its time to move on */ + chars = vsnprintf(&_T_emptybuffer, 0, fmt, ap)+1; + if(chars < 0) { chars *= -1; } /* CAW: old glibc versions have this problem */ + + b = (char*)malloc(sizeof(char)*chars); + if(!b) { return -1; } + + if((chars = vsprintf(b, fmt, ap)) < 0) { + free(b); + } else { + *buf = b; + } + + return chars; +} +#pragma GCC diagnostic pop +#endif /* !HAVE_VASPRINTF */ + +/** + * Internal structure that we use for buffering the print output + */ +struct buffer { + char *buffer; + size_t size; + size_t filled; + fjson_write_fn *overflow; + void *ptr; +}; + +/** + * Internal method to flush the buffer + * @param buffer + * @return size_t + */ +static size_t buffer_flush(struct buffer *buffer) +{ + // call the user-supplied overflow function + size_t result = buffer->overflow(buffer->ptr, buffer->buffer, buffer->filled); + + // buffer is empty now + buffer->filled = 0; + + // done + return result; +} + +/** + * Internal method to append data to the buffer + * @param buffer + * @param data + * @param size + * @return size_t + */ +static size_t buffer_append(struct buffer *buffer, const char *data, size_t size) +{ + // return value + size_t result = 0; + + // is the data to big to fit in the buffer? + if (buffer->filled + size > buffer->size) + { + // flush current buffer + if (buffer->filled > 0) result += buffer_flush(buffer); + + // does it still not fit? then we pass it to the callback immediately + if (size > buffer->size) return result + buffer->overflow(buffer->ptr, data, size); + } + + // append to the buffer + memcpy(buffer->buffer + buffer->filled, data, size); + + // update buffer size + buffer->filled += size; + + // done + return result; +} + +/** + * Internal method to printf() into the buffer + * @param buffer + * @param format + * @param ... + * @return size_t + */ +__attribute__((__format__(__printf__, 2, 3))) +static size_t buffer_printf(struct buffer *buffer, const char *format, ...) +{ + // return value + size_t result = 0; + + // variables used in this function + va_list arguments; + char *tmp; + int size; + + // make sure we have sufficient room in our buffer + if (buffer->size - buffer->filled < 32) result += buffer_flush(buffer); + + // initialize varargs + va_start(arguments, format); + + // write to the buffer (note the extra char for the extra null that is written by vsnprintf()) + size = vsnprintf(buffer->buffer + buffer->filled, buffer->size - buffer->filled - 1, format, arguments); + + // clean up varargs (it is not possible to reuse the vararg arguments later on, + // the have to be reset and possible reinitialized later on) + va_end(arguments); + + // was this all successful? + if (size >= 0 && size < (int)(buffer->size - buffer->filled)) + { + // this was a major success + buffer->filled += size; + } + else if (size > 0 && size < (int)buffer->size) + { + // there was not enough room in the buffer, but it would have been enough if + // we would have been able to use the entire buffer, so we reset the buffer, + // and retry the whole procedure + result += buffer_flush(buffer); + + // buffer is empty now, we can retry, start with the vararg initialization + va_start(arguments, format); + + // format into the buffer, again + buffer->size += vsnprintf(buffer->buffer + buffer->filled, buffer->size - buffer->filled - 1, format, arguments); + + // clean up varargs + va_end(arguments); + } + else + { + // initialize varargs + va_start(arguments, format); + + // our own buffer is not big enough to fit the text, we are going to use + // a dynamically allocated buffer using vasprintf(), init varargs first + va_start(arguments, format); + + // use dynamically allocated vasprintf() call + size = vasprintf(&tmp, format, arguments); + + // clean up varargs + va_end(arguments); + + // was this a success? + if (size > 0) result += buffer_append(buffer, tmp, size); + + // deallocate the memory + if (size >= 0) free(tmp); + } + + // done + return result; +} /* Forward declaration of the write function */ -static size_t write(struct fjson_object *jso, int level, int flags, fjson_write_fn *func, void *ptr); +static size_t write(struct fjson_object *jso, int level, int flags, struct buffer *buffer); /** * helper for accessing the optimized string data component in fjson_object @@ -80,45 +262,41 @@ extern const char char_needsEscape[256]; /** * Function to escape a string * @param str the string to be escaped - * @param func user supplied write function - * @param ptr user supplied pointer + * @param buffer the internal buffer to write to * @return size_t number of bytes written */ -static size_t escape(const char *str, fjson_write_fn *func, void *ptr) +static size_t escape(const char *str, struct buffer *buffer) { - int size; - char tempbuf[6]; size_t result = 0; const char *start_offset = str; while(1) { /* broken below on 0-byte */ if(char_needsEscape[*((unsigned char*)str)]) { if(*str == '\0') break; - if(str != start_offset) result += func(ptr, start_offset, str - start_offset); + if(str != start_offset) result += buffer_append(buffer, start_offset, str - start_offset); switch(*str) { - case '\b': result += func(ptr, "\\b", 2); break; - case '\n': result += func(ptr, "\\n", 2); break; - case '\r': result += func(ptr, "\\r", 2); break; - case '\t': result += func(ptr, "\\t", 2); break; - case '\f': result += func(ptr, "\\f", 2); break; - case '"': result += func(ptr, "\\\"", 2); break; - case '\\': result += func(ptr, "\\\\", 2); break; - case '/': result += func(ptr, "\\/", 2); break; + case '\b': result += buffer_append(buffer, "\\b", 2); break; + case '\n': result += buffer_append(buffer, "\\n", 2); break; + case '\r': result += buffer_append(buffer, "\\r", 2); break; + case '\t': result += buffer_append(buffer, "\\t", 2); break; + case '\f': result += buffer_append(buffer, "\\f", 2); break; + case '"': result += buffer_append(buffer, "\\\"", 2); break; + case '\\': result += buffer_append(buffer, "\\\\", 2); break; + case '/': result += buffer_append(buffer, "\\/", 2); break; default: - size = snprintf(tempbuf, sizeof(tempbuf), "\\u00%c%c", fjson_hex_chars[*str >> 4], fjson_hex_chars[*str & 0xf]); - result += func(ptr, tempbuf, size); + result += buffer_printf(buffer, "\\u00%c%c", fjson_hex_chars[*str >> 4], fjson_hex_chars[*str & 0xf]); break; } start_offset = ++str; } else ++str; } - if(str != start_offset) result += func(ptr, start_offset, str - start_offset); + if(str != start_offset) result += buffer_append(buffer, start_offset, str - start_offset); return result; } /* add indentation */ -static size_t indent(int level, int flags, fjson_write_fn *func, void *ptr) +static size_t indent(int level, int flags, struct buffer *buffer) { // result variable, and loop counter size_t result = 0; @@ -131,8 +309,8 @@ static size_t indent(int level, int flags, fjson_write_fn *func, void *ptr) for (i = 0; i < level; ++i) { // write a tab or two spaces - if (flags & FJSON_TO_STRING_PRETTY_TAB) result += func(ptr, "\t", 1); - else result += func(ptr, " ", 2); + if (flags & FJSON_TO_STRING_PRETTY_TAB) result += buffer_append(buffer, "\t", 1); + else result += buffer_append(buffer, " ", 2); } // done @@ -141,86 +319,100 @@ static size_t indent(int level, int flags, fjson_write_fn *func, void *ptr) /* write a json object */ -static size_t write_object(struct fjson_object* jso, int level, int flags, fjson_write_fn *func, void *ptr) +static size_t write_object(struct fjson_object* jso, int level, int flags, struct buffer *buffer) { int had_children = 0; size_t result = 0; - result += func(ptr, "{" /*}*/, 1); - if (flags & FJSON_TO_STRING_PRETTY) result += func(ptr, "\n", 1); + result += buffer_append(buffer, "{" /*}*/, 1); + if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1); struct fjson_object_iterator it = fjson_object_iter_begin(jso); struct fjson_object_iterator itEnd = fjson_object_iter_end(jso); while (!fjson_object_iter_equal(&it, &itEnd)) { if (had_children) { - result += func(ptr, ",", 1); - if (flags & FJSON_TO_STRING_PRETTY) result += func(ptr, "\n", 1); + result += buffer_append(buffer, ",", 1); + if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1); } had_children = 1; - if (flags & FJSON_TO_STRING_SPACED) result += func(ptr, " ", 1); - result += indent(level+1, flags, func, ptr); - result += func(ptr, "\"", 1); - result += escape(fjson_object_iter_peek_name(&it), func, ptr); - if (flags & FJSON_TO_STRING_SPACED) result += func(ptr, "\": ", 3); - else result += func(ptr, "\":", 2); - result += write(fjson_object_iter_peek_value(&it), level+1, flags, func, ptr); + if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, " ", 1); + result += indent(level+1, flags, buffer); + result += buffer_append(buffer, "\"", 1); + result += escape(fjson_object_iter_peek_name(&it), buffer); + if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, "\": ", 3); + else result += buffer_append(buffer, "\":", 2); + result += write(fjson_object_iter_peek_value(&it), level+1, flags, buffer); fjson_object_iter_next(&it); } if (flags & FJSON_TO_STRING_PRETTY) { - if (had_children) result += func(ptr, "\n", 1); - result += indent(level, flags, func, ptr); + if (had_children) result += buffer_append(buffer, "\n", 1); + result += indent(level, flags, buffer); } - if (flags & FJSON_TO_STRING_SPACED) result += func(ptr, /*{*/ " }", 2); - else result += func(ptr, /*{*/ "}", 1); + if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, /*{*/ " }", 2); + else result += buffer_append(buffer, /*{*/ "}", 1); return result; } /* write a json boolean */ -static size_t write_boolean(struct fjson_object* jso, fjson_write_fn *func, void *ptr) +static size_t write_boolean(struct fjson_object* jso, struct buffer *buffer) { - if (jso->o.c_boolean) return func(ptr, "true", 4); - else return func(ptr, "false", 5); + if (jso->o.c_boolean) return buffer_append(buffer, "true", 4); + else return buffer_append(buffer, "false", 5); } /* write a json int */ -static size_t write_int(struct fjson_object* jso, fjson_write_fn *func, void *ptr) +static size_t write_int(struct fjson_object* jso, struct buffer *buffer) { - // temporary buffer - char tempbuffer[32]; - size_t bytes = snprintf(tempbuffer, sizeof(tempbuffer), "%" PRId64, jso->o.c_int64); - return func(ptr, tempbuffer, bytes); + // printf into the buffer + return buffer_printf(buffer, "%" PRId64, jso->o.c_int64); } /* write a json floating point */ -static size_t write_double(struct fjson_object* jso, int flags, fjson_write_fn *func, void *ptr) +static size_t write_double(struct fjson_object* jso, int flags, struct buffer *buffer) { - char buf[128], *p, *q; - int size; + // return value for the function + size_t result = 0; + + // helper functions to fix the output + char *buf, *p, *q; + + // needed for modf() + double dummy; + + // if the original value is set, we reuse that + if (jso->o.c_double.source) return buffer_append(buffer, jso->o.c_double.source, strlen(jso->o.c_double.source)); + /* Although JSON RFC does not support * NaN or Infinity as numeric values * ECMA 262 section 9.8.1 defines * how to handle these cases as strings */ - if(isnan(jso->o.c_double)) - size = snprintf(buf, sizeof(buf), "NaN"); - else if(isinf(jso->o.c_double)) - if(jso->o.c_double > 0) - size = snprintf(buf, sizeof(buf), "Infinity"); - else - size = snprintf(buf, sizeof(buf), "-Infinity"); - else - size = snprintf(buf, sizeof(buf), "%.17g", jso->o.c_double); + if(isnan(jso->o.c_double.value)) return buffer_append(buffer, "NaN", 3); + if(isinf(jso->o.c_double.value)) return buffer_printf(buffer, jso->o.c_double.value > 0 ? "Infinity" : "-Infinity"); + + // store the beginning of the buffer (this is where buffer_printf() will most likely write) + buf = buffer->buffer + buffer->filled; + // write to the buffer + result = buffer_printf(buffer, (modf(jso->o.c_double.value, &dummy)==0)?"%.17g.0":"%.17g", jso->o.c_double.value); + + // if the buffer got flushed + if (buffer->buffer + buffer->filled < buf) buf = buffer->buffer; + + // if localization stuff caused "," to be generated instead of "." + // @todo is there not a nicer way to work around that??? p = strchr(buf, ','); if (p) { *p = '.'; } else { p = strchr(buf, '.'); } + + // remove trailing zero's if (p && (flags & FJSON_TO_STRING_NOZERO)) { /* last useful digit, always keep 1 zero */ p++; @@ -228,67 +420,68 @@ static size_t write_double(struct fjson_object* jso, int flags, fjson_write_fn * if (*q!='0') p=q; } /* drop trailing zeroes */ - *(++p) = 0; - size = p-buf; + buffer->filled = p - buffer->buffer; } - return func(ptr, buf, size); + + // done + return result; } /* write a json string */ -static size_t write_string(struct fjson_object* jso, fjson_write_fn *func, void *ptr) +static size_t write_string(struct fjson_object* jso, struct buffer *buffer) { - return func(ptr, "\"", 1) + escape(get_string_component(jso), func, ptr) + func(ptr, "\"", 1); + return buffer_append(buffer, "\"", 1) + escape(get_string_component(jso), buffer) + buffer_append(buffer, "\"", 1); } /* write a json array */ -static size_t write_array(struct fjson_object* jso, int level, int flags, fjson_write_fn *func, void *ptr) +static size_t write_array(struct fjson_object* jso, int level, int flags, struct buffer *buffer) { int had_children = 0; int ii; size_t result = 0; - result += func(ptr, "[", 1); - if (flags & FJSON_TO_STRING_PRETTY) result += func(ptr, "\n", 1); + result += buffer_append(buffer, "[", 1); + if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1); for(ii=0; ii < fjson_object_array_length(jso); ii++) { if (had_children) { - result += func(ptr, ",", 1); - if (flags & FJSON_TO_STRING_PRETTY) result += func(ptr, "\n", 1); + result += buffer_append(buffer, ",", 1); + if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1); } had_children = 1; - if (flags & FJSON_TO_STRING_SPACED) result += func(ptr, " ", 1); - result += indent(level + 1, flags, func, ptr); - result += write(fjson_object_array_get_idx(jso, ii), level+1, flags, func, ptr); + if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, " ", 1); + result += indent(level + 1, flags, buffer); + result += write(fjson_object_array_get_idx(jso, ii), level+1, flags, buffer); } if (flags & FJSON_TO_STRING_PRETTY) { - if (had_children) result += func(ptr, "\n", 1); - result += indent(level, flags, func, ptr); + if (had_children) result += buffer_append(buffer, "\n", 1); + result += indent(level, flags, buffer); } - if (flags & FJSON_TO_STRING_SPACED) result += func(ptr, " ]", 2); - else result += func(ptr, "]", 1); + if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, " ]", 2); + else result += buffer_append(buffer, "]", 1); return result; } /* write a json value */ -static size_t write(struct fjson_object *jso, int level, int flags, fjson_write_fn *func, void *ptr) +static size_t write(struct fjson_object *jso, int level, int flags, struct buffer *buffer) { // if object is not set - if (!jso) return func(ptr, "null", 4); + if (!jso) return buffer_append(buffer, "null", 4); // check type switch(jso->o_type) { - case fjson_type_null: return func(ptr, "null", 4); - case fjson_type_boolean: return write_boolean(jso, func, ptr); - case fjson_type_double: return write_double(jso, flags, func, ptr); - case fjson_type_int: return write_int(jso, func, ptr); - case fjson_type_object: return write_object(jso, level, flags, func, ptr); - case fjson_type_array: return write_array(jso, level, flags, func, ptr); - case fjson_type_string: return write_string(jso, func, ptr); + case fjson_type_null: return buffer_append(buffer, "null", 4); + case fjson_type_boolean: return write_boolean(jso, buffer); + case fjson_type_double: return write_double(jso, flags, buffer); + case fjson_type_int: return write_int(jso, buffer); + case fjson_type_object: return write_object(jso, level, flags, buffer); + case fjson_type_array: return write_array(jso, level, flags, buffer); + case fjson_type_string: return write_string(jso, buffer); default: return 0; } } @@ -300,12 +493,47 @@ static size_t fwrite_wrapper(void *ptr, const char *buffer, size_t size) return fwrite(buffer, 1, size, ptr); } +/* dummy output function that does not output, but is used to calculate the size */ + +static size_t calculate(void __attribute__((unused)) *ptr, const char __attribute__((unused)) *buffer, size_t size) +{ + return size; +} + +/* extended dump to which the helper buffer can be passed */ + +size_t fjson_object_dump_buffered(struct fjson_object *jso, int flags, char *temp, +size_t size, fjson_write_fn *func, void *ptr) +{ + // construct a buffer + struct buffer object; + + // initialize the properties + object.buffer = temp; + object.size = size; + object.filled = 0; + object.overflow = func; + object.ptr = ptr; + + // write the value + size_t result = write(jso, 0, flags, &object); + + // ready if buffer is now empty + if (object.size == 0) return result; + + // flush the buffer + return result + buffer_flush(&object); +} + /* extended dump function to string */ size_t fjson_object_dump_ext(struct fjson_object *jso, int flags, fjson_write_fn *func, void *ptr) { - // write the value - return write(jso, 0, flags, func, ptr); + // create a local 1k buffer on the stack + char buffer[1024]; + + // pass on to the other function + return fjson_object_dump_buffered(jso, flags, buffer, 1024, func, ptr); } /* more simple write function */ @@ -313,7 +541,25 @@ size_t fjson_object_dump_ext(struct fjson_object *jso, int flags, fjson_write_fn size_t fjson_object_dump(struct fjson_object *jso, fjson_write_fn *func, void *ptr) { // write the value - return write(jso, 0, FJSON_TO_STRING_SPACED, func, ptr); + return fjson_object_dump_ext(jso, FJSON_TO_STRING_SPACED, func, ptr); +} + +/* extended function to calculate the size */ + +size_t fjson_object_size_ext(struct fjson_object *jso, int flags) +{ + // write the value with a dummy function (this is a simple implementation that + // can later be optimized in a pure size-calculating function) + return fjson_object_dump_ext(jso, flags, &calculate, NULL); +} + +/* function to calculate the size */ + +size_t fjson_object_size(struct fjson_object *jso) +{ + // write the value with a dummy function (this is a simple implementation that + // can later be optimized in a pure size-calculating function) + return fjson_object_dump(jso, &calculate, NULL); } /* write to a file* */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 9dca5da..9df9892 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -17,7 +17,6 @@ TESTS+= test_parse.test TESTS+= test_locale.test TESTS+= test_charcase.test TESTS+= test_printbuf.test -TESTS+= test_set_serializer.test TESTS+= test_obj_iter-del.test TESTS+= test_object_object_add_ex.test TESTS+= test_many_subobj.test @@ -43,7 +42,6 @@ cr_obj_multi_SOURCES = cr_obj_multi.c chk_version_SOURCES = chk_version.c test_printbuf_SOURCES = test_printbuf.c -test_set_serializer_SOURCES = test_set_serializer.c # Note: handled by test1.test check_PROGRAMS += test1Formatted @@ -82,7 +80,6 @@ EXTRA_DIST += test_parse.expected EXTRA_DIST += test_parse_int64.expected EXTRA_DIST += test_printbuf.expected EXTRA_DIST += testReplaceExisting.expected -EXTRA_DIST += test_set_serializer.expected EXTRA_DIST += test_obj_iter-del.expected EXTRA_DIST += test_object_object_add_ex.expected EXTRA_DIST += test_object_object_add_exFormatted_plain.expected diff --git a/tests/test_set_serializer.c b/tests/test_set_serializer.c deleted file mode 100644 index 3f30279..0000000 --- a/tests/test_set_serializer.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "config.h" -#include -#include - -#include "../json.h" -#include "../printbuf.h" -#include "../debug.h" - -/* this is a work-around until we manage to fix configure.ac */ -#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" - -#define CHK(x) if (!(x)) { \ - printf("%s:%d: unexpected result with '%s'\n", \ - __FILE__, __LINE__, #x); \ - exit(1); \ -} - -struct myinfo { - int value; -}; - -static int freeit_was_called = 0; -static void freeit(fjson_object __attribute__((unused)) *jso, void *userdata) -{ - struct myinfo *info = userdata; - printf("freeit, value=%d\n", info->value); - // Don't actually free anything here, the userdata is stack allocated. - freeit_was_called = 1; -} -static int custom_serializer(struct fjson_object __attribute__((unused)) *o, - struct printbuf *pb, - int __attribute__((unused)) level, - int __attribute__((unused)) flags) -{ - sprintbuf(pb, "Custom Output"); - return 0; -} - -int main(int __attribute__((unused)) argc, char __attribute__((unused)) **argv) -{ - fjson_object *my_object; - - MC_SET_DEBUG(1); - - printf("Test setting, then resetting a custom serializer:\n"); - my_object = fjson_object_new_object(); - fjson_object_object_add(my_object, "abc", fjson_object_new_int(12)); - fjson_object_object_add(my_object, "foo", fjson_object_new_string("bar")); - - printf("my_object.to_string(standard)=%s\n", fjson_object_to_json_string(my_object)); - - struct myinfo userdata = { .value = 123 }; - fjson_object_set_serializer(my_object, custom_serializer, &userdata, freeit); - - printf("my_object.to_string(custom serializer)=%s\n", fjson_object_to_json_string(my_object)); - - printf("Next line of output should be from the custom freeit function:\n"); - freeit_was_called = 0; - fjson_object_set_serializer(my_object, NULL, NULL, NULL); - CHK(freeit_was_called); - - printf("my_object.to_string(standard)=%s\n", fjson_object_to_json_string(my_object)); - - fjson_object_put(my_object); - - // ============================================ - - my_object = fjson_object_new_object(); - printf("Check that the custom serializer isn't free'd until the last fjson_object_put:\n"); - fjson_object_set_serializer(my_object, custom_serializer, &userdata, freeit); - fjson_object_get(my_object); - fjson_object_put(my_object); - printf("my_object.to_string(custom serializer)=%s\n", fjson_object_to_json_string(my_object)); - printf("Next line of output should be from the custom freeit function:\n"); - - freeit_was_called = 0; - fjson_object_put(my_object); - CHK(freeit_was_called); - - return 0; -}