From 0ba768b2e998891f28d93250547031e93e39895f Mon Sep 17 00:00:00 2001 From: "Igor V. Kovalenko" Date: Sat, 13 Mar 2021 11:23:43 +0300 Subject: [PATCH] json: add JSON encoder Part-of: --- src/pulse/json.c | 469 ++++++++++++++++++++++++++++++++++++++++ src/pulse/json.h | 56 +++++ src/pulse/map-file | 33 +++ src/pulse/meson.build | 1 + src/tests/json-test.c | 576 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1135 insertions(+) diff --git a/src/pulse/json.c b/src/pulse/json.c index a38dbd8..460e9a7 100644 --- a/src/pulse/json.c +++ b/src/pulse/json.c @@ -44,6 +44,30 @@ struct pa_json_object { }; }; +/* JSON encoder context type */ +typedef enum pa_json_context_type { + /* Top-level context of empty encoder. JSON element can be added. */ + PA_JSON_CONTEXT_EMPTY = 0, + /* Top-level context of encoder with an element. JSON element cannot be added. */ + PA_JSON_CONTEXT_TOP = 1, + /* JSON array context. JSON elements can be added. */ + PA_JSON_CONTEXT_ARRAY = 2, + /* JSON object context. JSON object members can be added. */ + PA_JSON_CONTEXT_OBJECT = 3, +} pa_json_context_type_t; + +typedef struct encoder_context { + pa_json_context_type_t type; + int counter; + struct encoder_context *next; +} encoder_context; + +/* JSON encoder structure, a wrapper for pa_strbuf and encoder context */ +struct pa_json_encoder { + pa_strbuf *buffer; + encoder_context *context; +}; + static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth); static pa_json_object* json_object_new(void) { @@ -526,6 +550,11 @@ const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, return pa_hashmap_get(o->object_values, name); } +const pa_hashmap *pa_json_object_get_object_member_hashmap(const pa_json_object *o) { + pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + return o->object_values; +} + int pa_json_object_get_array_length(const pa_json_object *o) { pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); return pa_idxset_size(o->array_values); @@ -591,3 +620,443 @@ bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2) { pa_assert_not_reached(); } } + +/* Write functions. The functions are wrapper functions around pa_strbuf, + * so that the client does not need to use pa_strbuf directly. */ + +static void json_encoder_context_push(pa_json_encoder *encoder, pa_json_context_type_t type) { + pa_assert(encoder); + + encoder_context *head = pa_xnew0(encoder_context, 1); + head->type = type; + head->next = encoder->context; + encoder->context = head; +} + +/* Returns type of context popped off encoder context stack. */ +static pa_json_context_type_t json_encoder_context_pop(pa_json_encoder *encoder) { + encoder_context *head; + pa_json_context_type_t type; + + pa_assert(encoder); + pa_assert(encoder->context); + + type = encoder->context->type; + + head = encoder->context->next; + pa_xfree(encoder->context); + encoder->context = head; + + return type; +} + +pa_json_encoder *pa_json_encoder_new(void) { + pa_json_encoder *encoder; + + encoder = pa_xnew(pa_json_encoder, 1); + encoder->buffer = pa_strbuf_new(); + + encoder->context = NULL; + json_encoder_context_push(encoder, PA_JSON_CONTEXT_EMPTY); + + return encoder; +} + +void pa_json_encoder_free(pa_json_encoder *encoder) { + pa_json_context_type_t type; + pa_assert(encoder); + + /* should have exactly one encoder context left at this point */ + pa_assert(encoder->context); + type = json_encoder_context_pop(encoder); + pa_assert(encoder->context == NULL); + + pa_assert(type == PA_JSON_CONTEXT_TOP || type == PA_JSON_CONTEXT_EMPTY); + if (type == PA_JSON_CONTEXT_EMPTY) + pa_log_warn("JSON encoder is empty."); + + if (encoder->buffer) + pa_strbuf_free(encoder->buffer); + + pa_xfree(encoder); +} + +char *pa_json_encoder_to_string_free(pa_json_encoder *encoder) { + char *result; + + pa_assert(encoder); + + result = pa_strbuf_to_string_free(encoder->buffer); + + encoder->buffer = NULL; + pa_json_encoder_free(encoder); + + return result; +} + +static void json_encoder_insert_delimiter(pa_json_encoder *encoder) { + pa_assert(encoder); + + if (encoder->context->counter++) + pa_strbuf_putc(encoder->buffer, ','); +} + +/* Escapes p to create valid JSON string. + * The caller has to free the returned string. */ +static char *pa_json_escape(const char *p) { + const char *s; + char *out_string, *output; + int char_count = strlen(p); + + /* Maximum number of characters in output string + * including trailing 0. */ + char_count = 2 * char_count + 1; + + /* allocate output string */ + out_string = pa_xmalloc(char_count); + output = out_string; + + /* write output string */ + for (s = p; *s; ++s) { + switch (*s) { + case '"': + *output++ = '\\'; + *output++ = '"'; + break; + case '\\': + *output++ = '\\'; + *output++ = '\\'; + break; + case '\b': + *output++ = '\\'; + *output++ = 'b'; + break; + + case '\f': + *output++ = '\\'; + *output++ = 'f'; + break; + + case '\n': + *output++ = '\\'; + *output++ = 'n'; + break; + + case '\r': + *output++ = '\\'; + *output++ = 'r'; + break; + + case '\t': + *output++ = '\\'; + *output++ = 't'; + break; + default: + if (*s < 0x20 || *s > 0x7E) { + pa_log("Invalid non-ASCII character: 0x%x", (unsigned int) *s); + pa_xfree(out_string); + return NULL; + } + *output++ = *s; + break; + } + } + + *output = 0; + + return out_string; +} + +static void json_write_string_escaped(pa_json_encoder *encoder, const char *value) { + char *escaped_value; + + pa_assert(encoder); + + escaped_value = pa_json_escape(value); + pa_strbuf_printf(encoder->buffer, "\"%s\"", escaped_value); + pa_xfree(escaped_value); +} + +/* Writes an opening curly brace */ +void pa_json_encoder_begin_element_object(pa_json_encoder *encoder) { + pa_assert(encoder); + pa_assert(encoder->context->type != PA_JSON_CONTEXT_TOP); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + pa_strbuf_putc(encoder->buffer, '{'); + + json_encoder_context_push(encoder, PA_JSON_CONTEXT_OBJECT); +} + +/* Writes an opening curly brace */ +void pa_json_encoder_begin_member_object(pa_json_encoder *encoder, const char *name) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + pa_strbuf_putc(encoder->buffer, ':'); + + pa_strbuf_putc(encoder->buffer, '{'); + + json_encoder_context_push(encoder, PA_JSON_CONTEXT_OBJECT); +} + +/* Writes a closing curly brace */ +void pa_json_encoder_end_object(pa_json_encoder *encoder) { + pa_json_context_type_t type; + pa_assert(encoder); + + type = json_encoder_context_pop(encoder); + pa_assert(type == PA_JSON_CONTEXT_OBJECT); + + pa_strbuf_putc(encoder->buffer, '}'); +} + +/* Writes an opening bracket */ +void pa_json_encoder_begin_element_array(pa_json_encoder *encoder) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type != PA_JSON_CONTEXT_TOP); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + pa_strbuf_putc(encoder->buffer, '['); + + json_encoder_context_push(encoder, PA_JSON_CONTEXT_ARRAY); +} + +/* Writes member name and an opening bracket */ +void pa_json_encoder_begin_member_array(pa_json_encoder *encoder, const char *name) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + pa_strbuf_putc(encoder->buffer, ':'); + + pa_strbuf_putc(encoder->buffer, '['); + + json_encoder_context_push(encoder, PA_JSON_CONTEXT_ARRAY); +} + +/* Writes a closing bracket */ +void pa_json_encoder_end_array(pa_json_encoder *encoder) { + pa_json_context_type_t type; + pa_assert(encoder); + + type = json_encoder_context_pop(encoder); + pa_assert(type == PA_JSON_CONTEXT_ARRAY); + + pa_strbuf_putc(encoder->buffer, ']'); +} + +void pa_json_encoder_add_element_string(pa_json_encoder *encoder, const char *value) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_EMPTY || encoder->context->type == PA_JSON_CONTEXT_ARRAY); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, value); +} + +void pa_json_encoder_add_member_string(pa_json_encoder *encoder, const char *name, const char *value) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + + pa_strbuf_putc(encoder->buffer, ':'); + + /* Null value is written as empty element */ + if (!value) + value = ""; + + json_write_string_escaped(encoder, value); +} + +static void json_write_null(pa_json_encoder *encoder) { + pa_assert(encoder); + + pa_strbuf_puts(encoder->buffer, "null"); +} + +void pa_json_encoder_add_element_null(pa_json_encoder *encoder) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_EMPTY || encoder->context->type == PA_JSON_CONTEXT_ARRAY); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + + json_write_null(encoder); +} + +void pa_json_encoder_add_member_null(pa_json_encoder *encoder, const char *name) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + pa_strbuf_putc(encoder->buffer, ':'); + + json_write_null(encoder); +} + +static void json_write_bool(pa_json_encoder *encoder, bool value) { + pa_assert(encoder); + + pa_strbuf_puts(encoder->buffer, value ? "true" : "false"); +} + +void pa_json_encoder_add_element_bool(pa_json_encoder *encoder, bool value) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_EMPTY || encoder->context->type == PA_JSON_CONTEXT_ARRAY); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + + json_write_bool(encoder, value); +} + +void pa_json_encoder_add_member_bool(pa_json_encoder *encoder, const char *name, bool value) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + + pa_strbuf_putc(encoder->buffer, ':'); + + json_write_bool(encoder, value); +} + +static void json_write_int(pa_json_encoder *encoder, int64_t value) { + pa_assert(encoder); + + pa_strbuf_printf(encoder->buffer, "%"PRId64, value); +} + +void pa_json_encoder_add_element_int(pa_json_encoder *encoder, int64_t value) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_EMPTY || encoder->context->type == PA_JSON_CONTEXT_ARRAY); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + + json_write_int(encoder, value); +} + +void pa_json_encoder_add_member_int(pa_json_encoder *encoder, const char *name, int64_t value) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + + pa_strbuf_putc(encoder->buffer, ':'); + + json_write_int(encoder, value); +} + +static void json_write_double(pa_json_encoder *encoder, double value, int precision) { + pa_assert(encoder); + pa_strbuf_printf(encoder->buffer, "%.*f", precision, value); +} + +void pa_json_encoder_add_element_double(pa_json_encoder *encoder, double value, int precision) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_EMPTY || encoder->context->type == PA_JSON_CONTEXT_ARRAY); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + + json_write_double(encoder, value, precision); +} + +void pa_json_encoder_add_member_double(pa_json_encoder *encoder, const char *name, double value, int precision) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + + pa_strbuf_putc(encoder->buffer, ':'); + + json_write_double(encoder, value, precision); +} + +static void json_write_raw(pa_json_encoder *encoder, const char *raw_string) { + pa_assert(encoder); + pa_strbuf_puts(encoder->buffer, raw_string); +} + +void pa_json_encoder_add_element_raw_json(pa_json_encoder *encoder, const char *raw_json_string) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_EMPTY || encoder->context->type == PA_JSON_CONTEXT_ARRAY); + + if (encoder->context->type == PA_JSON_CONTEXT_EMPTY) + encoder->context->type = PA_JSON_CONTEXT_TOP; + + json_encoder_insert_delimiter(encoder); + + json_write_raw(encoder, raw_json_string); +} + +void pa_json_encoder_add_member_raw_json(pa_json_encoder *encoder, const char *name, const char *raw_json_string) { + pa_assert(encoder); + pa_assert(encoder->context); + pa_assert(encoder->context->type == PA_JSON_CONTEXT_OBJECT); + pa_assert(name && name[0]); + + json_encoder_insert_delimiter(encoder); + + json_write_string_escaped(encoder, name); + + pa_strbuf_putc(encoder->buffer, ':'); + + json_write_raw(encoder, raw_json_string); +} diff --git a/src/pulse/json.h b/src/pulse/json.h index 9a5b003..0c50242 100644 --- a/src/pulse/json.h +++ b/src/pulse/json.h @@ -17,8 +17,11 @@ along with PulseAudio; if not, see . ***/ +#pragma once + #include #include +#include #define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001) @@ -48,7 +51,60 @@ const char* pa_json_object_get_string(const pa_json_object *o); const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name); +/** Returns pa_hashmap (char* -> const pa_json_object*) to iterate over object members. \since 15.0 */ +const pa_hashmap *pa_json_object_get_object_member_hashmap(const pa_json_object *o); + int pa_json_object_get_array_length(const pa_json_object *o); const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index); bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2); + +/** @{ \name Write functions */ + +/** Structure which holds a JSON encoder. Wrapper for pa_strbuf and encoder context. \since 15.0 */ +typedef struct pa_json_encoder pa_json_encoder; + +/** Create a new pa_json_encoder structure. \since 15.0 */ +pa_json_encoder *pa_json_encoder_new(void); +/** Free a pa_json_encoder structure. \since 15.0 */ +void pa_json_encoder_free(pa_json_encoder *encoder); +/** Convert pa_json_encoder to string, free pa_json_encoder structure. + * The returned string needs to be freed with pa_xree(). \since 15.0 */ +char *pa_json_encoder_to_string_free(pa_json_encoder *encoder); + +/** Start appending JSON object element by writing an opening brace. \since 15.0 */ +void pa_json_encoder_begin_element_object(pa_json_encoder *encoder); +/** Start appending JSON object member to JSON object. \since 15.0 */ +void pa_json_encoder_begin_member_object(pa_json_encoder *encoder, const char *name); +/** End appending JSON object element or member to JSON object. \since 15.0 */ +void pa_json_encoder_end_object(pa_json_encoder *encoder); +/** Start appending JSON array element by writing an opening bracket. \since 15.0 */ +void pa_json_encoder_begin_element_array(pa_json_encoder *encoder); +/** Start appending JSON array member to JSON object. \since 15.0 */ +void pa_json_encoder_begin_member_array(pa_json_encoder *encoder, const char *name); +/** End appending JSON array element or member to JSON object. \since 15.0 */ +void pa_json_encoder_end_array(pa_json_encoder *encoder); +/** Append null element to JSON. \since 15.0 */ +void pa_json_encoder_add_element_null(pa_json_encoder *encoder); +/** Append null member to JSON object. \since 15.0 */ +void pa_json_encoder_add_member_null(pa_json_encoder *encoder, const char *name); +/** Append boolean element to JSON. \since 15.0 */ +void pa_json_encoder_add_element_bool(pa_json_encoder *encoder, bool value); +/** Append boolean member to JSON object. \since 15.0 */ +void pa_json_encoder_add_member_bool(pa_json_encoder *encoder, const char *name, bool value); +/** Append string element to JSON. Value will be escaped. \since 15.0 */ +void pa_json_encoder_add_element_string(pa_json_encoder *encoder, const char *value); +/** Append string member to JSON object. Value will be escaped. \since 15.0 */ +void pa_json_encoder_add_member_string(pa_json_encoder *encoder, const char *name, const char *value); +/** Append integer element to JSON. \since 15.0 */ +void pa_json_encoder_add_element_int(pa_json_encoder *encoder, int64_t value); +/** Append integer member to JSON object. \since 15.0 */ +void pa_json_encoder_add_member_int(pa_json_encoder *encoder, const char *name, int64_t value); +/** Append double element to JSON. \since 15.0 */ +void pa_json_encoder_add_element_double(pa_json_encoder *encoder, double value, int precision); +/** Append double member to JSON object. \since 15.0 */ +void pa_json_encoder_add_member_double(pa_json_encoder *encoder, const char *name, double value, int precision); +/** Append raw json string element to JSON. String will be written as is. \since 15.0 */ +void pa_json_encoder_add_element_raw_json(pa_json_encoder *encoder, const char *raw_json_string); +/** Append raw json string member to JSON object. String will be written as is. \since 15.0 */ +void pa_json_encoder_add_member_raw_json(pa_json_encoder *encoder, const char *name, const char *raw_json_string); diff --git a/src/pulse/map-file b/src/pulse/map-file index 5095470..c4fcf5a 100644 --- a/src/pulse/map-file +++ b/src/pulse/map-file @@ -215,6 +215,39 @@ pa_gettimeofday; pa_glib_mainloop_free; pa_glib_mainloop_get_api; pa_glib_mainloop_new; +pa_json_encoder_add_element_bool; +pa_json_encoder_add_element_double; +pa_json_encoder_add_element_int; +pa_json_encoder_add_element_null; +pa_json_encoder_add_element_raw_json; +pa_json_encoder_add_element_string; +pa_json_encoder_add_member_bool; +pa_json_encoder_add_member_double; +pa_json_encoder_add_member_int; +pa_json_encoder_add_member_null; +pa_json_encoder_add_member_raw_json; +pa_json_encoder_add_member_string; +pa_json_encoder_begin_element_array; +pa_json_encoder_begin_element_object; +pa_json_encoder_begin_member_array; +pa_json_encoder_begin_member_object; +pa_json_encoder_end_array; +pa_json_encoder_end_object; +pa_json_encoder_free; +pa_json_encoder_new; +pa_json_encoder_to_string_free; +pa_json_object_equal; +pa_json_object_free; +pa_json_object_get_array_length; +pa_json_object_get_array_member; +pa_json_object_get_bool; +pa_json_object_get_double; +pa_json_object_get_int; +pa_json_object_get_object_member; +pa_json_object_get_object_member_hashmap; +pa_json_object_get_string; +pa_json_object_get_type; +pa_json_parse; pa_locale_to_utf8; pa_mainloop_api_once; pa_mainloop_dispatch; diff --git a/src/pulse/meson.build b/src/pulse/meson.build index ccaff29..59ddea0 100644 --- a/src/pulse/meson.build +++ b/src/pulse/meson.build @@ -48,6 +48,7 @@ libpulse_headers = [ 'format.h', 'gccmacro.h', 'introspect.h', + 'json.h', 'mainloop-api.h', 'mainloop-signal.h', 'mainloop.h', diff --git a/src/tests/json-test.c b/src/tests/json-test.c index e40e9c7..41ac12d 100644 --- a/src/tests/json-test.c +++ b/src/tests/json-test.c @@ -24,6 +24,7 @@ #include #include +#include #include START_TEST (string_test) { @@ -50,6 +51,48 @@ START_TEST (string_test) { } END_TEST +START_TEST (encoder_string_test) { + const char *test_strings[] = { + "", "test", "test123", "123", "newline\n", " spaces ", + "lots of spaces", "esc\nape", "escape a \" quote", + }; + + pa_json_object *o; + unsigned int i; + pa_json_encoder *encoder; + const pa_json_object *v; + char *received; + + encoder = pa_json_encoder_new(); + + pa_json_encoder_begin_element_array(encoder); + + for (i = 0; i < PA_ELEMENTSOF(test_strings); i++) { + pa_json_encoder_add_element_string(encoder, test_strings[i]); + } + + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == PA_ELEMENTSOF(test_strings)); + + for (i = 0; i < PA_ELEMENTSOF(test_strings); i++) { + v = pa_json_object_get_array_member(o, i); + + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING); + fail_unless(pa_streq(pa_json_object_get_string(v), test_strings[i])); + } + + pa_json_object_free(o); +} +END_TEST + START_TEST(int_test) { pa_json_object *o; unsigned int i; @@ -75,6 +118,45 @@ START_TEST(int_test) { } END_TEST +START_TEST(encoder_int_test) { + const int64_t test_ints[] = { 1, -1, 1234, 0, LONG_MIN, LONG_MAX }; + + pa_json_object *o; + unsigned int i; + pa_json_encoder *encoder; + const pa_json_object *v; + char *received; + + encoder = pa_json_encoder_new(); + + pa_json_encoder_begin_element_array(encoder); + + for (i = 0; i < PA_ELEMENTSOF(test_ints); i++) { + pa_json_encoder_add_element_int(encoder, test_ints[i]); + } + + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == PA_ELEMENTSOF(test_ints)); + + for (i = 0; i < PA_ELEMENTSOF(test_ints); i++) { + v = pa_json_object_get_array_member(o, i); + + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_INT); + fail_unless(pa_json_object_get_int(v) == test_ints[i]); + } + + pa_json_object_free(o); +} +END_TEST + START_TEST(double_test) { pa_json_object *o; unsigned int i; @@ -104,6 +186,46 @@ START_TEST(double_test) { } END_TEST +START_TEST(encoder_double_test) { + const double test_doubles[] = { + 1.0, -1.1, 123400.0, 1234.0, 0.1234, -0.1234, 123.4, 123.45, 123450.0, + }; + pa_json_object *o; + unsigned int i; + pa_json_encoder *encoder; + const pa_json_object *v; + char *received; + + encoder = pa_json_encoder_new(); + + pa_json_encoder_begin_element_array(encoder); + + for (i = 0; i < PA_ELEMENTSOF(test_doubles); i++) { + pa_json_encoder_add_element_double(encoder, test_doubles[i], 6); + } + + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == PA_ELEMENTSOF(test_doubles)); + + for (i = 0; i < PA_ELEMENTSOF(test_doubles); i++) { + v = pa_json_object_get_array_member(o, i); + + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE); + fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), test_doubles[i])); + } + + pa_json_object_free(o); +} +END_TEST + START_TEST(null_test) { pa_json_object *o; @@ -116,6 +238,25 @@ START_TEST(null_test) { } END_TEST +START_TEST(encoder_null_test) { + pa_json_object *o; + pa_json_encoder *encoder; + char *received; + + encoder = pa_json_encoder_new(); + pa_json_encoder_add_element_null(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL); + + pa_json_object_free(o); +} +END_TEST + START_TEST(bool_test) { pa_json_object *o; @@ -137,6 +278,46 @@ START_TEST(bool_test) { } END_TEST +START_TEST(encoder_bool_test) { + const bool test_bools[] = { + true, false + }; + pa_json_object *o; + unsigned int i; + pa_json_encoder *encoder; + const pa_json_object *v; + char *received; + + encoder = pa_json_encoder_new(); + + pa_json_encoder_begin_element_array(encoder); + + for (i = 0; i < PA_ELEMENTSOF(test_bools); i++) { + pa_json_encoder_add_element_bool(encoder, test_bools[i]); + } + + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == PA_ELEMENTSOF(test_bools)); + + for (i = 0; i < PA_ELEMENTSOF(test_bools); i++) { + v = pa_json_object_get_array_member(o, i); + + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL); + fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_bool(v), test_bools[i])); + } + + pa_json_object_free(o); +} +END_TEST + START_TEST(object_test) { pa_json_object *o; const pa_json_object *v; @@ -192,6 +373,165 @@ START_TEST(object_test) { } END_TEST +START_TEST(object_member_iterator_test) { + pa_json_object *o; + const pa_json_object *v; + const char *k; + void *state; + size_t i; + + struct { + bool visited; + const char *key; + pa_json_type type; + union { + const char *str; + int64_t n; + } value; + } expected_entries[] = { + { .key = "name", .type = PA_JSON_TYPE_STRING, .value.str = "sample 1" }, + { .key = "number", .type = PA_JSON_TYPE_INT, .value.n = 42 }, + }; + + o = pa_json_parse(" { \"name\" : \"sample 1\", \"number\": 42 } "); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + PA_HASHMAP_FOREACH_KV(k, v, pa_json_object_get_object_member_hashmap(o), state) { + fail_unless(k != NULL); + fail_unless(v != NULL); + for (i = 0; i < PA_ELEMENTSOF(expected_entries); ++i) { + if (pa_streq(expected_entries[i].key, k)) { + fail_unless(!expected_entries[i].visited); + fail_unless(expected_entries[i].type == pa_json_object_get_type(v)); + switch (expected_entries[i].type) { + case PA_JSON_TYPE_STRING: + fail_unless(pa_streq(expected_entries[i].value.str, pa_json_object_get_string(v))); + break; + case PA_JSON_TYPE_INT: + fail_unless(expected_entries[i].value.n == pa_json_object_get_int(v)); + break; + default: + /* unreachable */ + fail_unless(false); + break; + } + expected_entries[i].visited = true; + } + } + } + + pa_json_object_free(o); +} +END_TEST + +START_TEST(encoder_object_test) { + pa_json_object *o; + const pa_json_object *v; + pa_json_encoder *encoder; + char *received; + + /* { "name" : "A Person" } */ + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_string(encoder, "name", "A Person"); + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "name"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING); + fail_unless(pa_streq(pa_json_object_get_string(v), "A Person")); + + pa_json_object_free(o); + + /* { "age" : -45.3e-0 } */ + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_double(encoder, "age", -45.3e-0, 2); + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "age"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE); + fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3)); + + pa_json_object_free(o); + + /* {"person":true} */ + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + pa_json_encoder_add_member_bool(encoder, "person", true); + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "person"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL); + fail_unless(pa_json_object_get_bool(v) == true); + + pa_json_object_free(o); +} +END_TEST + +START_TEST(encoder_member_object_test) { + pa_json_object *o; + const pa_json_object *v; + pa_json_encoder *encoder; + char *received; + + /* { "parent": { "child": false } } */ + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + + pa_json_encoder_begin_member_object(encoder, "parent"); + pa_json_encoder_add_member_bool(encoder, "child", false); + pa_json_encoder_end_object(encoder); + + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "parent"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT); + v = pa_json_object_get_object_member(v, "child"); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL); + fail_unless(pa_json_object_get_bool(v) == false); + + pa_json_object_free(o); +} +END_TEST + START_TEST(array_test) { pa_json_object *o; const pa_json_object *v, *v2; @@ -243,6 +583,231 @@ START_TEST(array_test) { } END_TEST +START_TEST(encoder_element_array_test) { + pa_json_object *o; + const pa_json_object *v, *v2; + + pa_json_encoder *encoder; + char *received; + pa_json_encoder *subobject; + char *subobject_string; + + /* [ ] */ + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == 0); + + pa_json_object_free(o); + + /* ["a member"] */ + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_add_element_string(encoder, "a member"); + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == 1); + + v = pa_json_object_get_array_member(o, 0); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING); + fail_unless(pa_streq(pa_json_object_get_string(v), "a member")); + + pa_json_object_free(o); + + /* [\"a member\", 1234.5, { \"another\": true } ] */ + + subobject = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(subobject); + pa_json_encoder_add_member_bool(subobject, "another", true); + pa_json_encoder_end_object(subobject); + subobject_string = pa_json_encoder_to_string_free(subobject); + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(encoder); + pa_json_encoder_add_element_string(encoder, "a member"); + pa_json_encoder_add_element_double(encoder, 1234.5, 1); + pa_json_encoder_add_element_raw_json(encoder, subobject_string); + pa_xfree(subobject_string); + pa_json_encoder_end_array(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(o) == 3); + + v = pa_json_object_get_array_member(o, 0); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING); + fail_unless(pa_streq(pa_json_object_get_string(v), "a member")); + v = pa_json_object_get_array_member(o, 1); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE); + fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), 1234.5)); + v = pa_json_object_get_array_member(o, 2); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT); + v2 =pa_json_object_get_object_member(v, "another"); + fail_unless(v2 != NULL); + fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL); + fail_unless(pa_json_object_get_bool(v2) == true); + + pa_json_object_free(o); +} +END_TEST + +START_TEST(encoder_member_array_test) { + pa_json_object *o; + unsigned int i; + const pa_json_object *v; + const pa_json_object *e; + pa_json_encoder *encoder; + char *received; + + const int64_t test_ints[] = { 1, -1, 1234, 0, LONG_MIN, LONG_MAX }; + + /* { "parameters": [ 1, -1, 1234, 0, -9223372036854775808, 9223372036854775807 ] } */ + + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + + pa_json_encoder_begin_member_array(encoder, "parameters"); + for (i = 0; i < PA_ELEMENTSOF(test_ints); i++) { + pa_json_encoder_add_element_int(encoder, test_ints[i]); + } + pa_json_encoder_end_array(encoder); + + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "parameters"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(v) == PA_ELEMENTSOF(test_ints)); + + for (i = 0; i < PA_ELEMENTSOF(test_ints); i++) { + e = pa_json_object_get_array_member(v, i); + + fail_unless(e != NULL); + fail_unless(pa_json_object_get_type(e) == PA_JSON_TYPE_INT); + fail_unless(pa_json_object_get_int(e) == test_ints[i]); + } + + pa_json_object_free(o); +} +END_TEST + +START_TEST(encoder_member_raw_json_test) { + pa_json_object *o; + const pa_json_object *v; + const pa_json_object *e; + pa_json_encoder *encoder; + char *received; + pa_json_encoder *subobject; + char *subobject_string; + + /* { "parameters": [1, "a", 2.0] } */ + + subobject = pa_json_encoder_new(); + pa_json_encoder_begin_element_array(subobject); + pa_json_encoder_add_element_int(subobject, 1); + pa_json_encoder_add_element_string(subobject, "a"); + pa_json_encoder_add_element_double(subobject, 2.0, 6); + pa_json_encoder_end_array(subobject); + subobject_string = pa_json_encoder_to_string_free(subobject); + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + + pa_json_encoder_add_member_raw_json(encoder, "parameters", subobject_string); + pa_xfree(subobject_string); + + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "parameters"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_ARRAY); + fail_unless(pa_json_object_get_array_length(v) == 3); + e = pa_json_object_get_array_member(v, 0); + fail_unless(e != NULL); + fail_unless(pa_json_object_get_type(e) == PA_JSON_TYPE_INT); + fail_unless(pa_json_object_get_int(e) == 1); + e = pa_json_object_get_array_member(v, 1); + fail_unless(e != NULL); + fail_unless(pa_json_object_get_type(e) == PA_JSON_TYPE_STRING); + fail_unless(pa_streq(pa_json_object_get_string(e), "a")); + e = pa_json_object_get_array_member(v, 2); + fail_unless(e != NULL); + fail_unless(pa_json_object_get_type(e) == PA_JSON_TYPE_DOUBLE); + fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(e), 2.0)); + + pa_json_object_free(o); + + /* { "parent": { "child": false } } */ + + subobject = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(subobject); + pa_json_encoder_add_member_bool(subobject, "child", false); + pa_json_encoder_end_object(subobject); + subobject_string = pa_json_encoder_to_string_free(subobject); + + encoder = pa_json_encoder_new(); + pa_json_encoder_begin_element_object(encoder); + + pa_json_encoder_add_member_raw_json(encoder, "parent", subobject_string); + pa_xfree(subobject_string); + + pa_json_encoder_end_object(encoder); + + received = pa_json_encoder_to_string_free(encoder); + o = pa_json_parse(received); + pa_xfree(received); + + fail_unless(o != NULL); + fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT); + + v = pa_json_object_get_object_member(o, "parent"); + fail_unless(v != NULL); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT); + v = pa_json_object_get_object_member(v, "child"); + fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL); + fail_unless(pa_json_object_get_bool(v) == false); + + pa_json_object_free(o); +} +END_TEST + START_TEST(bad_test) { unsigned int i; const char *bad_parse[] = { @@ -283,12 +848,23 @@ int main(int argc, char *argv[]) { s = suite_create("JSON"); tc = tcase_create("json"); tcase_add_test(tc, string_test); + tcase_add_test(tc, encoder_string_test); tcase_add_test(tc, int_test); + tcase_add_test(tc, encoder_int_test); tcase_add_test(tc, double_test); + tcase_add_test(tc, encoder_double_test); tcase_add_test(tc, null_test); + tcase_add_test(tc, encoder_null_test); tcase_add_test(tc, bool_test); + tcase_add_test(tc, encoder_bool_test); tcase_add_test(tc, object_test); + tcase_add_test(tc, encoder_member_object_test); + tcase_add_test(tc, object_member_iterator_test); + tcase_add_test(tc, encoder_object_test); tcase_add_test(tc, array_test); + tcase_add_test(tc, encoder_element_array_test); + tcase_add_test(tc, encoder_member_array_test); + tcase_add_test(tc, encoder_member_raw_json_test); tcase_add_test(tc, bad_test); suite_add_tcase(s, tc); -- 2.7.4