Pretty serialization + tests.

This commit is contained in:
Krzysztof Gabis 2015-06-22 16:53:19 +02:00
parent 1586461280
commit 6905548257
6 changed files with 287 additions and 41 deletions

181
parson.c
View File

@ -49,8 +49,7 @@
static JSON_Malloc_Function parson_malloc = malloc;
static JSON_Free_Function parson_free = free;
#define PRINT_AND_SKIP(str, to_append) str += sprintf(str, to_append);
#define PRINTF_AND_SKIP(str, format, to_append) str += sprintf(str, format, to_append);
#define INDENT_STRLEN(level) ((level)*4) /* strlen(" ") */
#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */
@ -93,6 +92,8 @@ static int verify_utf8_sequence(const unsigned char *string, int *len);
static int is_valid_utf8(const char *string, size_t string_len);
static int is_decimal(const char *string, size_t length);
static size_t serialization_strlen(const char *string);
static int print_indent(char *buf, int level);
/* JSON Object */
static JSON_Object * json_object_init(void);
@ -124,8 +125,8 @@ static JSON_Value * parse_null_value(const char **string);
static JSON_Value * parse_value(const char **string, size_t nesting);
/* Serialization */
static size_t json_serialization_size_r(const JSON_Value *value, char *buf);
static char * json_serialize_to_buffer_r(const JSON_Value *value, char *buf);
static size_t json_serialization_size_r(const JSON_Value *value, char *buf, int level, int is_pretty);
static char * json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty);
static char * json_serialize_string(const char *string, char *buf);
/* Various */
@ -238,6 +239,15 @@ static size_t serialization_strlen(const char *string) {
return result;
}
static int print_indent(char *buf, int level) {
int i;
char *buf_ptr = buf;
for (i = 0; i < level; i++) {
buf_ptr += sprintf(buf_ptr, " ");
}
return (int)(buf_ptr - buf);
}
static char * read_file(const char * filename) {
FILE *fp = fopen(filename, "r");
size_t file_size;
@ -727,7 +737,7 @@ static JSON_Value * parse_null_value(const char **string) {
}
/* Serialization */
static size_t json_serialization_size_r(const JSON_Value *value, char *buf) {
static size_t json_serialization_size_r(const JSON_Value *value, char *buf, int level, int is_pretty) {
size_t result_size = 0;
const char *key = NULL;
JSON_Value *temp_value = NULL;
@ -735,28 +745,45 @@ static size_t json_serialization_size_r(const JSON_Value *value, char *buf) {
JSON_Object *object = NULL;
size_t i = 0, count = 0;
double num = 0.0;
switch (json_value_get_type(value)) {
case JSONArray:
array = json_value_get_array(value);
count = json_array_get_count(array);
result_size += 2; /* [ and ] brackets */
if (count > 0)
if (count > 0) {
result_size += count - 1; /* , between items */
if (is_pretty) {
result_size += count + 1; /* \n after every item and [ */
result_size += INDENT_STRLEN(level+1) * count; /* indent for every item */
result_size += INDENT_STRLEN(level); /* indent for closing ] */
}
}
for (i = 0; i < count; i++) {
temp_value = json_array_get_value(array, i);
result_size += json_serialization_size_r(temp_value, buf);
result_size += json_serialization_size_r(temp_value, buf, level+1, is_pretty);
}
return result_size;
case JSONObject:
object = json_value_get_object(value);
count = json_object_get_count(object);
result_size += 2; /* { and } brackets */
if (count > 0)
result_size += (count * 2) - 1; /* : between keys and values and , between items */
if (count > 0) {
result_size += (2 * count) - 1; /* : between keys and values and , between items */
if (is_pretty) {
result_size += count + 1; /* \n after every item and after { */
result_size += count; /* " " after every : */
result_size += INDENT_STRLEN(level+1) * count; /* indent for every kvp pair */
result_size += INDENT_STRLEN(level); /* indent for closing } */
}
}
for (i = 0; i < count; i++) {
key = json_object_get_name(object, i);
result_size += serialization_strlen(key) + 2; /* string and quotes */
result_size += json_serialization_size_r(json_object_get_value(object, key), buf);
result_size += json_serialization_size_r(json_object_get_value(object, key), buf, level+1, is_pretty);
}
return result_size;
case JSONString:
@ -780,7 +807,7 @@ static size_t json_serialization_size_r(const JSON_Value *value, char *buf) {
}
}
char* json_serialize_to_buffer_r(const JSON_Value *value, char *buf)
char* json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty)
{
const char *key = NULL, *string = NULL;
JSON_Value *temp_value = NULL;
@ -788,39 +815,67 @@ char* json_serialize_to_buffer_r(const JSON_Value *value, char *buf)
JSON_Object *object = NULL;
size_t i = 0, count = 0;
double num = 0.0;
switch (json_value_get_type(value)) {
case JSONArray:
array = json_value_get_array(value);
count = json_array_get_count(array);
PRINT_AND_SKIP(buf, "[");
buf += sprintf(buf, "[");
if (count > 0 && is_pretty) {
buf += sprintf(buf, "\n");
}
for (i = 0; i < count; i++) {
if (is_pretty) {
buf += print_indent(buf, level+1);
}
temp_value = json_array_get_value(array, i);
buf = json_serialize_to_buffer_r(temp_value, buf);
buf = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty);
if (buf == NULL)
return NULL;
if (i < (count - 1))
PRINT_AND_SKIP(buf, ",");
buf += sprintf(buf, ",");
if (is_pretty) {
buf += sprintf(buf, "\n");
}
}
PRINT_AND_SKIP(buf, "]");
if (count > 0 && is_pretty) {
buf += print_indent(buf, level);
}
buf += sprintf(buf, "]");
return buf;
case JSONObject:
object = json_value_get_object(value);
count = json_object_get_count(object);
PRINT_AND_SKIP(buf, "{");
buf += sprintf(buf, "{");
if (count > 0 && is_pretty) {
buf += sprintf(buf, "\n");
}
for (i = 0; i < count; i++) {
key = json_object_get_name(object, i);
if (is_pretty) {
buf += print_indent(buf, level+1);
}
buf = json_serialize_string(key, buf);
if (buf == NULL)
return NULL;
PRINT_AND_SKIP(buf, ":");
buf += sprintf(buf, ":");
if (is_pretty) {
buf += sprintf(buf, " ");
}
temp_value = json_object_get_value(object, key);
buf = json_serialize_to_buffer_r(temp_value, buf);
buf = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty);
if (buf == NULL)
return NULL;
if (i < (count - 1))
PRINT_AND_SKIP(buf, ",");
buf += sprintf(buf, ",");
if (is_pretty) {
buf += sprintf(buf, "\n");
}
}
PRINT_AND_SKIP(buf, "}");
if (count > 0 && is_pretty) {
buf += print_indent(buf, level);
}
buf += sprintf(buf, "}");
return buf;
case JSONString:
string = json_value_get_string(value);
@ -828,21 +883,21 @@ char* json_serialize_to_buffer_r(const JSON_Value *value, char *buf)
return buf;
case JSONBoolean:
if (json_value_get_boolean(value)) {
PRINT_AND_SKIP(buf, "true");
buf += sprintf(buf, "true");
} else {
PRINT_AND_SKIP(buf, "false");
buf += sprintf(buf, "false");
}
return buf;
case JSONNumber:
num = json_value_get_number(value);
if (num == ((double)(int)num)) { /* check if num is integer */
PRINTF_AND_SKIP(buf, "%d", (int)num);
buf += sprintf(buf, "%d", (int)num);
} else {
PRINTF_AND_SKIP(buf, DOUBLE_SERIALIZATION_FORMAT, num);
buf += sprintf(buf, DOUBLE_SERIALIZATION_FORMAT, num);
}
return buf;
case JSONNull:
PRINT_AND_SKIP(buf, "null");
buf += sprintf(buf, "null");
return buf;
case JSONError:
return NULL;
@ -854,21 +909,21 @@ char* json_serialize_to_buffer_r(const JSON_Value *value, char *buf)
static char * json_serialize_string(const char *string, char *buf) {
size_t i = 0, len = strlen(string);
char c = '\0';
PRINT_AND_SKIP(buf, "\"")
buf += sprintf(buf, "\"");
for (i = 0; i < len; i++) {
c = string[i];
switch (c) {
case '\"': PRINT_AND_SKIP(buf, "\\\""); break;
case '\\': PRINT_AND_SKIP(buf, "\\\\"); break;
case '\b': PRINT_AND_SKIP(buf, "\\b"); break;
case '\f': PRINT_AND_SKIP(buf, "\\f"); break;
case '\n': PRINT_AND_SKIP(buf, "\\n"); break;
case '\r': PRINT_AND_SKIP(buf, "\\r"); break;
case '\t': PRINT_AND_SKIP(buf, "\\t"); break;
default: PRINTF_AND_SKIP(buf, "%c", c); break;
case '\"': buf += sprintf(buf, "\\\""); break;
case '\\': buf += sprintf(buf, "\\\\"); break;
case '\b': buf += sprintf(buf, "\\b"); break;
case '\f': buf += sprintf(buf, "\\f"); break;
case '\n': buf += sprintf(buf, "\\n"); break;
case '\r': buf += sprintf(buf, "\\r"); break;
case '\t': buf += sprintf(buf, "\\t"); break;
default: buf += sprintf(buf, "%c", c); break;
}
}
PRINT_AND_SKIP(buf, "\"");
buf += sprintf(buf, "\"");
return buf;
}
@ -1205,7 +1260,7 @@ JSON_Value * json_value_deep_copy(const JSON_Value *value) {
size_t json_serialization_size(const JSON_Value *value) {
char buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
return json_serialization_size_r(value, buf) + 1;
return json_serialization_size_r(value, buf, 0, 0) + 1;
}
JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
@ -1214,7 +1269,7 @@ JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t
if (buf_size_in_bytes < needed_size_in_bytes) {
return JSONFailure;
}
serialization_result = json_serialize_to_buffer_r(value, buf);
serialization_result = json_serialize_to_buffer_r(value, buf, 0, 0);
if(serialization_result == NULL)
return JSONFailure;
return JSONSuccess;
@ -1254,6 +1309,58 @@ char * json_serialize_to_string(const JSON_Value *value) {
return buf;
}
size_t json_serialization_size_pretty(const JSON_Value *value) {
char buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
return json_serialization_size_r(value, buf, 0, 1) + 1;
}
JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
char *serialization_result = NULL;
size_t needed_size_in_bytes = json_serialization_size_pretty(value);
if (buf_size_in_bytes < needed_size_in_bytes) {
return JSONFailure;
}
serialization_result = json_serialize_to_buffer_r(value, buf, 0, 1);
if(serialization_result == NULL)
return JSONFailure;
return JSONSuccess;
}
JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) {
JSON_Status return_code = JSONSuccess;
FILE *fp = NULL;
char *serialized_string = json_serialize_to_string_pretty(value);
if (serialized_string == NULL) {
return JSONFailure;
}
fp = fopen (filename, "w");
if (fp != NULL) {
if (fputs (serialized_string, fp) == EOF) {
return_code = JSONFailure;
}
if (fclose (fp) == EOF) {
return_code = JSONFailure;
}
}
json_free_serialized_string(serialized_string);
return return_code;
}
char * json_serialize_to_string_pretty(const JSON_Value *value) {
JSON_Status serialization_result = JSONFailure;
size_t buf_size_bytes = json_serialization_size_pretty(value);
char *buf = (char*)parson_malloc(buf_size_bytes);
if (buf == NULL)
return NULL;
serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes);
if (serialization_result == JSONFailure) {
json_free_serialized_string(buf);
return NULL;
}
return buf;
}
void json_free_serialized_string(char *string) {
parson_free(string);
}

View File

@ -79,7 +79,14 @@ size_t json_serialization_size(const JSON_Value *value);
JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename);
char * json_serialize_to_string(const JSON_Value *value);
void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string */
/* Pretty serialization */
size_t json_serialization_size_pretty(const JSON_Value *value);
JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename);
char * json_serialize_to_string_pretty(const JSON_Value *value);
void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */
/* Comparing */
int json_value_equals(const JSON_Value *a, const JSON_Value *b);

