├── .gitignore ├── .gitattributes ├── .editorconfig ├── post-example-build.cmake ├── tests ├── test_parse_null.c ├── test_parse_empty.c ├── test.h ├── test.c ├── test_stringify_array.c ├── test_prettify.c ├── test_stringify_object.c ├── test_stringify_primitives.c ├── test_parse_primitives.c ├── test_parse_array.c └── test_parse_object.c ├── CHANGELOG.md ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTING.md └── workflows │ └── ci.yml ├── include └── json.h ├── examples └── example.c ├── CMakeLists.txt ├── README.md ├── uncrustify.cfg ├── LICENSE └── src └── json.c /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | temp/ 3 | **/*.log 4 | /core 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.ico binary 3 | *.woff binary 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | -------------------------------------------------------------------------------- /post-example-build.cmake: -------------------------------------------------------------------------------- 1 | 2 | include("./cmake-modules/src/utils.cmake") 3 | 4 | utils_embed_example_source_in_readme( 5 | EXAMPLE_FILE "../examples/example.c" 6 | DOCUMENT_FILE "../README.md" 7 | SOURCE_TYPE "c" 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /tests/test_parse_null.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value = json_parse(NULL); 7 | 8 | assert_true(value == NULL); 9 | } 10 | 11 | 12 | int main() 13 | { 14 | test_run(test_impl); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /tests/test_parse_empty.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | 8 | value = json_parse(""); 9 | assert_true(value == NULL); 10 | 11 | value = json_parse(" "); 12 | assert_true(value == NULL); 13 | } 14 | 15 | 16 | int main() 17 | { 18 | test_run(test_impl); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### v0.3.1 (2023-10-22) 4 | 5 | * Fix unicode escape detection 6 | 7 | ### v0.3.0 (2023-09-25) 8 | 9 | * Make numeric stringify be compatible with standard json libraries 10 | 11 | ### v0.2.0 (2023-02-06) 12 | 13 | * Renamed release function 14 | 15 | ### v0.1.0 (2023-02-06) 16 | 17 | * Initial release 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: sagiegurari 7 | 8 | --- 9 | 10 | ### Feature Description 11 | 12 | 13 | ### Describe The Solution You'd Like 14 | 15 | 16 | ### Code Sample 17 | 18 | ```c 19 | // paste code here 20 | ``` 21 | -------------------------------------------------------------------------------- /tests/test.h: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_H__ 2 | #define __TEST_H__ 3 | 4 | #include "json.h" 5 | #include 6 | #include 7 | #include 8 | 9 | void test_run(void (*fn)()); 10 | void test_fail(); 11 | void assert_true(bool); 12 | void assert_true_with_description(bool, char *); 13 | 14 | void assert_size_equal(size_t, size_t); 15 | void assert_num_equal(long double, long double); 16 | void assert_string_equal(char *, char *); 17 | 18 | #endif 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: sagiegurari 7 | 8 | --- 9 | 10 | ### Describe The Bug 11 | 12 | 13 | ### To Reproduce 14 | 15 | 16 | ### Error Stack 17 | 18 | ```console 19 | The error stack trace 20 | ``` 21 | 22 | ### Code Sample 23 | 24 | ```c 25 | // paste code here 26 | ``` 27 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Issues 4 | 5 | Found a bug? Got a question? Want some enhancement?
6 | First place to go is the repository issues section, and I'll try to help as much as possible. 7 | 8 | ## Pull Requests 9 | 10 | Fixed a bug or just want to provided additional functionality?
11 | Simply fork this repository, implement your changes and create a pull request.
12 | Few guidelines regarding pull requests: 13 | 14 | * This repository is integrated with github actions for continuous integration.
15 | 16 | Your pull request build must pass (the build will run automatically).
17 | You can run the following command locally to ensure the build will pass: 18 | 19 | ```sh 20 | mkdir ./target 21 | cd ./target 22 | cmake .. 23 | make 24 | ctest -C Release --output-on-failure 25 | ``` 26 | 27 | * There are many automatic unit tests as part of the library which provide full coverage of the functionality.
Any fix/enhancement must come with a set of tests to ensure it's working well. 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | env: 4 | BUILD_TYPE: Release 5 | jobs: 6 | ci: 7 | name: CI 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macOS-latest] 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | - name: Setup Env 17 | run: cmake -E make_directory target 18 | - name: Configure 19 | shell: bash 20 | working-directory: target 21 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 22 | - name: Build 23 | shell: bash 24 | working-directory: target 25 | run: cmake --build . --config $BUILD_TYPE 26 | - name: Test 27 | shell: bash 28 | working-directory: target 29 | run: ctest -C $BUILD_TYPE --output-on-failure 30 | - name: Memory Leak Test 31 | if: matrix.os == 'ubuntu-latest' 32 | shell: bash 33 | working-directory: target 34 | run: | 35 | sudo apt update 36 | sudo apt install -y valgrind --fix-missing 37 | for testfile in ./bin/test_*; do echo "Testing ${testfile}" && valgrind --leak-check=full --show-leak-kinds=definite,indirect,possible --error-exitcode=1 "${testfile}"; done 38 | 39 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | void test_run(void (*fn)(void)) 8 | { 9 | printf("Test ... "); 10 | fn(); 11 | printf("Done\n"); 12 | } 13 | 14 | 15 | void test_fail() 16 | { 17 | printf(" Error\n"); 18 | exit(1); 19 | } 20 | 21 | 22 | void assert_true(bool value) 23 | { 24 | assert_true_with_description(value, NULL); 25 | } 26 | 27 | 28 | void assert_true_with_description(bool value, char *description) 29 | { 30 | if (!value) 31 | { 32 | if (description != NULL) 33 | { 34 | printf("Assert Failed, %s", description); 35 | } 36 | 37 | test_fail(); 38 | } 39 | } 40 | 41 | 42 | void assert_size_equal(size_t value1, size_t value2) 43 | { 44 | if (value1 != value2) 45 | { 46 | #ifdef linux 47 | printf("Assert Failed, value: %zu not equals to value: %zu", value1, value2); 48 | #endif 49 | test_fail(); 50 | } 51 | } 52 | 53 | 54 | void assert_num_equal(long double value1, long double value2) 55 | { 56 | if (value1 != value2) 57 | { 58 | #ifdef linux 59 | printf("Assert Failed, value: %Lf not equals to value: %Lf", value1, value2); 60 | #endif 61 | test_fail(); 62 | } 63 | } 64 | 65 | 66 | void assert_string_equal(char *value1, char *value2) 67 | { 68 | if (strcmp(value1, value2) != 0) 69 | { 70 | printf("Assert Failed, value: %s not equals to value: %s", value1, value2); 71 | test_fail(); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /tests/test_stringify_array.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | char *string; 8 | 9 | value = json_parse("[]"); 10 | string = json_stringify(value); 11 | assert_string_equal(string, "[]"); 12 | free(string); 13 | string = json_stringify_with_options(value, true, 2); 14 | assert_string_equal(string, "[\n" 15 | "]"); 16 | free(string); 17 | json_release(value); 18 | 19 | value = json_parse("[1,2,\"test\",false,true,null,12.5,[true,false,[{\"key\":\"value\"},true]]]"); 20 | string = json_stringify(value); 21 | assert_string_equal(string, "[1,2,\"test\",false,true,null,12.5,[true,false,[{\"key\":\"value\"},true]]]"); 22 | free(string); 23 | string = json_stringify_with_options(value, true, 2); 24 | assert_string_equal(string, "[\n" 25 | " 1,\n" 26 | " 2,\n" 27 | " \"test\",\n" 28 | " false,\n" 29 | " true,\n" 30 | " null,\n" 31 | " 12.5,\n" 32 | " [\n" 33 | " true,\n" 34 | " false,\n" 35 | " [\n" 36 | " {\n" 37 | " \"key\": \"value\"\n" 38 | " },\n" 39 | " true\n" 40 | " ]\n" 41 | " ]\n" 42 | "]"); 43 | free(string); 44 | json_release(value); 45 | } /* test_impl */ 46 | 47 | 48 | int main() 49 | { 50 | test_run(test_impl); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /tests/test_prettify.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | char *string; 7 | 8 | string = json_prettify("{}", true, 2); 9 | assert_string_equal(string, "{\n" 10 | "}"); 11 | free(string); 12 | 13 | string = json_prettify("{\"key1\":1,\"key2\":\"test\",\"key3\":false,\"key4\":true,\"key5\":null,\"key6\":12.5,\"key7\":[true,false,[true, {\"sub\":1,\"a\":[true]}, {\"sub\":1,\"b\":[true,[true]]}, 1]]]", true, 2); 14 | assert_string_equal(string, "{\n" 15 | " \"key1\": 1,\n" 16 | " \"key2\": \"test\",\n" 17 | " \"key3\": false,\n" 18 | " \"key4\": true,\n" 19 | " \"key5\": null,\n" 20 | " \"key6\": 12.5,\n" 21 | " \"key7\": [\n" 22 | " true,\n" 23 | " false,\n" 24 | " [\n" 25 | " true,\n" 26 | " {\n" 27 | " \"a\": [\n" 28 | " true\n" 29 | " ],\n" 30 | " \"sub\": 1\n" 31 | " },\n" 32 | " {\n" 33 | " \"b\": [\n" 34 | " true,\n" 35 | " [\n" 36 | " true\n" 37 | " ]\n" 38 | " ],\n" 39 | " \"sub\": 1\n" 40 | " },\n" 41 | " 1\n" 42 | " ]\n" 43 | " ]\n" 44 | "}"); 45 | free(string); 46 | } /* test_impl */ 47 | 48 | 49 | int main() 50 | { 51 | test_run(test_impl); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /include/json.h: -------------------------------------------------------------------------------- 1 | #ifndef JSON_H 2 | #define JSON_H 3 | 4 | #include "hashtable.h" 5 | #include "vector.h" 6 | #include 7 | #include 8 | 9 | enum JsonType 10 | { 11 | JSON_TYPE_OBJECT = 1, 12 | JSON_TYPE_ARRAY, 13 | JSON_TYPE_STRING, 14 | JSON_TYPE_NUMBER, 15 | JSON_TYPE_BOOLEAN, 16 | JSON_TYPE_NULL 17 | }; 18 | 19 | struct JsonValue 20 | { 21 | enum JsonType type; 22 | union JsonValueUnion *value; 23 | }; 24 | 25 | union JsonValueUnion 26 | { 27 | struct HashTable *object; 28 | struct Vector *array; 29 | char *string; 30 | long double number; 31 | bool boolean; 32 | }; 33 | 34 | /** 35 | * Parses the give string and returns the json value union. 36 | * In case of any error, this function will return null. 37 | * The json value must be fully released once done. 38 | */ 39 | struct JsonValue *json_parse(char * /* text */); 40 | 41 | /** 42 | * Converts the provided json value to string without any special formatting. 43 | * In case of any error or invalid value, NULL will be returned. 44 | */ 45 | char *json_stringify(struct JsonValue *); 46 | 47 | /** 48 | * Converts the provided json value to string with formatting based on the provided options. 49 | * In case of any error or invalid value, NULL will be returned. 50 | */ 51 | char *json_stringify_with_options(struct JsonValue *, bool /* multi line */, size_t /* indentation */); 52 | 53 | /** 54 | * Parses the provided json string and stringifies it back with 55 | * the provided formatting options. 56 | */ 57 | char *json_prettify(char *, bool /* multi line */, size_t /* indentation */); 58 | 59 | /** 60 | * Releases the json value and all internal memory used. 61 | * All internal strings will also be released, therefore no const strings 62 | * or freed strings must reside in the structure. 63 | */ 64 | void json_release(struct JsonValue *); 65 | 66 | #endif 67 | 68 | -------------------------------------------------------------------------------- /examples/example.c: -------------------------------------------------------------------------------- 1 | #include "json.h" 2 | #include 3 | #include 4 | 5 | 6 | int main() 7 | { 8 | // parsing the json string 9 | struct JsonValue *value = json_parse("{\"number\":1.6, \"null_key\" : null,\n" 10 | "\"bool_true\": true,\"bool_false\":false\n" 11 | ",\"string\": \"my string\nsecond line\" ,\n" 12 | " \"arr\": [1, 2.7, 3], \n" 13 | "\"obj\": {\"subkey\": 88}, \"subobj\": {\"sub\":{\"subkey\": 77}}}"); 14 | 15 | struct JsonValue *sub_value; 16 | 17 | 18 | // objects are converted to hashtables, so need to use the hashtable api to access/modify 19 | sub_value = hashtable_get(value->value->object, "string"); 20 | 21 | // The actual value is based on the type 22 | printf("string value: %s\n", sub_value->value->string); 23 | 24 | // arrays are converted to vectors, so need to use the vector api to access/modify 25 | sub_value = hashtable_get(value->value->object, "arr"); 26 | sub_value = vector_get(sub_value->value->array, 0); 27 | printf("array[0] value: %Lf\n", sub_value->value->number); 28 | 29 | // you can convert the parsed object back to string 30 | // The below is the same as json_stringify_with_options(value, false, 0); 31 | char *json_string = json_stringify(value); 32 | printf("JSON string:\n%s\n", json_string); 33 | free(json_string); 34 | 35 | // once done, release the parsed object 36 | json_release(value); 37 | 38 | // you can also prettify json strings which will 39 | // parse them and stringify them back with the 40 | // provided formatting options 41 | json_string = json_prettify("{\"key1\":1,\"key2\":\"test\",\"key3\":false,\"key4\":true,\"key5\":null,\"key6\":12.5,\"key7\":[true,false,[true, {\"sub\":1,\"a\":[true]}, {\"sub\":1,\"b\":[true,[true]]}, 1]]]", true, 2); 42 | printf("JSON string:\n%s\n", json_string); 43 | 44 | return(0); 45 | } /* main */ 46 | -------------------------------------------------------------------------------- /tests/test_stringify_object.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | char *string; 8 | 9 | value = json_parse("{}"); 10 | string = json_stringify(value); 11 | assert_string_equal(string, "{}"); 12 | free(string); 13 | string = json_stringify_with_options(value, true, 2); 14 | assert_string_equal(string, "{\n" 15 | "}"); 16 | free(string); 17 | json_release(value); 18 | 19 | value = json_parse("{\"key1\":1,\"key2\":\"test\",\"key3\":false,\"key4\":true,\"key5\":null,\"key6\":12.5,\"key7\":[true,false,[true, {\"sub\":1,\"a\":[true]}, {\"sub\":1,\"b\":[true,[true]]}, 1]]]"); 20 | string = json_stringify(value); 21 | assert_string_equal(string, "{\"key1\":1,\"key2\":\"test\",\"key3\":false,\"key4\":true,\"key5\":null,\"key6\":12.5,\"key7\":[true,false,[true,{\"a\":[true],\"sub\":1},{\"b\":[true,[true]],\"sub\":1},1]]}"); 22 | free(string); 23 | string = json_stringify_with_options(value, true, 2); 24 | assert_string_equal(string, "{\n" 25 | " \"key1\": 1,\n" 26 | " \"key2\": \"test\",\n" 27 | " \"key3\": false,\n" 28 | " \"key4\": true,\n" 29 | " \"key5\": null,\n" 30 | " \"key6\": 12.5,\n" 31 | " \"key7\": [\n" 32 | " true,\n" 33 | " false,\n" 34 | " [\n" 35 | " true,\n" 36 | " {\n" 37 | " \"a\": [\n" 38 | " true\n" 39 | " ],\n" 40 | " \"sub\": 1\n" 41 | " },\n" 42 | " {\n" 43 | " \"b\": [\n" 44 | " true,\n" 45 | " [\n" 46 | " true\n" 47 | " ]\n" 48 | " ],\n" 49 | " \"sub\": 1\n" 50 | " },\n" 51 | " 1\n" 52 | " ]\n" 53 | " ]\n" 54 | "}"); 55 | free(string); 56 | json_release(value); 57 | } /* test_impl */ 58 | 59 | 60 | int main() 61 | { 62 | test_run(test_impl); 63 | } 64 | 65 | -------------------------------------------------------------------------------- /tests/test_stringify_primitives.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | char *string; 8 | 9 | value = json_parse("null"); 10 | string = json_stringify(value); 11 | assert_string_equal(string, "null"); 12 | free(string); 13 | string = json_stringify_with_options(value, true, 2); 14 | assert_string_equal(string, "null"); 15 | free(string); 16 | json_release(value); 17 | 18 | value = json_parse("true"); 19 | string = json_stringify(value); 20 | assert_string_equal(string, "true"); 21 | free(string); 22 | string = json_stringify_with_options(value, true, 2); 23 | assert_string_equal(string, "true"); 24 | free(string); 25 | json_release(value); 26 | 27 | value = json_parse("false"); 28 | string = json_stringify(value); 29 | assert_string_equal(string, "false"); 30 | free(string); 31 | string = json_stringify_with_options(value, true, 2); 32 | assert_string_equal(string, "false"); 33 | free(string); 34 | json_release(value); 35 | 36 | value = json_parse("0"); 37 | string = json_stringify(value); 38 | assert_string_equal(string, "0"); 39 | free(string); 40 | string = json_stringify_with_options(value, true, 2); 41 | assert_string_equal(string, "0"); 42 | free(string); 43 | json_release(value); 44 | 45 | value = json_parse("12345"); 46 | string = json_stringify(value); 47 | assert_string_equal(string, "12345"); 48 | free(string); 49 | string = json_stringify_with_options(value, true, 2); 50 | assert_string_equal(string, "12345"); 51 | free(string); 52 | json_release(value); 53 | 54 | value = json_parse("123.401"); 55 | string = json_stringify(value); 56 | assert_string_equal(string, "123.401"); 57 | free(string); 58 | string = json_stringify_with_options(value, true, 2); 59 | assert_string_equal(string, "123.401"); 60 | free(string); 61 | json_release(value); 62 | 63 | value = json_parse("-12345"); 64 | string = json_stringify(value); 65 | assert_string_equal(string, "-12345"); 66 | free(string); 67 | string = json_stringify_with_options(value, true, 2); 68 | assert_string_equal(string, "-12345"); 69 | free(string); 70 | json_release(value); 71 | 72 | value = json_parse("\"first\\\\line\\n\\n" 73 | "second line\\tafter tab\""); 74 | string = json_stringify(value); 75 | assert_string_equal(string, "\"first\\\\line\\n\\n" 76 | "second line\\tafter tab\""); 77 | free(string); 78 | string = json_stringify_with_options(value, true, 2); 79 | assert_string_equal(string, "\"first\\\\line\\n\\n" 80 | "second line\\tafter tab\""); 81 | free(string); 82 | json_release(value); 83 | } /* test_impl */ 84 | 85 | 86 | int main() 87 | { 88 | test_run(test_impl); 89 | } 90 | 91 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.7) 3 | 4 | project(json C) 5 | 6 | # include shared utilities 7 | if(NOT EXISTS "target/cmake-modules/src/utils.cmake") 8 | execute_process(COMMAND git clone https://github.com/sagiegurari/cmake-modules.git) 9 | endif() 10 | include("target/cmake-modules/src/utils.cmake") 11 | 12 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 13 | 14 | set(CMAKE_BUILD_TYPE Release) 15 | if(NOT WIN32) 16 | set(X_CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wall -Wextra -Wcast-align -Wunused -Wshadow -Wpedantic") 17 | endif() 18 | 19 | set(X_CMAKE_PROJECT_ROOT_DIR ${CMAKE_BINARY_DIR}/..) 20 | 21 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 22 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 23 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 24 | 25 | macro(add_external_lib) 26 | utils_add_external_github_lib( 27 | REPO_USERNAME sagiegurari 28 | REPO_NAME c_${ARGV0} 29 | TAG_NAME ${ARGV1} 30 | LIBRARY_NAME ${ARGV0} 31 | LIBRARY_PARENT_DIRECTORY target 32 | ) 33 | endmacro(add_external_lib) 34 | add_external_lib("string_buffer") 35 | add_external_lib("stringfn") 36 | add_external_lib("vector") 37 | add_external_lib("hashtable") 38 | 39 | include_directories(include "${STRING_BUFFER_INCLUDE}" "${STRINGFN_INCLUDE}" "${VECTOR_INCLUDE}" "${HASHTABLE_INCLUDE}") 40 | 41 | # define all sources 42 | file(GLOB SOURCES "src/*.c") 43 | file(GLOB HEADER_SOURCES "include/*.h") 44 | file(GLOB TEST_SOURCES "tests/*") 45 | file(GLOB COMMON_TEST_SOURCES "tests/test.*") 46 | file(GLOB EXAMPLE_SOURCES "examples/*.c") 47 | 48 | # lint code 49 | utils_cppcheck(INCLUDE_DIRECTORY "./include/" SOURCES "./src/*.c" WORKING_DIRECTORY "${X_CMAKE_PROJECT_ROOT_DIR}") 50 | 51 | # format code 52 | utils_uncrustify( 53 | CONFIG_FILE "${X_CMAKE_PROJECT_ROOT_DIR}/uncrustify.cfg" 54 | SOURCES ${SOURCES} ${HEADER_SOURCES} ${TEST_SOURCES} ${EXAMPLE_SOURCES} 55 | ) 56 | 57 | # create static library 58 | add_library(${CMAKE_PROJECT_NAME} STATIC ${SOURCES} ${STRING_BUFFER_SOURCES} ${STRINGFN_SOURCES} ${VECTOR_SOURCES} ${HASHTABLE_SOURCES}) 59 | if(NOT WIN32) 60 | set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES COMPILE_FLAGS "${X_CMAKE_C_FLAGS} -Wconversion") 61 | endif() 62 | 63 | # example 64 | add_executable(example examples/example.c) 65 | target_link_libraries(example ${CMAKE_PROJECT_NAME}) 66 | set_target_properties(example PROPERTIES COMPILE_FLAGS "${X_CMAKE_C_FLAGS}") 67 | 68 | # tests 69 | include(CTest) 70 | 71 | utils_setup_test_lib( 72 | SOURCES "${COMMON_TEST_SOURCES}" 73 | COMPILATION_FLAGS "${X_CMAKE_C_FLAGS}" 74 | ) 75 | utils_setup_c_all_tests( 76 | COMPILATION_FLAGS "${X_CMAKE_C_FLAGS}" 77 | BINARY_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 78 | LIBRARIES "Test" 79 | ) 80 | 81 | if("$ENV{X_CMAKE_DOC_STEPS}" STREQUAL "true") 82 | # post build steps 83 | add_custom_command( 84 | TARGET example 85 | POST_BUILD 86 | COMMENT "Post Build Steps" 87 | COMMAND ${CMAKE_COMMAND} -P "../post-example-build.cmake" 88 | ) 89 | endif() 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # json 2 | 3 | [![CI](https://github.com/sagiegurari/c_json/workflows/CI/badge.svg?branch=master)](https://github.com/sagiegurari/c_json/actions) 4 | [![Release](https://img.shields.io/github/v/release/sagiegurari/c_json)](https://github.com/sagiegurari/c_json/releases) 5 | [![license](https://img.shields.io/github/license/sagiegurari/c_json)](https://github.com/sagiegurari/c_json/blob/master/LICENSE) 6 | 7 | > JSON parser/writer for C. 8 | 9 | * [Overview](#overview) 10 | * [Usage](#usage) 11 | * [Contributing](.github/CONTRIBUTING.md) 12 | * [Release History](CHANGELOG.md) 13 | * [License](#license) 14 | 15 | 16 | ## Overview 17 | This library provides both parsing JSON text/files and creating JSON text/files. 18 | 19 | 20 | ## Usage 21 | 22 | 23 | ```c 24 | #include "json.h" 25 | #include 26 | #include 27 | 28 | 29 | int main() 30 | { 31 | // parsing the json string 32 | struct JsonValue *value = json_parse("{\"number\":1.6, \"null_key\" : null,\n" 33 | "\"bool_true\": true,\"bool_false\":false\n" 34 | ",\"string\": \"my string\nsecond line\" ,\n" 35 | " \"arr\": [1, 2.7, 3], \n" 36 | "\"obj\": {\"subkey\": 88}, \"subobj\": {\"sub\":{\"subkey\": 77}}}"); 37 | 38 | struct JsonValue *sub_value; 39 | 40 | 41 | // objects are converted to hashtables, so need to use the hashtable api to access/modify 42 | sub_value = hashtable_get(value->value->object, "string"); 43 | 44 | // The actual value is based on the type 45 | printf("string value: %s\n", sub_value->value->string); 46 | 47 | // arrays are converted to vectors, so need to use the vector api to access/modify 48 | sub_value = hashtable_get(value->value->object, "arr"); 49 | sub_value = vector_get(sub_value->value->array, 0); 50 | printf("array[0] value: %Lf\n", sub_value->value->number); 51 | 52 | // you can convert the parsed object back to string 53 | // The below is the same as json_stringify_with_options(value, false, 0); 54 | char *json_string = json_stringify(value); 55 | printf("JSON string:\n%s\n", json_string); 56 | free(json_string); 57 | 58 | // once done, release the parsed object 59 | json_release(value); 60 | 61 | // you can also prettify json strings which will 62 | // parse them and stringify them back with the 63 | // provided formatting options 64 | json_string = json_prettify("{\"key1\":1,\"key2\":\"test\",\"key3\":false,\"key4\":true,\"key5\":null,\"key6\":12.5,\"key7\":[true,false,[true, {\"sub\":1,\"a\":[true]}, {\"sub\":1,\"b\":[true,[true]]}, 1]]]", true, 2); 65 | printf("JSON string:\n%s\n", json_string); 66 | 67 | return(0); 68 | } /* main */ 69 | ``` 70 | 71 | 72 | ## Contributing 73 | See [contributing guide](.github/CONTRIBUTING.md) 74 | 75 | 76 | ## Release History 77 | 78 | See [Changelog](CHANGELOG.md) 79 | 80 | 81 | ## License 82 | Developed by Sagie Gur-Ari and licensed under the Apache 2 open source license. 83 | -------------------------------------------------------------------------------- /tests/test_parse_primitives.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | 8 | value = json_parse("null"); 9 | assert_true_with_description(value != NULL, "null not detected"); 10 | assert_true_with_description(value->type == JSON_TYPE_NULL, "null type not set"); 11 | json_release(value); 12 | 13 | value = json_parse("true"); 14 | assert_true_with_description(value != NULL, "true not detected"); 15 | assert_true_with_description(value->type == JSON_TYPE_BOOLEAN, "boolean type for true not set"); 16 | assert_true_with_description(value->value->boolean, "boolean value not true"); 17 | json_release(value); 18 | 19 | value = json_parse("false"); 20 | assert_true_with_description(value != NULL, "false not detected"); 21 | assert_true_with_description(value->type == JSON_TYPE_BOOLEAN, "boolean type for false not set"); 22 | assert_true_with_description(!value->value->boolean, "boolean value not false"); 23 | json_release(value); 24 | 25 | value = json_parse("0"); 26 | assert_true_with_description(value != NULL, "number 0 not detected"); 27 | assert_true_with_description(value->type == JSON_TYPE_NUMBER, "number type for 0 not set"); 28 | assert_num_equal(value->value->number, 0); 29 | json_release(value); 30 | 31 | value = json_parse("12345"); 32 | assert_true_with_description(value != NULL, "number 12345 not detected"); 33 | assert_true_with_description(value->type == JSON_TYPE_NUMBER, "number type for 12345 not set"); 34 | assert_num_equal(value->value->number, 12345); 35 | json_release(value); 36 | 37 | value = json_parse("123.45"); 38 | assert_true_with_description(value != NULL, "number 123.45 not detected"); 39 | assert_true_with_description(value->type == JSON_TYPE_NUMBER, "number type for 123.45 not set"); 40 | assert_num_equal(value->value->number, 123.45L); 41 | json_release(value); 42 | 43 | value = json_parse("-12345"); 44 | assert_true_with_description(value != NULL, "number -12345 not detected"); 45 | assert_true_with_description(value->type == JSON_TYPE_NUMBER, "number type for -12345 not set"); 46 | assert_num_equal(value->value->number, -12345); 47 | json_release(value); 48 | 49 | value = json_parse("\"first line\\n\\n" 50 | "second line\\tafter tab\""); 51 | assert_true_with_description(value != NULL, "string not detected"); 52 | assert_true_with_description(value->type == JSON_TYPE_STRING, "string type not set"); 53 | assert_string_equal(value->value->string, "first line\n\n" 54 | "second line\tafter tab"); 55 | json_release(value); 56 | 57 | value = json_parse("null2"); 58 | assert_true_with_description(value == NULL, "bad null value"); 59 | 60 | value = json_parse("true2"); 61 | assert_true_with_description(value == NULL, "bad true value"); 62 | 63 | value = json_parse("false2"); 64 | assert_true_with_description(value == NULL, "bad false value"); 65 | 66 | value = json_parse("12345a"); 67 | assert_true_with_description(value == NULL, "bad number value"); 68 | 69 | value = json_parse("123.4.5"); 70 | assert_true_with_description(value == NULL, "bad flat value"); 71 | 72 | value = json_parse("--12345"); 73 | assert_true_with_description(value == NULL, "bad negative number value"); 74 | 75 | value = json_parse("\"abc"); 76 | assert_true_with_description(value == NULL, "bad string value"); 77 | 78 | value = json_parse("\"abc\\q\""); 79 | assert_true_with_description(value == NULL, "bad string value"); 80 | } /* test_impl */ 81 | 82 | 83 | int main() 84 | { 85 | test_run(test_impl); 86 | } 87 | 88 | -------------------------------------------------------------------------------- /tests/test_parse_array.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | struct JsonValue *sub_value; 8 | 9 | value = json_parse("[]"); 10 | assert_true_with_description(value != NULL, "empty array not detected"); 11 | assert_true_with_description(value->type == JSON_TYPE_ARRAY, "array type not set"); 12 | assert_size_equal(vector_size(value->value->array), 0); 13 | json_release(value); 14 | 15 | value = json_parse("[ ]"); 16 | assert_true_with_description(value != NULL, "empty array not detected"); 17 | assert_true_with_description(value->type == JSON_TYPE_ARRAY, "array type not set"); 18 | assert_size_equal(vector_size(value->value->array), 0); 19 | json_release(value); 20 | 21 | value = json_parse("[1]"); 22 | assert_true_with_description(value != NULL, "number array not detected"); 23 | assert_true_with_description(value->type == JSON_TYPE_ARRAY, "array type not set"); 24 | assert_size_equal(vector_size(value->value->array), 1); 25 | sub_value = vector_get(value->value->array, 0); 26 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 27 | assert_num_equal(sub_value->value->number, 1L); 28 | json_release(value); 29 | 30 | value = json_parse("[1,2]"); 31 | assert_true_with_description(value != NULL, "multi number array not detected"); 32 | assert_true_with_description(value->type == JSON_TYPE_ARRAY, "array type not set"); 33 | assert_size_equal(vector_size(value->value->array), 2); 34 | sub_value = vector_get(value->value->array, 0); 35 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 36 | assert_num_equal(sub_value->value->number, 1L); 37 | sub_value = vector_get(value->value->array, 1); 38 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 39 | assert_num_equal(sub_value->value->number, 2L); 40 | json_release(value); 41 | 42 | value = json_parse("[ 1, \"test\", true, false, null, 2.2, {\"key\":\"value\"} , [1, 2, 3]]"); 43 | assert_true_with_description(value != NULL, "mixed array not detected"); 44 | assert_true_with_description(value->type == JSON_TYPE_ARRAY, "array type not set"); 45 | assert_size_equal(vector_size(value->value->array), 8); 46 | sub_value = vector_get(value->value->array, 0); 47 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 48 | assert_num_equal(sub_value->value->number, 1L); 49 | sub_value = vector_get(value->value->array, 1); 50 | assert_true(sub_value->type == JSON_TYPE_STRING); 51 | assert_string_equal(sub_value->value->string, "test"); 52 | sub_value = vector_get(value->value->array, 2); 53 | assert_true(sub_value->type == JSON_TYPE_BOOLEAN); 54 | assert_true(sub_value->value->boolean); 55 | sub_value = vector_get(value->value->array, 3); 56 | assert_true(sub_value->type == JSON_TYPE_BOOLEAN); 57 | assert_true(!sub_value->value->boolean); 58 | sub_value = vector_get(value->value->array, 4); 59 | assert_true(sub_value->type == JSON_TYPE_NULL); 60 | sub_value = vector_get(value->value->array, 5); 61 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 62 | assert_num_equal(sub_value->value->number, 2.2L); 63 | sub_value = vector_get(value->value->array, 6); 64 | assert_true(sub_value->type == JSON_TYPE_OBJECT); 65 | assert_size_equal(hashtable_size(sub_value->value->object), 1); 66 | sub_value = hashtable_get(sub_value->value->object, "key"); 67 | assert_true(sub_value->type == JSON_TYPE_STRING); 68 | assert_string_equal(sub_value->value->string, "value"); 69 | sub_value = vector_get(value->value->array, 7); 70 | assert_true(sub_value->type == JSON_TYPE_ARRAY); 71 | assert_size_equal(vector_size(sub_value->value->array), 3); 72 | sub_value = vector_get(sub_value->value->array, 1); 73 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 74 | assert_num_equal(sub_value->value->number, 2L); 75 | json_release(value); 76 | } /* test_impl */ 77 | 78 | 79 | int main() 80 | { 81 | test_run(test_impl); 82 | } 83 | 84 | -------------------------------------------------------------------------------- /tests/test_parse_object.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | 4 | void test_impl() 5 | { 6 | struct JsonValue *value; 7 | struct JsonValue *sub_value; 8 | 9 | value = json_parse("{}"); 10 | assert_true_with_description(value != NULL, "empty object not detected"); 11 | assert_true_with_description(value->type == JSON_TYPE_OBJECT, "object type not set"); 12 | assert_size_equal(hashtable_size(value->value->object), 0); 13 | json_release(value); 14 | 15 | value = json_parse("{ }"); 16 | assert_true_with_description(value != NULL, "empty object not detected"); 17 | assert_true_with_description(value->type == JSON_TYPE_OBJECT, "object type not set"); 18 | assert_size_equal(hashtable_size(value->value->object), 0); 19 | json_release(value); 20 | 21 | value = json_parse("{\"key\":1}"); 22 | assert_true_with_description(value != NULL, "number object not detected"); 23 | assert_true_with_description(value->type == JSON_TYPE_OBJECT, "object type not set"); 24 | assert_size_equal(hashtable_size(value->value->object), 1); 25 | sub_value = hashtable_get(value->value->object, "key"); 26 | assert_true_with_description(sub_value != NULL, "key not found"); 27 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 28 | assert_num_equal(sub_value->value->number, 1L); 29 | json_release(value); 30 | 31 | value = json_parse("{\"number\":1.6, \"null_key\" : null,\n" 32 | "\"bool_true\": true,\"bool_false\":false,\n" 33 | "\"string\": \"my string\nsecond line\" ,\n" 34 | " \"arr\": [1, 2.7, 3] \n" 35 | ",\"obj\": {\"subkey\": 88}, \"subobj\": {\"sub\":{\"subkey\": 77}}}"); 36 | assert_true_with_description(value != NULL, "mixed object not detected"); 37 | assert_true_with_description(value->type == JSON_TYPE_OBJECT, "object type not set"); 38 | assert_size_equal(hashtable_size(value->value->object), 8); 39 | sub_value = hashtable_get(value->value->object, "number"); 40 | assert_true_with_description(sub_value != NULL, "number not found"); 41 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 42 | assert_num_equal(sub_value->value->number, 1.6L); 43 | sub_value = hashtable_get(value->value->object, "null_key"); 44 | assert_true_with_description(sub_value != NULL, "null_key not found"); 45 | assert_true(sub_value->type == JSON_TYPE_NULL); 46 | sub_value = hashtable_get(value->value->object, "bool_true"); 47 | assert_true_with_description(sub_value != NULL, "bool_true not found"); 48 | assert_true(sub_value->type == JSON_TYPE_BOOLEAN); 49 | assert_true(sub_value->value->boolean); 50 | sub_value = hashtable_get(value->value->object, "bool_false"); 51 | assert_true_with_description(sub_value != NULL, "bool_false not found"); 52 | assert_true(sub_value->type == JSON_TYPE_BOOLEAN); 53 | assert_true(!sub_value->value->boolean); 54 | sub_value = hashtable_get(value->value->object, "string"); 55 | assert_true(sub_value->type == JSON_TYPE_STRING); 56 | assert_string_equal(sub_value->value->string, "my string\nsecond line"); 57 | sub_value = hashtable_get(value->value->object, "arr"); 58 | assert_true(sub_value->type == JSON_TYPE_ARRAY); 59 | sub_value = vector_get(sub_value->value->array, 1); 60 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 61 | assert_num_equal(sub_value->value->number, 2.7L); 62 | sub_value = hashtable_get(value->value->object, "obj"); 63 | assert_true(sub_value->type == JSON_TYPE_OBJECT); 64 | sub_value = hashtable_get(sub_value->value->object, "subkey"); 65 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 66 | assert_num_equal(sub_value->value->number, 88L); 67 | sub_value = hashtable_get(value->value->object, "subobj"); 68 | assert_true(sub_value->type == JSON_TYPE_OBJECT); 69 | sub_value = hashtable_get(sub_value->value->object, "sub"); 70 | assert_true(sub_value->type == JSON_TYPE_OBJECT); 71 | sub_value = hashtable_get(sub_value->value->object, "subkey"); 72 | assert_true(sub_value->type == JSON_TYPE_NUMBER); 73 | assert_num_equal(sub_value->value->number, 77L); 74 | json_release(value); 75 | } /* test_impl */ 76 | 77 | 78 | int main() 79 | { 80 | test_run(test_impl); 81 | } 82 | 83 | -------------------------------------------------------------------------------- /uncrustify.cfg: -------------------------------------------------------------------------------- 1 | output_tab_size = 2 2 | tok_split_gte = true 3 | indent_columns = 2 4 | indent_with_tabs = 0 5 | indent_class = true 6 | indent_member = 2 7 | indent_bool_paren = true 8 | indent_first_bool_expr = true 9 | sp_arith = force 10 | sp_assign = force 11 | sp_assign_default = force 12 | sp_bool = force 13 | sp_compare = force 14 | sp_inside_paren = remove 15 | sp_paren_paren = remove 16 | sp_before_ptr_star = force 17 | sp_between_ptr_star = remove 18 | sp_after_ptr_star = remove 19 | sp_before_byref = force 20 | sp_after_byref = remove 21 | sp_before_angle = remove 22 | sp_inside_angle = remove 23 | sp_after_angle = force 24 | sp_angle_paren = remove 25 | sp_angle_paren_empty = remove 26 | sp_angle_word = force 27 | sp_before_sparen = force 28 | sp_inside_sparen = remove 29 | sp_after_sparen = force 30 | sp_before_semi_for = remove 31 | sp_before_semi_for_empty = force 32 | sp_before_squares = remove 33 | sp_inside_square = remove 34 | sp_after_comma = force 35 | sp_before_ellipsis = remove 36 | sp_after_cast = remove 37 | sp_sizeof_paren = remove 38 | sp_inside_braces_enum = force 39 | sp_inside_braces_struct = force 40 | sp_inside_braces = force 41 | sp_func_proto_paren = remove 42 | sp_func_def_paren = remove 43 | sp_inside_fparen = remove 44 | sp_after_tparen_close = remove 45 | sp_func_call_paren = remove 46 | sp_return_paren = remove 47 | sp_attribute_paren = remove 48 | sp_defined_paren = force 49 | sp_brace_typedef = force 50 | sp_before_dc = remove 51 | sp_after_dc = remove 52 | sp_enum_assign = force 53 | align_func_params = true 54 | align_var_def_span = 2 55 | align_var_def_star_style = 1 56 | align_var_def_amp_style = 1 57 | align_var_def_colon = true 58 | align_var_def_inline = true 59 | align_assign_span = 1 60 | align_enum_equ_span = 4 61 | align_var_class_span = 2 62 | align_var_struct_span = 3 63 | align_struct_init_span = 3 64 | align_typedef_gap = 3 65 | align_typedef_span = 5 66 | align_typedef_star_style = 1 67 | align_right_cmt_span = 3 68 | align_nl_cont = true 69 | align_pp_define_gap = 4 70 | align_pp_define_span = 3 71 | align_asm_colon = true 72 | align_enum_equ_thresh = 8 73 | nl_assign_leave_one_liners = true 74 | nl_class_leave_one_liners = true 75 | nl_enum_leave_one_liners = true 76 | nl_getset_leave_one_liners = true 77 | nl_assign_brace = add 78 | nl_func_var_def_blk = 1 79 | nl_fcall_brace = add 80 | nl_enum_brace = force 81 | nl_struct_brace = add 82 | nl_union_brace = add 83 | nl_if_brace = add 84 | nl_brace_else = add 85 | nl_elseif_brace = add 86 | nl_else_brace = add 87 | nl_finally_brace = add 88 | nl_try_brace = add 89 | nl_for_brace = add 90 | nl_catch_brace = add 91 | nl_while_brace = add 92 | nl_do_brace = add 93 | nl_brace_while = remove 94 | nl_switch_brace = add 95 | nl_before_case = true 96 | nl_after_case = true 97 | nl_case_colon_brace = force 98 | nl_namespace_brace = force 99 | nl_constr_init_args = force 100 | nl_func_type_name = remove 101 | nl_func_paren = remove 102 | nl_func_def_paren = remove 103 | nl_func_decl_start = remove 104 | nl_func_def_start = remove 105 | nl_func_decl_start_single = remove 106 | nl_func_def_start_single = remove 107 | nl_func_decl_args = remove 108 | nl_func_decl_end = remove 109 | nl_func_def_end = remove 110 | nl_func_decl_end_single = remove 111 | nl_func_def_end_single = remove 112 | nl_fdef_brace = force 113 | nl_return_expr = remove 114 | nl_after_semicolon = true 115 | nl_after_brace_open = true 116 | nl_after_vbrace_open = true 117 | nl_after_brace_close = true 118 | nl_squeeze_ifdef = true 119 | nl_constr_colon = force 120 | pos_constr_comma = lead_force 121 | pos_constr_colon = lead_break 122 | pos_bool = lead 123 | pos_enum_comma = trail_force 124 | nl_max = 3 125 | nl_after_func_proto = 1 126 | nl_after_func_proto_group = 2 127 | nl_before_func_body_def = 3 128 | nl_after_access_spec = 1 129 | eat_blanks_after_open_brace = true 130 | eat_blanks_before_close_brace = true 131 | nl_after_return = true 132 | mod_full_brace_do = add 133 | mod_full_brace_for = add 134 | mod_full_brace_if = add 135 | mod_full_brace_while = add 136 | mod_paren_on_return = add 137 | mod_remove_extra_semicolon = true 138 | mod_add_long_function_closebrace_comment = 40 139 | mod_add_long_namespace_closebrace_comment = 5 140 | mod_add_long_switch_closebrace_comment = 40 141 | mod_remove_empty_return = true 142 | cmt_star_cont = true 143 | pp_indent = remove 144 | pp_space = remove 145 | mod_sort_include = true 146 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/json.c: -------------------------------------------------------------------------------- 1 | #include "json.h" 2 | #include "stringbuffer.h" 3 | #include "stringfn.h" 4 | #include 5 | #include 6 | #include 7 | 8 | // private functions 9 | static void _json_free(void *); 10 | static void _json_release_json_array(struct Vector *); 11 | static struct JsonValue *_json_create_null_value(void); 12 | static struct JsonValue *_json_parse(char *, size_t /* length */, size_t * /* offset */); 13 | static struct JsonValue *_json_parse_object(char *, size_t /* length */, size_t * /* offset */); 14 | static struct JsonValue *_json_parse_array(char *, size_t /* length */, size_t * /* offset */); 15 | static struct JsonValue *_json_parse_string(char *, size_t /* length */, size_t * /* offset */); 16 | static struct JsonValue *_json_parse_number(char *, size_t /* length */, size_t * /* offset */); 17 | static struct JsonValue *_json_parse_boolean(char *, size_t /* length */, size_t * /* offset */); 18 | static struct JsonValue *_json_parse_null(char *, size_t /* length */, size_t * /* offset */); 19 | static bool _json_skip_whitespaces(char *, size_t /* length */, size_t * /* offset */); 20 | static bool _json_parse_object_key(struct HashTable *, char *, size_t /* length */, size_t * /* offset */); 21 | static void _json_release_hashtable_key_value(char *, void *); 22 | static bool _json_stringify(struct JsonValue *, struct StringBuffer *, bool /* multi line */, size_t /* indentation */, size_t /* current indentation */, bool /* skip indent */, struct StringBuffer * /* temporary work buffer */); 23 | static bool _json_stringify_object(struct HashTable *, struct StringBuffer *, bool /* multi line */, size_t /* indentation */, size_t /* current indentation */, bool /* skip indent */, struct StringBuffer * /* work buffer */); 24 | static bool _json_stringify_array(struct Vector *, struct StringBuffer *, bool /* multi line */, size_t /* indentation */, size_t /* current indentation */, bool /* skip indent */, struct StringBuffer * /* work buffer */); 25 | static bool _json_stringify_string(char *, struct StringBuffer *); 26 | static bool _json_stringify_number(long double, struct StringBuffer * /* output buffer */, struct StringBuffer * /* work buffer */); 27 | static void _json_add_indentation(struct StringBuffer *, size_t /* indentation */); 28 | 29 | struct JsonValue *json_parse(char *text) 30 | { 31 | if (text == NULL) 32 | { 33 | return(NULL); 34 | } 35 | 36 | size_t length = strlen(text); 37 | if (!length) 38 | { 39 | return(NULL); 40 | } 41 | 42 | size_t offset = 0; 43 | struct JsonValue *value = _json_parse(text, length, &offset); 44 | 45 | if (offset < length) 46 | { 47 | // we have some leftovers that we can't parse 48 | json_release(value); 49 | return(NULL); 50 | } 51 | 52 | return(value); 53 | } 54 | 55 | 56 | char *json_stringify(struct JsonValue *value) 57 | { 58 | return(json_stringify_with_options(value, false /* multi line */, 0 /* indentation */)); 59 | } 60 | 61 | 62 | char *json_stringify_with_options(struct JsonValue *value, bool multi_line, size_t indentation) 63 | { 64 | if (value == NULL) 65 | { 66 | return(NULL); 67 | } 68 | 69 | struct StringBuffer *buffer = stringbuffer_new(); 70 | struct StringBuffer *work_buffer = stringbuffer_new_with_options(100, true); 71 | bool done = _json_stringify(value, buffer, multi_line, indentation, 0, false, work_buffer); 72 | stringbuffer_release(work_buffer); 73 | if (!done) 74 | { 75 | stringbuffer_release(buffer); 76 | return(NULL); 77 | } 78 | 79 | char *json_string = stringbuffer_to_string(buffer); 80 | stringbuffer_release(buffer); 81 | 82 | return(json_string); 83 | } 84 | 85 | 86 | char *json_prettify(char *value, bool multi_line, size_t indentation) 87 | { 88 | struct JsonValue *parsed = json_parse(value); 89 | 90 | if (parsed == NULL) 91 | { 92 | return(NULL); 93 | } 94 | 95 | char *formatted = json_stringify_with_options(parsed, multi_line, indentation); 96 | 97 | json_release(parsed); 98 | 99 | return(formatted); 100 | } 101 | 102 | 103 | void json_release(struct JsonValue *value) 104 | { 105 | if (value == NULL) 106 | { 107 | return; 108 | } 109 | 110 | switch (value->type) 111 | { 112 | case JSON_TYPE_OBJECT: 113 | hashtable_release(value->value->object); 114 | break; 115 | 116 | case JSON_TYPE_ARRAY: 117 | _json_release_json_array(value->value->array); 118 | break; 119 | 120 | case JSON_TYPE_STRING: 121 | _json_free(value->value->string); 122 | 123 | default: 124 | // no need to release 125 | break; 126 | } 127 | 128 | _json_free(value->value); 129 | _json_free(value); 130 | } 131 | 132 | 133 | static void _json_free(void *value) 134 | { 135 | if (value == NULL) 136 | { 137 | return; 138 | } 139 | 140 | free(value); 141 | } 142 | 143 | 144 | static void _json_release_json_array(struct Vector *array) 145 | { 146 | if (array == NULL) 147 | { 148 | return; 149 | } 150 | 151 | size_t count = vector_size(array); 152 | for (size_t index = 0; index < count; index++) 153 | { 154 | struct JsonValue *value = vector_get(array, index); 155 | json_release(value); 156 | } 157 | vector_release(array); 158 | } 159 | 160 | static struct JsonValue *_json_create_null_value(void) 161 | { 162 | struct JsonValue *value = malloc(sizeof(struct JsonValue)); 163 | 164 | value->type = JSON_TYPE_NULL; 165 | value->value = malloc(sizeof(union JsonValueUnion)); 166 | 167 | return(value); 168 | } 169 | 170 | static struct JsonValue *_json_parse(char *text, size_t length, size_t *offset) 171 | { 172 | if (text == NULL || !length || *offset >= length) 173 | { 174 | return(NULL); 175 | } 176 | 177 | // skip whitespaces 178 | if (!_json_skip_whitespaces(text, length, offset)) 179 | { 180 | return(NULL); 181 | } 182 | 183 | // detect next value type 184 | if (text[*offset] == '{') 185 | { 186 | return(_json_parse_object(text, length, offset)); 187 | } 188 | 189 | if (text[*offset] == '[') 190 | { 191 | return(_json_parse_array(text, length, offset)); 192 | } 193 | 194 | if (text[*offset] == '-' || isdigit(text[*offset])) 195 | { 196 | return(_json_parse_number(text, length, offset)); 197 | } 198 | 199 | if (text[*offset] == '"') 200 | { 201 | return(_json_parse_string(text, length, offset)); 202 | } 203 | 204 | if (text[*offset] == 't' || text[*offset] == 'f') 205 | { 206 | return(_json_parse_boolean(text, length, offset)); 207 | } 208 | 209 | if (text[*offset] == 'n') 210 | { 211 | return(_json_parse_null(text, length, offset)); 212 | } 213 | 214 | return(NULL); 215 | } /* _json_parse */ 216 | 217 | static struct JsonValue *_json_parse_object(char *text, size_t length, size_t *offset) 218 | { 219 | if (text == NULL || (*offset + 2 > length) || text[*offset] != '{') 220 | { 221 | return(NULL); 222 | } 223 | 224 | *offset = *offset + 1; 225 | 226 | struct HashTable *object = hashtable_new(); 227 | struct JsonValue *value = _json_create_null_value(); 228 | value->type = JSON_TYPE_OBJECT; 229 | value->value->object = object; 230 | 231 | bool found_separator = false; 232 | bool can_start_next_entry = true; 233 | for ( ; *offset < length; *offset = *offset + 1) 234 | { 235 | // skip whitespaces 236 | if (!_json_skip_whitespaces(text, length, offset)) 237 | { 238 | json_release(value); 239 | return(NULL); 240 | } 241 | 242 | size_t index = *offset; 243 | 244 | if (found_separator) 245 | { 246 | if (text[index] == '"') 247 | { 248 | if (!_json_parse_object_key(object, text, length, offset)) 249 | { 250 | json_release(value); 251 | return(NULL); 252 | } 253 | 254 | // we are in next char now, but for loop will advance it one more, so go back 1 255 | *offset = *offset - 1; 256 | 257 | found_separator = false; 258 | can_start_next_entry = false; 259 | } 260 | else 261 | { 262 | json_release(value); 263 | return(NULL); 264 | } 265 | } 266 | else if (!found_separator && text[index] == '}') 267 | { 268 | *offset = *offset + 1; 269 | break; 270 | } 271 | else if (can_start_next_entry && text[index] == '"') 272 | { 273 | if (!_json_parse_object_key(object, text, length, offset)) 274 | { 275 | json_release(value); 276 | return(NULL); 277 | } 278 | 279 | // we are in next char now, but for loop will advance it one more, so go back 1 280 | *offset = *offset - 1; 281 | 282 | can_start_next_entry = false; 283 | found_separator = false; 284 | } 285 | else if (!found_separator && text[index] == ',') 286 | { 287 | found_separator = true; 288 | can_start_next_entry = true; 289 | } 290 | } 291 | 292 | return(value); 293 | } /* _json_parse_object */ 294 | 295 | static struct JsonValue *_json_parse_array(char *text, size_t length, size_t *offset) 296 | { 297 | if (text == NULL || (*offset + 2 > length) || text[*offset] != '[') 298 | { 299 | return(NULL); 300 | } 301 | 302 | *offset = *offset + 1; 303 | 304 | struct Vector *array = vector_new_with_options(100 /* initial size */, true /* allow resize */); 305 | struct JsonValue *value = _json_create_null_value(); 306 | value->type = JSON_TYPE_ARRAY; 307 | value->value->array = array; 308 | 309 | bool found_seperator = true; 310 | for ( ; *offset < length; *offset = *offset + 1) 311 | { 312 | // skip whitespaces 313 | if (!_json_skip_whitespaces(text, length, offset)) 314 | { 315 | json_release(value); 316 | return(NULL); 317 | } 318 | 319 | size_t index = *offset; 320 | 321 | if (text[index] == ']') 322 | { 323 | *offset = *offset + 1; 324 | break; 325 | } 326 | else if (text[index] == ',') 327 | { 328 | if (found_seperator) 329 | { 330 | json_release(value); 331 | return(NULL); 332 | } 333 | else 334 | { 335 | found_seperator = true; 336 | } 337 | } 338 | else if (found_seperator) 339 | { 340 | found_seperator = false; 341 | 342 | struct JsonValue *array_item = _json_parse(text, length, offset); 343 | if (array_item == NULL) 344 | { 345 | json_release(value); 346 | return(NULL); 347 | } 348 | 349 | vector_push(array, array_item); 350 | 351 | // we are in next char now, but for loop will advance it one more, so go back 1 352 | *offset = *offset - 1; 353 | } 354 | else 355 | { 356 | json_release(value); 357 | return(NULL); 358 | } 359 | } 360 | 361 | return(value); 362 | } /* _json_parse_array */ 363 | 364 | static struct JsonValue *_json_parse_string(char *text, size_t length, size_t *offset) 365 | { 366 | if (text == NULL || (*offset + 2 > length) || text[*offset] != '"') 367 | { 368 | return(NULL); 369 | } 370 | 371 | size_t delta_offset = 0; 372 | bool in_escape = false; 373 | bool found_end = false; 374 | struct StringBuffer *buffer = stringbuffer_new_with_options(100 /* initial size */, true /* allow resize */); 375 | for (size_t index = *offset + 1; index < length; index++) 376 | { 377 | char character = text[index]; 378 | 379 | if (in_escape) 380 | { 381 | in_escape = false; 382 | delta_offset = delta_offset + 1; 383 | 384 | if (character == 'b') 385 | { 386 | stringbuffer_append(buffer, '\b'); 387 | } 388 | else if (character == 'f') 389 | { 390 | stringbuffer_append(buffer, '\f'); 391 | } 392 | else if (character == 'n') 393 | { 394 | stringbuffer_append(buffer, '\n'); 395 | } 396 | else if (character == 'r') 397 | { 398 | stringbuffer_append(buffer, '\r'); 399 | } 400 | else if (character == 't') 401 | { 402 | stringbuffer_append(buffer, '\t'); 403 | } 404 | else if (character == '"') 405 | { 406 | stringbuffer_append(buffer, '"'); 407 | } 408 | else if (character == '\\') 409 | { 410 | stringbuffer_append(buffer, '\\'); 411 | } 412 | else if (character == 'u') 413 | { 414 | stringbuffer_append_string(buffer, "\\u"); 415 | } 416 | else 417 | { 418 | // invalid/unsupported escape 419 | stringbuffer_release(buffer); 420 | return(NULL); 421 | } 422 | } 423 | else if (character == '\\') 424 | { 425 | in_escape = true; 426 | } 427 | else if (character == '"') 428 | { 429 | found_end = true; 430 | break; 431 | } 432 | else 433 | { 434 | stringbuffer_append(buffer, character); 435 | } 436 | } 437 | 438 | if (!found_end) 439 | { 440 | stringbuffer_release(buffer); 441 | return(NULL); 442 | } 443 | 444 | // new offset is now previous offset + 2 (start/end ") + content length + escape characters cound 445 | *offset = *offset + delta_offset + stringbuffer_get_content_size(buffer) + 2; 446 | struct JsonValue *value = _json_create_null_value(); 447 | value->type = JSON_TYPE_STRING; 448 | value->value->string = stringbuffer_to_string(buffer); 449 | stringbuffer_release(buffer); 450 | 451 | return(value); 452 | } /* _json_parse_string */ 453 | 454 | static struct JsonValue *_json_parse_number(char *text, size_t length, size_t *offset) 455 | { 456 | if (text == NULL || (*offset + 1 > length)) 457 | { 458 | return(NULL); 459 | } 460 | 461 | size_t start = *offset; 462 | bool found_decimal_point = false; 463 | for ( ; *offset < length; *offset = *offset + 1) 464 | { 465 | size_t index = *offset; 466 | 467 | char character = text[index]; 468 | if (isdigit(character) || (index == start && character == '-')) 469 | { 470 | // continue 471 | } 472 | else if (character == '.' && !found_decimal_point) 473 | { 474 | found_decimal_point = true; 475 | } 476 | else 477 | { 478 | break; 479 | } 480 | } 481 | 482 | if (start == *offset) 483 | { 484 | return(NULL); 485 | } 486 | 487 | struct JsonValue *value = _json_create_null_value(); 488 | value->type = JSON_TYPE_NUMBER; 489 | char *subtext = stringfn_substring(text, (int)start, *offset - start); 490 | value->value->number = strtold(subtext, NULL); 491 | _json_free(subtext); 492 | 493 | return(value); 494 | } /* _json_parse_number */ 495 | 496 | static struct JsonValue *_json_parse_boolean(char *text, size_t length, size_t *offset) 497 | { 498 | if (text == NULL) 499 | { 500 | return(NULL); 501 | } 502 | 503 | char *subtext = text + *offset; 504 | 505 | if ((*offset + 4 <= length) && stringfn_starts_with(subtext, "true")) 506 | { 507 | struct JsonValue *value = _json_create_null_value(); 508 | value->type = JSON_TYPE_BOOLEAN; 509 | value->value->boolean = true; 510 | *offset = *offset + 4; 511 | 512 | return(value); 513 | } 514 | else if ((*offset + 5 <= length) && stringfn_starts_with(subtext, "false")) 515 | { 516 | struct JsonValue *value = _json_create_null_value(); 517 | value->type = JSON_TYPE_BOOLEAN; 518 | value->value->boolean = false; 519 | 520 | *offset = *offset + 5; 521 | 522 | return(value); 523 | } 524 | 525 | return(NULL); 526 | } 527 | 528 | static struct JsonValue *_json_parse_null(char *text, size_t length, size_t *offset) 529 | { 530 | if (text == NULL || (*offset + 4 > length)) 531 | { 532 | return(NULL); 533 | } 534 | 535 | char *subtext = text + *offset; 536 | 537 | if (stringfn_starts_with(subtext, "null")) 538 | { 539 | *offset = *offset + 4; 540 | return(_json_create_null_value()); 541 | } 542 | 543 | return(NULL); 544 | } 545 | 546 | 547 | static bool _json_skip_whitespaces(char *text, size_t length, size_t *offset) 548 | { 549 | if (text == NULL || *offset >= length) 550 | { 551 | return(false); 552 | } 553 | 554 | // skip whitespaces 555 | for ( ; *offset < length; *offset = *offset + 1) 556 | { 557 | if (!isspace(text[*offset])) 558 | { 559 | break; 560 | } 561 | } 562 | 563 | return(*offset < length); 564 | } 565 | 566 | 567 | static bool _json_parse_object_key(struct HashTable *object, char *text, size_t length, size_t *offset) 568 | { 569 | if (object == NULL || text == NULL || (*offset + 4 > length) || text[*offset] != '"') 570 | { 571 | return(false); 572 | } 573 | 574 | // parse key 575 | struct JsonValue *json_value = _json_parse_string(text, length, offset); 576 | if (json_value == NULL) 577 | { 578 | return(false); 579 | } 580 | if (json_value->type != JSON_TYPE_STRING) 581 | { 582 | json_release(json_value); 583 | return(false); 584 | } 585 | 586 | char *key = json_value->value->string; 587 | _json_free(json_value->value); 588 | _json_free(json_value); 589 | 590 | // skip whitespaces 591 | if (!_json_skip_whitespaces(text, length, offset) || text[*offset] != ':') 592 | { 593 | _json_free(key); 594 | return(false); 595 | } 596 | *offset = *offset + 1; 597 | 598 | // parse value 599 | json_value = _json_parse(text, length, offset); 600 | if (json_value == NULL) 601 | { 602 | _json_free(key); 603 | return(false); 604 | } 605 | 606 | bool added = hashtable_insert(object, key, json_value, _json_release_hashtable_key_value); 607 | 608 | if (!added) 609 | { 610 | _json_free(key); 611 | json_release(json_value); 612 | return(false); 613 | } 614 | 615 | return(true); 616 | } /* _json_parse_object_key */ 617 | 618 | 619 | static void _json_release_hashtable_key_value(char *key, void *value) 620 | { 621 | _json_free(key); 622 | struct JsonValue *json_value = (struct JsonValue *)value; 623 | json_release(json_value); 624 | } 625 | 626 | 627 | static bool _json_stringify(struct JsonValue *value, struct StringBuffer *buffer, bool multi_line, size_t indentation, size_t current_indentation, bool skip_indent, struct StringBuffer *work_buffer) 628 | { 629 | if (value == NULL || buffer == NULL || work_buffer == NULL) 630 | { 631 | return(false); 632 | } 633 | 634 | if (multi_line && !skip_indent) 635 | { 636 | if (!stringbuffer_is_empty(buffer)) 637 | { 638 | stringbuffer_append(buffer, '\n'); 639 | } 640 | 641 | if (value->type != JSON_TYPE_OBJECT && value->type != JSON_TYPE_ARRAY) 642 | { 643 | _json_add_indentation(buffer, current_indentation); 644 | } 645 | } 646 | 647 | switch (value->type) 648 | { 649 | case JSON_TYPE_OBJECT: 650 | if (!_json_stringify_object(value->value->object, buffer, multi_line, indentation, current_indentation, skip_indent, work_buffer)) 651 | { 652 | return(false); 653 | } 654 | break; 655 | 656 | case JSON_TYPE_ARRAY: 657 | if (!_json_stringify_array(value->value->array, buffer, multi_line, indentation, current_indentation, skip_indent, work_buffer)) 658 | { 659 | return(false); 660 | } 661 | break; 662 | 663 | case JSON_TYPE_STRING: 664 | _json_stringify_string(value->value->string, buffer); 665 | break; 666 | 667 | case JSON_TYPE_NUMBER: 668 | _json_stringify_number(value->value->number, buffer, work_buffer); 669 | break; 670 | 671 | case JSON_TYPE_BOOLEAN: 672 | stringbuffer_append_bool(buffer, value->value->boolean); 673 | break; 674 | 675 | case JSON_TYPE_NULL: 676 | stringbuffer_append_string(buffer, "null"); 677 | break; 678 | } 679 | 680 | return(true); 681 | } /* _json_stringify */ 682 | 683 | 684 | static bool _json_stringify_object(struct HashTable *object, struct StringBuffer *buffer, bool multi_line, size_t indentation, size_t current_indentation, bool skip_indent, struct StringBuffer *work_buffer) 685 | { 686 | if (object == NULL || buffer == NULL || work_buffer == NULL) 687 | { 688 | return(false); 689 | } 690 | 691 | if (multi_line && !skip_indent) 692 | { 693 | _json_add_indentation(buffer, current_indentation); 694 | } 695 | stringbuffer_append(buffer, '{'); 696 | 697 | size_t size = hashtable_size(object); 698 | if (size) 699 | { 700 | struct HashTableEntries entries = hashtable_entries(object); 701 | 702 | bool added = false; 703 | size_t next_indentation = indentation + current_indentation; 704 | for (size_t index = 0; index < size; index++) 705 | { 706 | char *key = entries.keys[index]; 707 | struct JsonValue *value = (struct JsonValue *)entries.values[index]; 708 | 709 | if (key != NULL && value != NULL) 710 | { 711 | if (!added) 712 | { 713 | added = true; 714 | } 715 | else 716 | { 717 | stringbuffer_append(buffer, ','); 718 | } 719 | 720 | if (multi_line) 721 | { 722 | stringbuffer_append(buffer, '\n'); 723 | _json_add_indentation(buffer, next_indentation); 724 | } 725 | 726 | if (!_json_stringify_string(key, buffer)) 727 | { 728 | return(false); 729 | } 730 | stringbuffer_append(buffer, ':'); 731 | if (multi_line) 732 | { 733 | stringbuffer_append(buffer, ' '); 734 | } 735 | 736 | if (!_json_stringify(value, buffer, multi_line, indentation, next_indentation, true, work_buffer)) 737 | { 738 | return(false); 739 | } 740 | } 741 | } 742 | 743 | _json_free(entries.keys); 744 | _json_free(entries.values); 745 | } 746 | 747 | if (multi_line) 748 | { 749 | stringbuffer_append(buffer, '\n'); 750 | _json_add_indentation(buffer, current_indentation); 751 | } 752 | stringbuffer_append(buffer, '}'); 753 | 754 | return(true); 755 | } /* _json_stringify_object */ 756 | 757 | 758 | static bool _json_stringify_array(struct Vector *array, struct StringBuffer *buffer, bool multi_line, size_t indentation, size_t current_indentation, bool skip_indent, struct StringBuffer *work_buffer) 759 | { 760 | if (array == NULL || buffer == NULL || work_buffer == NULL) 761 | { 762 | return(false); 763 | } 764 | 765 | if (multi_line && !skip_indent) 766 | { 767 | _json_add_indentation(buffer, current_indentation); 768 | } 769 | stringbuffer_append(buffer, '['); 770 | 771 | size_t size = vector_size(array); 772 | bool added = false; 773 | size_t next_indentation = indentation + current_indentation; 774 | for (size_t index = 0; index < size; index++) 775 | { 776 | struct JsonValue *value = vector_get(array, index); 777 | 778 | if (value != NULL) 779 | { 780 | if (!added) 781 | { 782 | added = true; 783 | } 784 | else 785 | { 786 | stringbuffer_append(buffer, ','); 787 | } 788 | 789 | if (!_json_stringify(value, buffer, multi_line, indentation, next_indentation, false, work_buffer)) 790 | { 791 | return(false); 792 | } 793 | } 794 | } 795 | 796 | if (multi_line) 797 | { 798 | stringbuffer_append(buffer, '\n'); 799 | _json_add_indentation(buffer, current_indentation); 800 | } 801 | stringbuffer_append(buffer, ']'); 802 | 803 | return(true); 804 | } /* _json_stringify_array */ 805 | 806 | 807 | static bool _json_stringify_string(char *text, struct StringBuffer *buffer) 808 | { 809 | if (text == NULL || buffer == NULL) 810 | { 811 | return(false); 812 | } 813 | 814 | size_t length = strlen(text); 815 | 816 | stringbuffer_append(buffer, '"'); 817 | for (size_t index = 0; index < length; index++) 818 | { 819 | char character = text[index]; 820 | 821 | if (character == '\b') 822 | { 823 | stringbuffer_append_string(buffer, "\\b"); 824 | } 825 | else if (character == '\f') 826 | { 827 | stringbuffer_append_string(buffer, "\\f"); 828 | } 829 | else if (character == '\n') 830 | { 831 | stringbuffer_append_string(buffer, "\\n"); 832 | } 833 | else if (character == '\r') 834 | { 835 | stringbuffer_append_string(buffer, "\\r"); 836 | } 837 | else if (character == '\t') 838 | { 839 | stringbuffer_append_string(buffer, "\\t"); 840 | } 841 | else if (character == '"') 842 | { 843 | stringbuffer_append_string(buffer, "\\\""); 844 | } 845 | else if (character == '\\') 846 | { 847 | stringbuffer_append_string(buffer, "\\\\"); 848 | } 849 | else 850 | { 851 | stringbuffer_append(buffer, character); 852 | } 853 | } 854 | stringbuffer_append(buffer, '"'); 855 | 856 | return(true); 857 | } /* _json_stringify_string */ 858 | 859 | 860 | static bool _json_stringify_number(long double value, struct StringBuffer *buffer, struct StringBuffer *work_buffer) 861 | { 862 | if (buffer == NULL || work_buffer == NULL) 863 | { 864 | return(false); 865 | } 866 | 867 | stringbuffer_clear(work_buffer); 868 | stringbuffer_append_long_double(work_buffer, value); 869 | char *number_string = stringbuffer_to_string(work_buffer); 870 | size_t length = strlen(number_string); 871 | size_t length_to_remove = 0; 872 | bool stop_removing = false; 873 | for (size_t index = length - 1; index > 0; index--) 874 | { 875 | char character = number_string[index]; 876 | if (character == '.') 877 | { 878 | if (length_to_remove > 0) 879 | { 880 | if (!stop_removing) 881 | { 882 | length_to_remove = length_to_remove + 1; 883 | } 884 | length = length - length_to_remove; 885 | } 886 | break; 887 | } 888 | else if (character == '0' && !stop_removing) 889 | { 890 | length_to_remove = length_to_remove + 1; 891 | } 892 | else 893 | { 894 | stop_removing = true; 895 | } 896 | } 897 | 898 | stringbuffer_append_string_with_options(buffer, number_string, 0, length); 899 | free(number_string); 900 | 901 | return(true); 902 | } /* _json_stringify_number */ 903 | 904 | 905 | static void _json_add_indentation(struct StringBuffer *buffer, size_t indentation) 906 | { 907 | if (buffer == NULL) 908 | { 909 | return; 910 | } 911 | 912 | for (size_t index = 0; index < indentation; index++) 913 | { 914 | stringbuffer_append(buffer, ' '); 915 | } 916 | } 917 | --------------------------------------------------------------------------------