From 1a94c70336ac355aaf9c9f34bdc1f8fcbb64018d Mon Sep 17 00:00:00 2001 From: Eric Haszlakiewicz Date: Sun, 18 Jun 2017 18:04:49 +0000 Subject: [PATCH] Add a json_c_set_serialization_double_format() function to set the *library-wide* format for how doubles are written to a serialized JSON output. --- configure.ac | 12 ++++ json_object.c | 85 ++++++++++++++++++++++++--- json_object.h | 31 ++++++++++ tests/test_double_serializer.c | 26 ++++++++ tests/test_double_serializer.expected | 3 + 5 files changed, 148 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index 9f495af..364d27d 100644 --- a/configure.ac +++ b/configure.ac @@ -41,6 +41,18 @@ AC_CHECK_HEADER(inttypes.h,[AC_DEFINE([JSON_C_HAVE_INTTYPES_H],[1],[Public defin AC_C_CONST AC_TYPE_SIZE_T +AC_CACHE_CHECK([for __thread support], ac_cv___thread, [dnl +AC_LINK_IFELSE([dnl +AC_LANG_PROGRAM([[#undef __thread +static __thread int a; int foo (int b) { return a + b; }]], + [[exit (foo (0));]])], + ac_cv___thread=yes, ac_cv___thread=no) +]) +AS_IF([test "x$ac_cv___thread" != xno], +[AC_DEFINE(HAVE___THREAD, 1, [Have __thread]), + AC_DEFINE(SPEC___THREAD, [__thread], [Specifier for __thread])] +) + # Checks for library functions. AC_FUNC_VPRINTF AC_FUNC_MEMCMP diff --git a/json_object.c b/json_object.c index 57342b0..42bb738 100644 --- a/json_object.c +++ b/json_object.c @@ -692,6 +692,50 @@ int json_object_set_int64(struct json_object *jso,int64_t new_value){ /* json_object_double */ +#ifdef HAVE___THREAD +// i.e. __thread or __declspec(thread) +static SPEC___THREAD char *tls_serialization_float_format = NULL; +#endif +static char *global_serialization_float_format = NULL; + +int json_c_set_serialization_double_format(const char *double_format, int global_or_thread) +{ + if (global_or_thread == JSON_C_OPTION_GLOBAL) + { +#ifdef HAVE___THREAD + if (tls_serialization_float_format) + { + free(tls_serialization_float_format); + tls_serialization_float_format = NULL; + } +#endif + if (global_serialization_float_format) + free(global_serialization_float_format); + global_serialization_float_format = double_format ? strdup(double_format) : NULL; + } + else if (global_or_thread == JSON_C_OPTION_THREAD) + { +#ifdef HAVE___THREAD + if (tls_serialization_float_format) + { + free(tls_serialization_float_format); + tls_serialization_float_format = NULL; + } + tls_serialization_float_format = double_format ? strdup(double_format) : NULL; +#else + _set_last_err("json_c_set_option: not compiled with __thread support\n"); + return -1; +#endif + } + else + { + _set_last_err("json_c_set_option: invalid global_or_thread value: %d\n", global_or_thread); + return -1; + } + return 0; +} + + static int json_object_double_to_json_string_format(struct json_object* jso, struct printbuf *pb, int level, @@ -712,13 +756,31 @@ static int json_object_double_to_json_string_format(struct json_object* jso, size = snprintf(buf, sizeof(buf), "Infinity"); else size = snprintf(buf, sizeof(buf), "-Infinity"); - else - size = snprintf(buf, sizeof(buf), - format ? format : - (modf(jso->o.c_double, &dummy) == 0) ? "%.17g.0" : "%.17g", - jso->o.c_double); - if(size < 0 || size >= (int)sizeof(buf)) - size = (int)sizeof(buf); + else + { + const char *std_format = "%.17g"; + +#ifdef HAVE___THREAD + if (tls_serialization_float_format) + std_format = tls_serialization_float_format; + else +#endif + if (global_serialization_float_format) + std_format = global_serialization_float_format; + if (!format) + format = std_format; + size = snprintf(buf, sizeof(buf), format, jso->o.c_double); + if (modf(jso->o.c_double, &dummy) == 0) + { + // Ensure it looks like a float, even if snprintf didn't. + strncat(buf, ".0", sizeof(buf) - 1); + if (size >= 0) + size += 2; // yes, even if strncat ran out of room + } + } + // although unlikely, snprintf can fail + if (size < 0) + return -1; p = strchr(buf, ','); if (p) { @@ -736,8 +798,13 @@ static int json_object_double_to_json_string_format(struct json_object* jso, *(++p) = 0; size = p-buf; } - printbuf_memappend(pb, buf, size); - return size; + + if (size >= (int)sizeof(buf)) + // The standard formats are guaranteed not to overrun the buffer, + // but if a custom one happens to do so, just silently truncate. + size = sizeof(buf) - 1; + printbuf_memappend(pb, buf, size); + return size; } static int json_object_double_to_json_string_default(struct json_object* jso, diff --git a/json_object.h b/json_object.h index f86f678..e3da64b 100644 --- a/json_object.h +++ b/json_object.h @@ -105,6 +105,22 @@ extern "C" { #undef TRUE #define TRUE ((json_bool)1) +/** + * Set the global value of an option, which will apply to all + * current and future threads that have not set a thread-local value. + * + * @see json_c_set_serialization_double_format + */ +#define JSON_C_OPTION_GLOBAL (0) +/** + * Set a thread-local value of an option, overriding the global value. + * This will fail if json-c is not compiled with threading enabled, and + * with the __thread specifier (or equivalent) available. + * + * @see json_c_set_serialization_double_format + */ +#define JSON_C_OPTION_THREAD (1) + extern const char *json_number_chars; extern const char *json_hex_chars; @@ -748,6 +764,21 @@ extern struct json_object* json_object_new_double(double d); */ extern struct json_object* json_object_new_double_s(double d, const char *ds); +/** + * Set a global or thread-local json-c option, depending on whether + * JSON_C_OPTION_GLOBAL or JSON_C_OPTION_THREAD is passed. + * Thread-local options default to undefined, and inherit from the global + * value, even if the global value is changed after the thread is created. + * Attempting to set thread-local options when threading is not compiled in + * will result in an error. Be sure to check the return value. + * + * double_format is a "%g" printf format, such as "%.20g" + * + * @return -1 on errors, 0 on success. + */ +int json_c_set_serialization_double_format(const char *double_format, int global_or_thread); + + /** Serialize a json_object of type json_type_double to a string. * diff --git a/tests/test_double_serializer.c b/tests/test_double_serializer.c index 4ef754d..c7b229e 100644 --- a/tests/test_double_serializer.c +++ b/tests/test_double_serializer.c @@ -27,5 +27,31 @@ int main() json_object_set_serializer(obj, NULL, NULL, NULL); printf("obj.to_string(reset)=%s\n", json_object_to_json_string(obj)); + json_object_put(obj); + obj = json_object_new_double(0.52381); + + printf("obj.to_string(default format)=%s\n", json_object_to_json_string(obj)); + if (json_c_set_serialization_double_format("x%0.3fy", JSON_C_OPTION_GLOBAL) < 0) + printf("ERROR: json_c_set_serialization_double_format() failed"); + printf("obj.to_string(with global format)=%s\n", json_object_to_json_string(obj)); +#ifdef HAVE___THREAD + if (json_c_set_serialization_double_format("T%0.2fX", JSON_C_OPTION_THREAD) < 0) + printf("ERROR: json_c_set_serialization_double_format() failed"); + printf("obj.to_string(with thread format)=%s\n", json_object_to_json_string(obj)); + if (json_c_set_serialization_double_format("Ttttttttttttt%0.2fxxxxxxxxxxxxxxxxxxX", JSON_C_OPTION_THREAD) < 0) + printf("ERROR: json_c_set_serialization_double_format() failed"); + printf("obj.to_string(long thread format)=%s\n", json_object_to_json_string(obj)); + if (json_c_set_serialization_double_format(NULL, JSON_C_OPTION_THREAD) < 0) + printf("ERROR: json_c_set_serialization_double_format() failed"); + printf("obj.to_string(back to global format)=%s\n", json_object_to_json_string(obj)); +#else + // Just fake it up, so the output matches. + printf("obj.to_string(with thread format)=%s\n", "T0.52X"); + printf("obj.to_string(back to global format)=%s\n", "x0.524y"); +#endif + if (json_c_set_serialization_double_format(NULL, JSON_C_OPTION_GLOBAL) < 0) + printf("ERROR: json_c_set_serialization_double_format() failed"); + printf("obj.to_string(back to default format)=%s\n", json_object_to_json_string(obj)); + json_object_put(obj); } diff --git a/tests/test_double_serializer.expected b/tests/test_double_serializer.expected index da645d7..6678910 100644 --- a/tests/test_double_serializer.expected +++ b/tests/test_double_serializer.expected @@ -6,3 +6,6 @@ Test explicit serializer with custom userdata: obj.to_string(custom)=test Test reset serializer: obj.to_string(reset)=0.5 +obj.to_string(default format)=0.52381 +obj.to_string(with global format)=x0.524y +obj.to_string(with thread format)=T0.52X -- 2.40.0