80
tests.c
View File

@ -46,12 +46,15 @@ void test_suite_4(void); /* Test deep copy funtion */
void test_suite_5(void); /* Test building json values from scratch */
void test_suite_6(void); /* Test value comparing verification */
void test_suite_7(void); /* Test schema validation */
void test_suite_8(void); /* Test serialization to file */
void test_suite_8(void); /* Test serialization */
void test_suite_9(void); /* Test serialization (pretty) */
void print_commits_info(const char *username, const char *repo);
void persistence_example(void);
void serialization_example(void);
static char * read_file(const char * filename);
static int tests_passed;
static int tests_failed;
@ -69,6 +72,7 @@ int main() {
test_suite_6();
test_suite_7();
test_suite_8();
test_suite_9();
printf("Tests failed: %d\n", tests_failed);
printf("Tests passed: %d\n", tests_passed);
return 0;
@ -78,22 +82,32 @@ void test_suite_1(void) {
JSON_Value *val;
TEST((val = json_parse_file("tests/test_1_1.txt")) != NULL);
TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val));
TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val));
if (val) { json_value_free(val); }
TEST((val = json_parse_file("tests/test_1_2.txt")) != NULL);
TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val));
TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val));
if (val) { json_value_free(val); }
TEST((val = json_parse_file("tests/test_1_3.txt")) != NULL);
TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val));
TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val));
if (val) { json_value_free(val); }
TEST((val = json_parse_file_with_comments("tests/test_1_1.txt")) != NULL);
TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val));
TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val));
if (val) { json_value_free(val); }
TEST((val = json_parse_file_with_comments("tests/test_1_2.txt")) != NULL);
TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val));
TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val));
if (val) { json_value_free(val); }
TEST((val = json_parse_file_with_comments("tests/test_1_3.txt")) != NULL);
TEST(json_value_equals(json_parse_string(json_serialize_to_string(val)), val));
TEST(json_value_equals(json_parse_string(json_serialize_to_string_pretty(val)), val));
if (val) { json_value_free(val); }
}
@ -157,6 +171,10 @@ void test_suite_2(JSON_Value *root_value) {
TEST(STREQ(json_object_get_string(root_object, "//"), "comment"));
TEST(STREQ(json_object_get_string(root_object, "url"), "https://www.example.com/search?q=12345"));
TEST(STREQ(json_object_get_string(root_object, "escaped chars"), "\" \\ /"));
TEST(json_object_get_object(root_object, "empty object") != NULL);
TEST(json_object_get_array(root_object, "empty array") != NULL);
}
void test_suite_2_no_comments(void) {
@ -165,6 +183,7 @@ void test_suite_2_no_comments(void) {
root_value = json_parse_file(filename);
test_suite_2(root_value);
TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string(root_value))));
TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string_pretty(root_value))));
json_value_free(root_value);
}
@ -174,6 +193,7 @@ void test_suite_2_with_comments(void) {
root_value = json_parse_file_with_comments(filename);
test_suite_2(root_value);
TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string(root_value))));
TEST(json_value_equals(root_value, json_parse_string(json_serialize_to_string_pretty(root_value))));
json_value_free(root_value);
}
@ -359,11 +379,37 @@ void test_suite_8(void) {
const char *temp_filename = "tests/test_2_serialized.txt";
JSON_Value *a = NULL;
JSON_Value *b = NULL;
char *buf = NULL;
size_t serialization_size = 0;
a = json_parse_file(filename);
TEST(json_serialize_to_file(a, temp_filename) == JSONSuccess);
b = json_parse_file(temp_filename);
TEST(json_value_equals(a, b));
remove(temp_filename);
serialization_size = json_serialization_size(a);
buf = json_serialize_to_string(a);
TEST((strlen(buf)+1) == serialization_size);
}
void test_suite_9(void) {
const char *filename = "tests/test_2_pretty.txt";
const char *temp_filename = "tests/test_2_serialized_pretty.txt";
char *file_contents = NULL;
char *serialized = NULL;
JSON_Value *a = NULL;
JSON_Value *b = NULL;
size_t serialization_size = 0;
a = json_parse_file(filename);
TEST(json_serialize_to_file_pretty(a, temp_filename) == JSONSuccess);
b = json_parse_file(temp_filename);
TEST(json_value_equals(a, b));
remove(temp_filename);
serialization_size = json_serialization_size_pretty(a);
serialized = json_serialize_to_string_pretty(a);
TEST((strlen(serialized)+1) == serialization_size);
file_contents = read_file(filename);
TEST(STREQ(file_contents, serialized));
}
void print_commits_info(const char *username, const char *repo) {
@ -439,3 +485,35 @@ void serialization_example(void) {
json_free_serialized_string(serialized_string);
json_value_free(root_value);
}
static char * read_file(const char * filename) {
FILE *fp = fopen(filename, "r");
size_t file_size;
long pos;
char *file_contents;
if (!fp)
return NULL;
fseek(fp, 0L, SEEK_END);
pos = ftell(fp);
if (pos < 0) {
fclose(fp);
return NULL;
}
file_size = pos;
rewind(fp);
file_contents = (char*)malloc(sizeof(char) * (file_size + 1));
if (!file_contents) {
fclose(fp);
return NULL;
}
if (fread(file_contents, file_size, 1, fp) < 1) {
if (ferror(fp)) {
fclose(fp);
free(file_contents);
return NULL;
}
}
fclose(fp);
file_contents[file_size] = '\0';
return file_contents;
}

View File

@ -24,5 +24,7 @@
"/**/" : "comment",
"//" : "comment",
"url" : "https:\/\/www.example.com\/search?q=12345",
"escaped chars" : "\" \\ \/"
"escaped chars" : "\" \\ \/",
"empty object" : {},
"empty array" : []
}

