From b0ea647d53140bbe685424a41b2fbd9b8d688629 Mon Sep 17 00:00:00 2001 From: larsewi Date: Sat, 25 Nov 2023 20:26:38 +0100 Subject: [PATCH] Created a JSON parser --- lib/Makefile.am | 1 + lib/json.c | 491 +++++++++++++++++++++++++++++++++++++++++++++ lib/json.h | 46 +++++ lib/list.c | 1 + tests/Makefile.am | 1 + tests/check_json.c | 328 ++++++++++++++++++++++++++++++ tests/unit_test.c | 2 + 7 files changed, 870 insertions(+) create mode 100644 lib/json.c create mode 100644 lib/json.h create mode 100644 tests/check_json.c diff --git a/lib/Makefile.am b/lib/Makefile.am index ec9ce92ed..0885c5f93 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -15,6 +15,7 @@ libleech_la_SOURCES = leech.c \ block.h block.c \ buffer.h buffer.c \ csv.h csv.c \ + json.h json.c \ debug_messenger.h debug_messenger.c \ dict.h dict.c \ delta.h delta.c \ diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 000000000..31c03df0d --- /dev/null +++ b/lib/json.c @@ -0,0 +1,491 @@ +#include "json.h" + +#include +#include +#include +#include +#include + +#include "leech.h" +#include "utils.h" + +struct LCH_Json { + LCH_JsonType type; + float number; + char *str; + LCH_List *list; + LCH_Dict *object; +}; + +LCH_JsonType LCH_JsonGetType(const LCH_Json *const json) { return json->type; } + +/****************************************************************************/ + +const char *LCH_JsonStringGet(const LCH_Json *const json) { + assert(json->type == LCH_JSON_TYPE_STRING); + assert(json->str != NULL); + return json->str; +} + +/****************************************************************************/ + +const LCH_Json *LCH_JsonObjectGet(const LCH_Json *json, const char *const key) { + assert(json->type == LCH_JSON_TYPE_OBJECT); + assert(json->object != NULL); + const LCH_Json *const value = LCH_DictGet(json->object, key); + return value; +} + +size_t LCH_JsonObjectLength(const LCH_Json *json) { + assert(json->type == LCH_JSON_TYPE_OBJECT); + assert(json->object != NULL); + return LCH_DictLength(json->object); +} + +/****************************************************************************/ + +const LCH_Json *LCH_JsonListGet(const LCH_Json *const json, + const size_t index) { + assert(json->type == LCH_JSON_TYPE_LIST); + assert(json->list != NULL); + const LCH_Json *const value = LCH_ListGet(json->list, index); + return value; +} + +size_t LCH_JsonListLength(const LCH_Json *const json) { + assert(json->type == LCH_JSON_TYPE_LIST); + assert(json->list != NULL); + return LCH_ListLength(json->list); +} + +/****************************************************************************/ + +float LCH_JsonNumberGet(const LCH_Json *const json) { + assert(json->type == LCH_JSON_TYPE_NUMBER); + return json->number; +} + +/****************************************************************************/ + +static const char *JsonParse(const char *str, LCH_Json **json); + +static const char *JsonParseNull(const char *const str, LCH_Json **json) { + assert(str != NULL); + assert(strncmp(str, "null", strlen("null")) == 0); + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_NULL; + + return str + strlen("null"); +} + +static const char *JsonParseTrue(const char *const str, LCH_Json **json) { + assert(str != NULL); + assert(strncmp(str, "true", strlen("true")) == 0); + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_TRUE; + + return str + strlen("true"); +} + +static const char *JsonParseFalse(const char *const str, LCH_Json **json) { + assert(str != NULL); + assert(strncmp(str, "false", strlen("false")) == 0); + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_FALSE; + + return str + strlen("false"); +} + +static const char *BufferParseString(const char *str, LCH_Buffer **buffer) { + assert(str != NULL); + assert(*str == '"'); + + str++; // Skip initial double quote + + *buffer = LCH_BufferCreate(); + if (*buffer == NULL) { + LCH_LOG_ERROR("Failed to allocate memory for JSON string"); + return NULL; + } + + while (*str != '\0' && *str != '"') { + if (*str == '\\') { + switch (str[1]) { + case '"': + if (!LCH_BufferAppend(*buffer, '"')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case '\\': + if (!LCH_BufferAppend(*buffer, '\\')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case '/': + if (!LCH_BufferAppend(*buffer, '/')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case 'b': + if (!LCH_BufferAppend(*buffer, '\b')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case 'f': + if (!LCH_BufferAppend(*buffer, '\f')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case 'n': + if (!LCH_BufferAppend(*buffer, '\n')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case 'r': + if (!LCH_BufferAppend(*buffer, '\r')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case 't': + if (!LCH_BufferAppend(*buffer, '\t')) { + LCH_BufferDestroy(*buffer); + return NULL; + } + break; + + case 'u': + if (!LCH_BufferUnicodeToUTF8(*buffer, str + 2)) { + LCH_LOG_ERROR( + "Failed to parse JSON string: Illegal unicode control sequence " + "'%.6s'", + str); + LCH_BufferDestroy(*buffer); + } + str += 4; + break; + + default: + LCH_LOG_ERROR( + "Failed to parse JSON string: Illegal control character '\\%c'", + *str); + LCH_BufferDestroy(*buffer); + return NULL; + } + str++; + } else if (!LCH_BufferAppend(*buffer, str[0])) { + LCH_BufferDestroy(*buffer); + return NULL; + } + str++; + } + + if (*str != '"') { + LCH_LOG_ERROR( + "Failed to parse JSON string: Syntax error; expected '\"', found '%c'", + *str); + LCH_BufferDestroy(*buffer); + return NULL; + } + + return str + 1; +} + +static const char *JsonParseString(const char *str, LCH_Json **json) { + assert(str != NULL); + assert(*str == '"'); + + LCH_Buffer *buffer; + str = BufferParseString(str, &buffer); + if (str == NULL) { + return NULL; + } + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + LCH_BufferDestroy(buffer); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_STRING; + (*json)->str = LCH_BufferToString(buffer); + + return str; +} + +static const char *JsonParseObject(const char *str, LCH_Json **json) { + assert(str != NULL); + assert(*str == '{'); + + LCH_Dict *dict = LCH_DictCreate(); + if (dict == NULL) { + return NULL; + } + + // Skip initial curly brace + str++; + + // Skip whitespace + str += strspn(str, " \r\n\t"); + + bool first = true; + while (*str != '\0' && *str != '}') { + if (!first) { + // Skip comma + if (*str != ',') { + LCH_LOG_ERROR( + "Failed to parse JSON: Syntax error; expected ',', found '%c'", + *str); + LCH_DictDestroy(dict); + return NULL; + } + str++; + + // Skip whitespace + str += strspn(str, " \r\n\t"); + } + first = false; + + // Extract key + LCH_Buffer *buffer; + str = BufferParseString(str, &buffer); + if (str == NULL) { + LCH_DictDestroy(dict); + return NULL; + } + char *const key = LCH_BufferToString(buffer); + + // Skip whitespace + str += strspn(str, " \r\n\t"); + + // Skip colon + if (*str != ':') { + LCH_LOG_ERROR( + "Failed to parse JSON: Syntax error; expected ':', found '%c'", *str); + free(key); + LCH_DictDestroy(dict); + return NULL; + } + str++; + + // Extract value + LCH_Json *value; + str = JsonParse(str, &value); + if (str == NULL) { + free(key); + LCH_DictDestroy(dict); + } + + if (!LCH_DictSet(dict, key, value, (void (*)(void *))LCH_JsonDestroy)) { + free(key); + LCH_DictDestroy(dict); + LCH_JsonDestroy(value); + return NULL; + } + + free(key); + + // Skip whitespace + str += strspn(str, " \r\n\t"); + } + + if (*str != '}') { + LCH_LOG_ERROR( + "Failed to parse JSON string: Syntax error; expected '}', found '%c'", + *str); + LCH_DictDestroy(dict); + return NULL; + } + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + LCH_DictDestroy(dict); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_OBJECT; + (*json)->object = dict; + + return str + 1; +} + +static const char *JsonParseList(const char *str, LCH_Json **json) { + assert(str != NULL); + assert(*str == '['); + + LCH_List *list = LCH_ListCreate(); + if (list == NULL) { + return NULL; + } + + // Skip initial square bracket + str++; + + // Skip whitespace + str += strspn(str, " \r\n\t"); + + bool first = true; + while (*str != '\0' && *str != ']') { + if (!first) { + // Skip comma + if (*str != ',') { + LCH_LOG_ERROR( + "Failed to parse JSON: Syntax error; expected ',', found '%c'", + *str); + LCH_ListDestroy(list); + return NULL; + } + str++; + + // Skip whitespace + str += strspn(str, " \r\n\t"); + } + first = false; + + // Extract value + LCH_Json *value; + str = JsonParse(str, &value); + if (str == NULL) { + LCH_ListDestroy(list); + return NULL; + } + + if (!LCH_ListAppend(list, value, (void (*)(void *))LCH_JsonDestroy)) { + LCH_JsonDestroy(value); + LCH_ListDestroy(list); + return NULL; + } + + // Skip whitespace + str += strspn(str, " \r\n\t"); + } + + if (*str != ']') { + LCH_LOG_ERROR( + "Failed to parse JSON string: Syntax error; expected ']', found '%c'", + *str); + LCH_ListDestroy(list); + return NULL; + } + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + LCH_ListDestroy(list); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_LIST; + (*json)->list = list; + + return str + 1; +} + +static const char *JsonParseNumber(const char *const str, LCH_Json **json) { + int n_chars; + float number; + int ret = sscanf(str, "%e%n", &number, &n_chars); + if (ret != 1) { + LCH_LOG_ERROR( + "Failed to parse JSON string: Syntax error; expected a number in the " + "format [-]d.ddde±dd"); + return NULL; + } + + *json = calloc(1, sizeof(LCH_Json)); + if (*json == NULL) { + LCH_LOG_ERROR("Failed to allocate memeory for JSON data structure: %s", + strerror(errno)); + return NULL; + } + (*json)->type = LCH_JSON_TYPE_NUMBER; + (*json)->number = number; + + return str + n_chars; +} + +static const char *JsonParse(const char *str, LCH_Json **json) { + assert(str != NULL); + + str += strspn(str, " \r\n\t"); // Skip whitespace + + if (strncmp(str, "null", strlen("null")) == 0) { + return JsonParseNull(str, json); + } + if (strncmp(str, "true", strlen("true")) == 0) { + return JsonParseTrue(str, json); + } + if (strncmp(str, "false", strlen("false")) == 0) { + return JsonParseFalse(str, json); + } + if (*str == '"') { + return JsonParseString(str, json); + } + if (*str == '{') { + return JsonParseObject(str, json); + } + if (*str == '[') { + return JsonParseList(str, json); + } + if (isdigit(*str) != 0 || *str == '-') { + return JsonParseNumber(str, json); + } else { + LCH_LOG_ERROR( + "Failed to parse JSON: Expected 'null', 'true', 'false', NUMBER, " + "STRING, OBJECT, LIST; found '%c'", + *str); + return NULL; + } +} + +LCH_Json *LCH_JsonParse(const char *const str) { + assert(str != NULL); + + LCH_Json *json; + const char *const ret = JsonParse(str, &json); + return (ret == NULL) ? NULL : json; +} + +/****************************************************************************/ + +void LCH_JsonDestroy(LCH_Json *const json) { + if (json != NULL) { + free(json->str); + LCH_ListDestroy(json->list); + LCH_DictDestroy(json->object); + } + free(json); +} diff --git a/lib/json.h b/lib/json.h new file mode 100644 index 000000000..eab6531e5 --- /dev/null +++ b/lib/json.h @@ -0,0 +1,46 @@ +#ifndef _LEECH_JSON_H +#define _LEECH_JSON_H + +#include + +typedef struct LCH_Json LCH_Json; + +typedef enum { + LCH_JSON_TYPE_NULL, + LCH_JSON_TYPE_TRUE, + LCH_JSON_TYPE_FALSE, + LCH_JSON_TYPE_STRING, + LCH_JSON_TYPE_NUMBER, + LCH_JSON_TYPE_LIST, + LCH_JSON_TYPE_OBJECT, +} LCH_JsonType; + +LCH_JsonType LCH_JsonGetType(const LCH_Json *json); + +/****************************************************************************/ + +const char *LCH_JsonStringGet(const LCH_Json *json); + +/****************************************************************************/ + +float LCH_JsonNumberGet(const LCH_Json *json); + +/****************************************************************************/ + +const LCH_Json *LCH_JsonObjectGet(const LCH_Json *json, const char *key); + +size_t LCH_JsonObjectLength(const LCH_Json *json); + +/****************************************************************************/ + +const LCH_Json *LCH_JsonListGet(const LCH_Json *json, size_t index); + +size_t LCH_JsonObjectLength(const LCH_Json *json); + +/****************************************************************************/ + +LCH_Json *LCH_JsonParse(const char *str); + +void LCH_JsonDestroy(LCH_Json *json); + +#endif // _LEECH_JSON_H diff --git a/lib/list.c b/lib/list.c index e3d13c6eb..b2693c370 100644 --- a/lib/list.c +++ b/lib/list.c @@ -183,6 +183,7 @@ void LCH_ListDestroy(LCH_List *self) { for (size_t i = 0; i < self->length; i++) { ListElement *item = self->buffer[i]; + assert(item != NULL); if (item->destroy != NULL) { item->destroy(item->value); } diff --git a/tests/Makefile.am b/tests/Makefile.am index 0c50df848..5c62d6a70 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -8,6 +8,7 @@ unit_test_SOURCES = \ check_block.c \ check_buffer.c \ check_csv.c \ + check_json.c \ check_delta.c \ check_dict.c \ check_head.c \ diff --git a/tests/check_json.c b/tests/check_json.c new file mode 100644 index 000000000..43dada971 --- /dev/null +++ b/tests/check_json.c @@ -0,0 +1,328 @@ +#include +#include + +#include "../lib/json.c" + +START_TEST(test_JsonParseNull) { + const char *str = "nullnulltull"; + LCH_Json *json; + str = JsonParseNull(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_str_eq(str, "nulltull"); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NULL); + LCH_JsonDestroy(json); + + str = JsonParseNull(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_str_eq(str, "tull"); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NULL); + LCH_JsonDestroy(json); +} +END_TEST + +START_TEST(test_JsonParseTrue) { + const char *str = "truetruetull"; + LCH_Json *json; + str = JsonParseTrue(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_str_eq(str, "truetull"); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_TRUE); + LCH_JsonDestroy(json); + + str = JsonParseTrue(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_str_eq(str, "tull"); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_TRUE); + LCH_JsonDestroy(json); +} +END_TEST + +START_TEST(test_JsonParseFalse) { + const char *str = "falsefalsetull"; + LCH_Json *json; + str = JsonParseFalse(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_str_eq(str, "falsetull"); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_FALSE); + LCH_JsonDestroy(json); + + str = JsonParseFalse(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_str_eq(str, "tull"); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_FALSE); + LCH_JsonDestroy(json); +} +END_TEST + +START_TEST(test_JsonParseString) { + const char *str = + "\"Hello World\"" + "\" \\\" \\\\ \\/ \\b \\f \\n \\r \\t \"" + "\"\\u0041\\u0100\\u0101\"" + "bogus"; + LCH_Json *json; + + str = JsonParseString(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(json), "Hello World"); + LCH_JsonDestroy(json); + + str = JsonParseString(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(json), " \" \\ / \b \f \n \r \t "); + LCH_JsonDestroy(json); + + str = JsonParseString(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(json), "AĀā"); + LCH_JsonDestroy(json); + + ck_assert_str_eq(str, "bogus"); +} +END_TEST + +START_TEST(test_JsonParseObject) { + const char *str = + "{}" + "{ }" + "{ \"name\" : \"leech\" }" + "{ \"name\" : \"leech\", \"version\":\"1.2.3\"}" + "bogus"; + LCH_Json *json; + + str = JsonParseObject(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_OBJECT); + ck_assert_int_eq(LCH_JsonObjectLength(json), 0); + LCH_JsonDestroy(json); + + str = JsonParseObject(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_OBJECT); + ck_assert_int_eq(LCH_JsonObjectLength(json), 0); + LCH_JsonDestroy(json); + + str = JsonParseObject(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_OBJECT); + ck_assert_int_eq(LCH_JsonObjectLength(json), 1); + const LCH_Json *child = LCH_JsonObjectGet(json, "name"); + ck_assert_ptr_nonnull(child); + ck_assert_int_eq(LCH_JsonGetType(child), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(child), "leech"); + LCH_JsonDestroy(json); + + str = JsonParseObject(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_OBJECT); + ck_assert_int_eq(LCH_JsonObjectLength(json), 2); + child = LCH_JsonObjectGet(json, "name"); + ck_assert_ptr_nonnull(child); + ck_assert_int_eq(LCH_JsonGetType(child), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(child), "leech"); + child = LCH_JsonObjectGet(json, "version"); + ck_assert_ptr_nonnull(child); + ck_assert_int_eq(LCH_JsonGetType(child), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(child), "1.2.3"); + LCH_JsonDestroy(json); + + ck_assert_str_eq(str, "bogus"); +} +END_TEST + +START_TEST(test_JsonParseList) { + const char *str = + "[]" + "[ ]" + "[\"leech\"]" + "[\"leech\" , \"1.2.3\"]" + "bogus"; + LCH_Json *json; + const LCH_Json *child; + + str = JsonParseList(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_LIST); + ck_assert_int_eq(LCH_JsonListLength(json), 0); + LCH_JsonDestroy(json); + + str = JsonParseList(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_LIST); + ck_assert_int_eq(LCH_JsonListLength(json), 0); + LCH_JsonDestroy(json); + + str = JsonParseList(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_LIST); + ck_assert_int_eq(LCH_JsonListLength(json), 1); + child = LCH_JsonListGet(json, 0); + ck_assert_int_eq(LCH_JsonGetType(child), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(child), "leech"); + LCH_JsonDestroy(json); + + str = JsonParseList(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_LIST); + ck_assert_int_eq(LCH_JsonListLength(json), 2); + child = LCH_JsonListGet(json, 0); + ck_assert_int_eq(LCH_JsonGetType(child), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(child), "leech"); + child = LCH_JsonListGet(json, 1); + ck_assert_int_eq(LCH_JsonGetType(child), LCH_JSON_TYPE_STRING); + ck_assert_str_eq(LCH_JsonStringGet(child), "1.2.3"); + LCH_JsonDestroy(json); +} +END_TEST + +START_TEST(test_JsonParseNumber) { + const char *str = + "123" + " 123.45" + " -123.45" + " 123e4" + " 123E-4" + " 123E+4" + " 123.4e-5"; + LCH_Json *json; + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + float number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, 123.0f); + LCH_JsonDestroy(json); + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, 123.45f); + LCH_JsonDestroy(json); + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, -123.45f); + LCH_JsonDestroy(json); + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, 123e4f); + LCH_JsonDestroy(json); + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, 123e-4f); + LCH_JsonDestroy(json); + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, 123e4f); + LCH_JsonDestroy(json); + + str = JsonParseNumber(str, &json); + ck_assert_ptr_nonnull(str); + ck_assert_ptr_nonnull(json); + ck_assert_int_eq(LCH_JsonGetType(json), LCH_JSON_TYPE_NUMBER); + number = LCH_JsonNumberGet(json); + ck_assert_float_eq(number, 123.4e-5f); + LCH_JsonDestroy(json); +} +END_TEST + +START_TEST(test_LCH_JsonParse) { + const char *const str = + " { " + "\"name\": \"lars\"," + "\"height\": 185.3," + "\"skills\": [ \"C\", \"C++\" ] ," + "\"awesome\": true" + " } "; + LCH_Json *json = LCH_JsonParse(str); + float height = LCH_JsonNumberGet(LCH_JsonObjectGet(json, "height")); + ck_assert_float_eq(height, 185.3f); + const char *skill = + LCH_JsonStringGet(LCH_JsonListGet(LCH_JsonObjectGet(json, "skills"), 1)); + ck_assert_str_eq(skill, "C++"); + LCH_JsonDestroy(json); +} +END_TEST + +Suite *JSONSuite(void) { + Suite *s = suite_create("json.c"); + { + TCase *tc = tcase_create("JsonParseNull"); + tcase_add_test(tc, test_JsonParseNull); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("JsonParseTrue"); + tcase_add_test(tc, test_JsonParseTrue); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("JsonParseFalse"); + tcase_add_test(tc, test_JsonParseFalse); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("JsonParseString"); + tcase_add_test(tc, test_JsonParseString); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("JsonParseObject"); + tcase_add_test(tc, test_JsonParseObject); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("JsonParseList"); + tcase_add_test(tc, test_JsonParseList); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("JsonParseNumber"); + tcase_add_test(tc, test_JsonParseNumber); + suite_add_tcase(s, tc); + } + { + TCase *tc = tcase_create("LCH_JsonParse"); + tcase_add_test(tc, test_LCH_JsonParse); + suite_add_tcase(s, tc); + } + return s; +} diff --git a/tests/unit_test.c b/tests/unit_test.c index 5090c28c2..78cea173e 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -9,6 +9,7 @@ static void SetupDebugMessenger(void); Suite *BlockSuite(void); Suite *BufferSuite(void); Suite *CSVSuite(void); +Suite *JSONSuite(void); Suite *DeltaSuite(void); Suite *DictSuite(void); Suite *HeadSuite(void); @@ -27,6 +28,7 @@ int main(int argc, char *argv[]) { srunner_add_suite(sr, DeltaSuite()); srunner_add_suite(sr, BlockSuite()); srunner_add_suite(sr, CSVSuite()); + srunner_add_suite(sr, JSONSuite()); srunner_add_suite(sr, LeechCSVSuite()); srunner_add_suite(sr, TableSuite());