View File

@ -31,7 +31,9 @@
"/**/" : "comment",
"//" : "comment",
"url" : "https:\/\/www.example.com\/search?q=12345",
"escaped chars" : "\" \\ \/"
"escaped chars" : "\" \\ \/",
"empty object" : {},
"empty array" : []
}
/**/
//

50
tests/test_2_pretty.txt Normal file
View File

@ -0,0 +1,50 @@
{
"string": "lorem ipsum",
"utf string": "lorem ipsum",
"utf-8 string": "あいうえお",
"surrogate string": "lorem𝄞ipsum𝍧lorem",
"positive one": 1,
"negative one": -1,
"pi": 3.140000,
"hard to parse number": -0.000314,
"boolean true": true,
"boolean false": false,
"null": null,
"string array": [
"lorem",
"ipsum"
],
"x^2 array": [
0,
1,
4,
9,
16,
25,
36,
49,
64,
81,
100
],
"object_empty": {},
"/*": null,
"object": {
"nested string": "str",
"nested true": true,
"nested false": false,
"nested null": null,
"nested number": 123,
"nested array": [
"lorem",
"ipsum"
]
},
"*/": null,
"/**/": "comment",
"//": "comment",
"url": "https://www.example.com/search?q=12345",
"escaped chars": "\" \\ /",
"empty object": {},
"empty array": []
}