├── .gitmodules ├── docs ├── static │ ├── css │ │ ├── custom.css │ │ └── common.css │ ├── dark-light │ │ ├── moon.png │ │ ├── sun.png │ │ ├── unchecked.svg │ │ ├── checked.svg │ │ ├── moon.svg │ │ ├── sun.svg │ │ ├── light.css │ │ ├── dark.css │ │ ├── common-dark-light.css │ │ └── dark-mode-toggle.mjs │ └── images │ │ ├── logo_tm.png │ │ ├── logo_tm_full.png │ │ ├── logo.drawio │ │ ├── json_token_list.drawio │ │ ├── logo.svg │ │ └── json_token_list.svg ├── api-reference │ ├── lwjson.rst │ ├── lwjson_serializer.rst │ ├── index.rst │ └── lwjson_opt.rst ├── changelog │ └── index.rst ├── authors │ └── index.rst ├── user-manual │ ├── index.rst │ ├── token-design.rst │ ├── how-it-works.rst │ ├── stream.rst │ └── data-access.rst ├── requirements.txt ├── examples │ └── stream.json ├── Makefile ├── make.bat ├── index.rst ├── conf.py └── get-started │ └── index.rst ├── trial_env ├── trial_run.json └── trial_run.c ├── .github ├── FUNDING.yml └── workflows │ ├── build-and-test.yml │ └── release.yml ├── lwjson ├── CMakeLists.txt ├── src │ ├── include │ │ └── lwjson │ │ │ ├── lwjson_opts_template.h │ │ │ ├── lwjson_utils.h │ │ │ ├── lwjson_opt.h │ │ │ ├── lwjson_serializer.h │ │ │ └── lwjson.h │ └── lwjson │ │ ├── lwjson_debug.c │ │ └── lwjson_utils.c └── library.cmake ├── TODO.md ├── .vscode ├── extensions.json ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── tests ├── json │ ├── custom_stream.json │ ├── weather_current.json │ └── custom.json ├── test_stream │ ├── cmake.cmake │ ├── lwjson_opts.h │ └── test_stream.c ├── test_json_find │ ├── cmake.cmake │ ├── lwjson_opts.h │ └── test_json_find.c ├── test_serializer │ ├── cmake.cmake │ ├── lwjson_opts.h │ └── test_serializer.c ├── test_json_data_types │ ├── cmake.cmake │ ├── lwjson_opts.h │ └── test_json_data_types.c ├── test_json_parse_basic │ ├── cmake.cmake │ ├── lwjson_opts.h │ └── test_json_parse_basic.c ├── test_parse_token_count │ ├── cmake.cmake │ ├── lwjson_opts.h │ └── test_parse_token_count.c ├── test_main.c ├── test.h ├── CMakeLists.txt └── test.py ├── CMakeLists.txt ├── cmake ├── i686-w64-mingw32-gcc.cmake └── x86_64-w64-mingw32-gcc.cmake ├── AUTHORS ├── .readthedocs.yaml ├── .gitattributes ├── .clang-tidy ├── examples ├── example_minimal.c ├── example_stream.c ├── example_traverse.c └── example_stream_with_user_data.c ├── LICENSE ├── library.json ├── CMakePresets.json ├── README.md ├── CHANGELOG.md ├── .clang-format └── .gitignore /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/static/css/custom.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /trial_env/trial_run.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": "value" 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | custom: ['paypal.me/tilz0R'] 4 | -------------------------------------------------------------------------------- /docs/api-reference/lwjson.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwjson: 2 | 3 | LwJSON 4 | ====== 5 | 6 | .. doxygengroup:: LWJSON -------------------------------------------------------------------------------- /docs/static/dark-light/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwjson/HEAD/docs/static/dark-light/moon.png -------------------------------------------------------------------------------- /docs/static/dark-light/sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwjson/HEAD/docs/static/dark-light/sun.png -------------------------------------------------------------------------------- /docs/static/images/logo_tm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwjson/HEAD/docs/static/images/logo_tm.png -------------------------------------------------------------------------------- /lwjson/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | include(${CMAKE_CURRENT_LIST_DIR}/library.cmake) -------------------------------------------------------------------------------- /docs/static/images/logo_tm_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaJerle/lwjson/HEAD/docs/static/images/logo_tm_full.png -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - Stream parser: split large strings to multiple calbacks 4 | - Stream parser: ignore comments (optional) -------------------------------------------------------------------------------- /docs/changelog/index.rst: -------------------------------------------------------------------------------- 1 | .. _changelof: 2 | 3 | Changelog 4 | ========= 5 | 6 | .. literalinclude:: ../../CHANGELOG.md 7 | -------------------------------------------------------------------------------- /docs/api-reference/lwjson_serializer.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwjson_serializer: 2 | 3 | JSON Serializer 4 | =============== 5 | 6 | .. doxygengroup:: LWJSON_SERIALIZER -------------------------------------------------------------------------------- /docs/authors/index.rst: -------------------------------------------------------------------------------- 1 | .. _authors: 2 | 3 | Authors 4 | ======= 5 | 6 | List of authors and contributors to the library 7 | 8 | .. literalinclude:: ../../AUTHORS -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.cpptools", 4 | "ms-vscode.cmake-tools", 5 | "twxs.cmake", 6 | ] 7 | } -------------------------------------------------------------------------------- /docs/user-manual/index.rst: -------------------------------------------------------------------------------- 1 | .. _um: 2 | 3 | User manual 4 | =========== 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | how-it-works 10 | token-design 11 | data-access 12 | stream -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=3.5.1 2 | breathe>=4.9.1 3 | urllib3==1.26.15 4 | docutils==0.16 5 | colorama 6 | sphinx_rtd_theme>=1.0.0 7 | sphinx-tabs 8 | sphinxcontrib-svg2pdfconverter 9 | sphinx-sitemap 10 | -------------------------------------------------------------------------------- /docs/api-reference/index.rst: -------------------------------------------------------------------------------- 1 | .. _api_reference: 2 | 3 | API reference 4 | ============= 5 | 6 | List of all the modules: 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | 11 | lwjson 12 | lwjson_opt 13 | lwjson_serializer -------------------------------------------------------------------------------- /docs/examples/stream.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "abc", 3 | "array": [ 4 | "123", 5 | "def", 6 | "ghi" 7 | ], 8 | "array_in_array": [ 9 | ["1", "2", "3"], 10 | ["4", "5", "6"], 11 | ["7", "8", "9"] 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/json/custom_stream.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": "abc", 3 | "array": [ 4 | "123", 5 | "def", 6 | "ghi" 7 | ], 8 | "array_in_array": [ 9 | ["1", "2", "3"], 10 | ["4", "5", "6"], 11 | ["7", "8", "9"] 12 | ] 13 | } -------------------------------------------------------------------------------- /docs/static/dark-light/unchecked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_stream/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_stream.c 6 | ) 7 | 8 | # Options file 9 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwjson_opts.h) -------------------------------------------------------------------------------- /tests/test_json_find/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_json_find.c 6 | ) 7 | 8 | # Options file 9 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwjson_opts.h) -------------------------------------------------------------------------------- /tests/test_serializer/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_serializer.c 6 | ) 7 | 8 | # Options file 9 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwjson_opts.h) -------------------------------------------------------------------------------- /tests/test_json_data_types/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_json_data_types.c 6 | ) 7 | 8 | # Options file 9 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwjson_opts.h) -------------------------------------------------------------------------------- /tests/test_json_parse_basic/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_json_parse_basic.c 6 | ) 7 | 8 | # Options file 9 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwjson_opts.h) -------------------------------------------------------------------------------- /tests/test_parse_token_count/cmake.cmake: -------------------------------------------------------------------------------- 1 | # CMake include file 2 | 3 | # Add more sources 4 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 5 | ${CMAKE_CURRENT_LIST_DIR}/test_parse_token_count.c 6 | ) 7 | 8 | # Options file 9 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/lwjson_opts.h) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Setup project 4 | project(LwLibPROJECT) 5 | 6 | if(NOT PROJECT_IS_TOP_LEVEL) 7 | add_subdirectory(lwjson) 8 | else() 9 | set(TEST_CMAKE_FILE_NAME "test_stream/cmake.cmake") 10 | add_subdirectory(tests) 11 | endif() -------------------------------------------------------------------------------- /cmake/i686-w64-mingw32-gcc.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | # Some default GCC settings 4 | set(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 5 | set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 6 | 7 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 8 | -------------------------------------------------------------------------------- /docs/static/dark-light/checked.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cmake/x86_64-w64-mingw32-gcc.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Windows) 2 | 3 | # Some default GCC settings 4 | set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc) 5 | set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++) 6 | 7 | set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) 8 | -------------------------------------------------------------------------------- /tests/test_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | #include "test.h" 4 | 5 | extern int test_run(void); 6 | 7 | int 8 | main(void) { 9 | int ret = 0; 10 | printf("Application start\r\n"); 11 | ret = test_run(); 12 | printf("Done\r\n"); 13 | 14 | return ret; 15 | } 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Tilen Majerle 2 | Tilen Majerle 3 | Tristen Pierson 4 | erics 5 | Eric Sidorov 6 | erics 7 | Josef Salda 8 | Josef Salda 9 | David Kubasek -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | build: 3 | os: ubuntu-22.04 4 | tools: 5 | python: "3.11" 6 | 7 | # Build documentation in the docs/ directory with Sphinx 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | # Python configuration 12 | python: 13 | install: 14 | - requirements: docs/requirements.txt 15 | 16 | formats: 17 | - pdf 18 | - epub 19 | -------------------------------------------------------------------------------- /docs/static/dark-light/moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | moon 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/api-reference/lwjson_opt.rst: -------------------------------------------------------------------------------- 1 | .. _api_lwjson_opt: 2 | 3 | Configuration 4 | ============= 5 | 6 | This is the default configuration of the middleware. 7 | When any of the settings shall be modified, it shall be done in dedicated application config ``lwjson_opts.h`` file. 8 | 9 | .. note:: 10 | Check :ref:`getting_started` for guidelines on how to create and use configuration file. 11 | 12 | .. doxygengroup:: LWJSON_OPT 13 | :inner: -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "configurations": [ 4 | { 5 | /* 6 | * Full configuration is provided by CMake plugin for vscode, 7 | * that shall be installed by user 8 | */ 9 | "name": "Win32", 10 | "intelliSenseMode": "${default}", 11 | "configurationProvider": "ms-vscode.cmake-tools" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain -------------------------------------------------------------------------------- /tests/json/weather_current.json: -------------------------------------------------------------------------------- 1 | {"coord":{"lon":15.19,"lat":45.57},"weather":[{"id":701,"main":"Mist","description":"mist","icon":"50n"}],"base":"stations","main":{"temp":273.15,"feels_like":270.4,"temp_min":273.15,"temp_max":273.15,"pressure":1008,"humidity":97},"visibility":6000,"wind":{"speed":1.01,"deg":281},"clouds":{"all":100},"dt":1607031635,"sys":{"type":1,"id":6816,"country":"SI","sunrise":1606976516,"sunset":1607008607},"timezone":3600,"id":3202333,"name":"Črnomelj","cod":200} -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | /* GDB must in be in the PATH environment */ 6 | "name": "(Windows) Launch", 7 | "type": "cppdbg", 8 | "request": "launch", 9 | "program": "${command:cmake.launchTargetPath}", 10 | "args": [], 11 | "stopAtEntry": false, 12 | "cwd": "${fileDirname}", 13 | "environment": [] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.lcdjson": "json", 4 | "lwevt_types.h": "c", 5 | "lwevt_type.h": "c", 6 | "lwevt.h": "c", 7 | "string.h": "c", 8 | "lwevt_opt.h": "c", 9 | "lwjson.h": "c", 10 | "lwjson_opt.h": "c", 11 | "math.h": "c", 12 | "cstdlib": "c", 13 | "*.def": "c", 14 | "lwjson_opts.h": "c" 15 | }, 16 | "esbonio.sphinx.confDir": "", 17 | "C_Cpp.codeAnalysis.clangTidy.useBuildPath": true 18 | } -------------------------------------------------------------------------------- /docs/static/dark-light/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "*, 3 | -abseil-*, 4 | -altera-*, 5 | -android-*, 6 | -fuchsia-*, 7 | -google-*, 8 | -llvm*, 9 | -modernize-use-trailing-return-type, 10 | -zircon-*, 11 | -readability-else-after-return, 12 | -readability-static-accessed-through-instance, 13 | -readability-avoid-const-params-in-decls, 14 | -cppcoreguidelines-non-private-member-variables-in-classes, 15 | -misc-non-private-member-variables-in-classes, 16 | " 17 | WarningsAsErrors: '' 18 | HeaderFilterRegex: '' 19 | FormatStyle: none -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Windows CMake Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | - test 8 | pull_request: 9 | branches: 10 | - develop 11 | 12 | jobs: 13 | build: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Install MinGW 21 | run: | 22 | choco install mingw --version=12.2.0 -y 23 | echo "C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin" >> $GITHUB_PATH 24 | gcc --version 25 | 26 | - name: Run Tests 27 | working-directory: tests 28 | run: | 29 | python test.py 30 | -------------------------------------------------------------------------------- /docs/static/images/logo.drawio: -------------------------------------------------------------------------------- 1 | jZLdToQwEIWfhksToLsbvVTc1fi3iRiNlw2dpY2FIaUI+PQWmcKSzSbeNJ1vptP2nAlYUnR3hlfyGQXoIA5FF7DbII7ZauXWAfQjiK42I8iNEoRmkKofGOGaYKME1Is6i6itqpYww7KEzC4YNwbbZdkB9fLSiudwAtKM61P6oYSVI71chzO/B5VLf3MUUqbgvphALbnA9qgr2wYsMYh23BVdAnqQzssyntudyU4PM1Da/xzIK/gU72wP+eMb7GoRvl4nF9Tlm+uGPvzUPqT7F3qy7b0OBptSwNAqCthNK5WFtOLZkG2d745JW2hK19bg16TXxpEDlpa8ZUM8iRG6gN4AxkJ39nOzZG7SAAuwpncl05jRvNCUxT5uZ9OiDTkhjwzzRnKak3xqPUvpNqSmD2fX/nJHk8+2vw== -------------------------------------------------------------------------------- /examples/example_minimal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | /* LwJSON instance and tokens */ 5 | static lwjson_token_t tokens[128]; 6 | static lwjson_t lwjson; 7 | 8 | /* Parse JSON */ 9 | void 10 | example_minimal_run(void) { 11 | lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens)); 12 | if (lwjson_parse(&lwjson, "{\"mykey\":\"myvalue\"}") == lwjsonOK) { 13 | const lwjson_token_t* t; 14 | printf("JSON parsed..\r\n"); 15 | 16 | /* Find custom key in JSON */ 17 | if ((t = lwjson_find(&lwjson, "mykey")) != NULL) { 18 | printf("Key found with data type: %d\r\n", (int)t->type); 19 | } 20 | 21 | /* Call this when not used anymore */ 22 | lwjson_free(&lwjson); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /tests/json/custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "int": { 3 | "num1": 1234, 4 | "num2": -1234, 5 | "num3": 0 6 | }, 7 | "real": { 8 | "num1":123.4, 9 | "num2":-123.4, 10 | "num3":123E3, 11 | "num4":123e4, 12 | "num5":-123E3, 13 | "num6":-123e4, 14 | "num7":123E-3, 15 | "num8":123e-4, 16 | "num9":-123E-3, 17 | "num10":-123e-4, 18 | "num11":123.12E3, 19 | "num12":123.1e4, 20 | "num13":-123.0E3, 21 | "num14":-123.1e4, 22 | "num15":123.1E-3, 23 | "num16":123.1235e-4, 24 | "num17":-123.324342E-3, 25 | "num18":-123.3232e-4, 26 | }, 27 | "obj": { 28 | "obj1":{}, 29 | "obj2":{ 30 | "key1":[], 31 | "key2":"string", 32 | }, 33 | }, 34 | "boolean": { 35 | "true":true, 36 | "false":false 37 | }, 38 | "null":null 39 | } -------------------------------------------------------------------------------- /docs/static/dark-light/light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | color-scheme: light; /* stylelint-disable-line property-no-unknown */ 19 | 20 | --background-color: rgb(240 240 240); 21 | --text-color: rgb(15 15 15); 22 | --shadow-color: rgb(15 15 15 / 50%); 23 | --accent-color: rgb(240 0 0 / 50%); 24 | } 25 | -------------------------------------------------------------------------------- /docs/user-manual/token-design.rst: -------------------------------------------------------------------------------- 1 | .. _token_design: 2 | 3 | Token design 4 | ============ 5 | 6 | Every element of LwJSON is a token. 7 | There are different set of token types: 8 | 9 | * *Object*: Type that has nested key-value pairs, eg ``{"key":{"sub-key":"value"}}`` 10 | * *Array*: Type that holds nested values, eg ``{"key":[1,2,3,4,5]}`` 11 | * *String*: Regular string, quoted sequence of characters, eg ``{"key":"my_string"}`` 12 | * *Number*: Integer or real number, eg ``{"intnum":123,"realnum":4.3}`` 13 | * *Boolean true*: Boolean type true, eg ``{"key":true}`` 14 | * *Boolean false*: Boolean type false, eg ``{"key":false}`` 15 | * *Null*: Null indicator, eg ``{"key":null}`` 16 | 17 | When parsed, input string is not copied to token, every token uses input string as 18 | a reference and points to the beginning of strings/values. 19 | This is valid for all string data types and for parameter names. 20 | 21 | .. note :: 22 | Input string is not modified therefore all strings contain additional 23 | parameter with string length. 24 | 25 | .. toctree:: 26 | :maxdepth: 2 -------------------------------------------------------------------------------- /docs/static/dark-light/dark.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | color-scheme: dark; /* stylelint-disable-line property-no-unknown */ 19 | 20 | --background-color: rgb(15 15 15); 21 | --text-color: rgb(240 240 240); 22 | --shadow-color: rgb(240 240 240 / 50%); 23 | --accent-color: rgb(0 0 240 / 50%); 24 | } 25 | 26 | img { 27 | filter: grayscale(50%); 28 | } 29 | 30 | .icon { 31 | filter: invert(100%); 32 | } 33 | 34 | a { 35 | color: yellow; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tilen MAJERLE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LwJSON", 3 | "version": "1.8.1", 4 | "description": "Lightweight JSON parser for embedded systems with support for inline comments", 5 | "keywords": "json, javascript, lightweight, parser, stm32, manager, library, comment, object, notation, object notation", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/MaJerle/lwjson.git" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Tilen Majerle", 13 | "email": "tilen@majerle.eu", 14 | "url": "https://majerle.eu" 15 | } 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/MaJerle/lwjson", 19 | "dependencies": {}, 20 | "frameworks": "*", 21 | "platforms": "*", 22 | "export": { 23 | "exclude": [ 24 | ".github", 25 | "dev", 26 | "docs", 27 | "**/.vs", 28 | "**/Debug", 29 | "build", 30 | "**/build" 31 | ] 32 | }, 33 | "build": { 34 | "includeDir": "lwjson/src/include", 35 | "srcDir": ".", 36 | "srcFilter": "+" 37 | } 38 | } -------------------------------------------------------------------------------- /docs/static/images/json_token_list.drawio: -------------------------------------------------------------------------------- 1 | 1Zddb9sgFIZ/jS8nGfBHfJmk2XbTalMmTdodtXGMik1KSJ321w9sbIfYblJljWZfRPiFc4Anh5fEQcv88E3gbXbPE8Ic6CYHB905EHpRqD618FoLcIZqYSNoUkugE9b0jdjiniZkZ0mScybp1hZjXhQklpaGheClPSzlzJ50izekJ6xjzPrqb5rIrFZnvtvp3wndZM3MwDU9OW4GG2GX4YSXRxJaOWgpOJd1Kz8sCdPoGix13NeR3nZhghTykoDVn1LcL55/rh7mP9L5tuC/HuIvJssLZnuzYQcGTOVbbAVRzY1uNtLjqZByNbVmz7ioooPnPa8HoDSFWLPoJBMbtuFqrXWGy7O6Lg5QMpC1VZ7AUS+ady8vwAobXsHysq1FURy/uwjPXsRVe9ZPmr43HbZmg1fMNbq18O46Zuc38eTbzPxrmMXqGao+cN0uxtJeg3yUjD+O/JIv0nWHj9+5rEruHXSlWX5Q+Zl8bUxSkoPWM5kzJQBdkYxuCtWOlTkRoU8fEZIqW52bjpwmiQ5fCLKjb/ixSuVq3+G0kJVp+xUCXQwLvJd8V18NoF2ATkkOo24IWo9VVxPhOZHiVQ1pApCxZXMtRV79WnYmD0MzJDsyeM9o2NwrmzZzZ72qYdz3A07sDTjxCWbB90VCEgOqzKgk6y2OdW+p7l77G0gpY0tTGQUvyL/BBsHMwgbcsMdtFvaxgeCTsMFpYAvACbaohw0NYYOfhA1NAhvwgrPYAv+G2PxJYEPgtNr6hzSENzykwTSwBdHZapvdsNjCaVDz/f+K2mwS1AA6rTW/Rw36A2cUfvz3h3rt/mVWfUf/1NHqLw== -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "default", 6 | "hidden": true, 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build/${presetName}", 9 | "cacheVariables": { 10 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 11 | } 12 | }, 13 | { 14 | "name": "Win32-Debug", 15 | "inherits": "default", 16 | "toolchainFile": "${sourceDir}/cmake/i686-w64-mingw32-gcc.cmake", 17 | "cacheVariables": { 18 | "CMAKE_BUILD_TYPE": "Debug" 19 | } 20 | }, 21 | { 22 | "name": "Win64-Debug", 23 | "inherits": "default", 24 | "toolchainFile": "${sourceDir}/cmake/x86_64-w64-mingw32-gcc.cmake", 25 | "cacheVariables": { 26 | "CMAKE_BUILD_TYPE": "Debug" 27 | } 28 | } 29 | ], 30 | "buildPresets": [ 31 | { 32 | "name": "Win32-Debug", 33 | "configurePreset": "Win32-Debug" 34 | }, 35 | { 36 | "name": "Win64-Debug", 37 | "configurePreset": "Win64-Debug" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /tests/test.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file tests.h 3 | * \author Tilen MAJERLE 4 | * \brief 5 | * \version 0.1 6 | * \date 2025-03-17 7 | * 8 | * @copyright Copyright (c) 2025 9 | * 10 | */ 11 | #ifndef TEST_HDR_H 12 | #define TEST_HDR_H 13 | 14 | #include "math.h" 15 | 16 | /* Assert check */ 17 | #define TEST_ASSERT(x) \ 18 | do { \ 19 | if (!(x)) { \ 20 | printf("Assert in file %s and on line %d failed with condition (" #x ")\r\n", __FILE__, (int)__LINE__); \ 21 | return -1; \ 22 | } \ 23 | } while (0) 24 | 25 | #define INT_IS_EQUAL(a, b) ((a) == (b)) 26 | #define FLOAT_IS_EQUAL(a, b) ((((a) * (a)) - ((b) * (b))) <= 0.0001) 27 | 28 | #endif /* TEST_HDR_H */ -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Setup project 4 | project(LwLibPROJECT) 5 | 6 | # Set default compile flags for GCC 7 | if(CMAKE_COMPILER_IS_GNUCXX) 8 | message(STATUS "GCC detected, adding compile flags") 9 | set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wextra") 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic -Wall -Wextra") 11 | endif(CMAKE_COMPILER_IS_GNUCXX) 12 | 13 | enable_testing() 14 | add_executable(${CMAKE_PROJECT_NAME}) 15 | target_sources(${CMAKE_PROJECT_NAME} PRIVATE 16 | ${CMAKE_CURRENT_LIST_DIR}/test_main.c 17 | ) 18 | target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE 19 | ${CMAKE_CURRENT_LIST_DIR}/ 20 | ) 21 | 22 | # Include file that can add more sources and prepare lib parameters 23 | if(DEFINED TEST_CMAKE_FILE_NAME AND NOT "${TEST_CMAKE_FILE_NAME}" STREQUAL "") 24 | include(${TEST_CMAKE_FILE_NAME}) 25 | endif() 26 | 27 | # Add subdir with lwjson and link to project 28 | add_subdirectory("../lwjson" lwjson) 29 | target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC lwjson) 30 | target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC lwjson_debug) 31 | target_compile_definitions(lwjson PUBLIC LWJSON_DEV) 32 | 33 | # Add test 34 | add_test(NAME Test COMMAND $) 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release workflow 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 8 | 9 | jobs: 10 | # Create the release from the tag 11 | create-release: 12 | name: Create Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Create Release 18 | id: create_release 19 | uses: actions/create-release@v1 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: Release ${{ github.ref }} 25 | body: | 26 | See the [CHANGELOG](CHANGELOG.md) 27 | draft: false 28 | prerelease: false 29 | 30 | # Publish package to PlatformIO 31 | publish-platformio: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Set up Python 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: "3.x" 41 | 42 | - name: Install PlatformIO 43 | run: pip install platformio 44 | 45 | - name: Publish to PlatformIO 46 | env: 47 | PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }} 48 | run: pio pkg publish --type library --non-interactive 49 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import glob, os, shutil, sys, argparse 2 | 3 | def main(args): 4 | retval:int = 0 5 | 6 | rootpath = os.getcwd() 7 | files = [f for f in glob.glob(os.path.join(os.getcwd(), '**', '*.cmake'), recursive=True) if '__build__' not in f] 8 | print(files, flush=True) 9 | 10 | for file in files: 11 | basep = os.path.dirname(file) 12 | print('Test path:', basep, flush=True) 13 | 14 | # Reset directory, delete previous runs 15 | os.chdir(basep) 16 | try: shutil.rmtree('__build__/', ignore_errors=True) 17 | except: pass 18 | 19 | # Run and test 20 | os.mkdir('__build__') 21 | os.chdir('__build__') 22 | print('Configure the CMake', flush=True) 23 | retval |= os.system('cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -S../.. -G Ninja -DTEST_CMAKE_FILE_NAME={}'.format(file)) 24 | 25 | print('Compile', flush=True) 26 | retval |= os.system('cmake --build .') 27 | print('Run test', flush=True) 28 | retval |= os.system('ctest . --output-on-failure -C Debug') 29 | 30 | return retval 31 | 32 | # Get parser 33 | def get_parser(): 34 | parser = argparse.ArgumentParser() 35 | parser.add_argument("--github", required=False, action='store_true', help="Flag if test runs on Github workflow") 36 | return parser 37 | 38 | # Run the script 39 | if __name__ == '__main__': 40 | print('Main script running', flush=True) 41 | 42 | parser = get_parser() 43 | sys.exit(1 if main(parser.parse_args()) != 0 else 0) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightweight JSON text parser 2 | 3 | Library provides generic JSON text parser, that is optimized for embedded systems. 4 | Supports `streaming` parsing or classic parsing with full JSON data available in one big linear memory. 5 | First one being optimized for ultra small microcontrollers, second one being ready for PC applications - or simply when several kB of RAM memory is available at any given point of time 6 | 7 |

Read first: Documentation

8 | 9 | ## Features 10 | 11 | * Written in C (C11), compatible with ``size_t`` for size data types 12 | * RFC 4627 and RFC 8259 compliant 13 | * Based on static token allocation with optional application dynamic pre-allocation 14 | * No recursion during parse operation 15 | * Re-entrant functions 16 | * Zero-copy, no ``malloc`` or ``free`` functions used 17 | * Supports streaming parsing as secondary option 18 | * Optional support for inline comments with `/* comment... */` syntax between any *blank* region of input string 19 | * JSON serializer separate module 20 | * Advanced find algorithm for tokens 21 | * Test coverage is available 22 | * User friendly MIT license 23 | 24 | ## Contribute 25 | 26 | Fresh contributions are always welcome. Simple instructions to proceed: 27 | 28 | 1. Fork Github repository 29 | 2. Follow [C style & coding rules](https://github.com/MaJerle/c-code-style) already used in the project 30 | 3. Create a pull request to develop branch with new features or bug fixes 31 | 32 | Alternatively you may: 33 | 34 | 1. Report a bug 35 | 2. Ask for a feature request 36 | -------------------------------------------------------------------------------- /docs/static/css/common.css: -------------------------------------------------------------------------------- 1 | /* Center aligned text */ 2 | .center { 3 | text-align: center; 4 | } 5 | 6 | /* Paragraph with main links on index page */ 7 | .index-links { 8 | text-align: center; 9 | margin-top: 10px; 10 | } 11 | .index-links a { 12 | display: inline-block; 13 | border: 1px solid #0E4263; 14 | padding: 5px 20px; 15 | margin: 2px 5px; 16 | background: #2980B9; 17 | border-radius: 4px; 18 | color: #FFFFFF; 19 | } 20 | .index-links a:hover, .index-links a:active { 21 | background: #0E4263; 22 | } 23 | 24 | /* Table header p w/0 margin */ 25 | .index-links a table thead th { 26 | vertical-align: middle; 27 | } 28 | 29 | table thead th p { 30 | margin: 0; 31 | } 32 | 33 | .table-nowrap td { 34 | white-space: normal !important; 35 | } 36 | 37 | /* Breathe output changes */ 38 | .breathe-sectiondef.container { 39 | background: #f9f9f9; 40 | padding: 10px; 41 | margin-bottom: 10px; 42 | border: 1px solid #efefef; 43 | } 44 | .breathe-sectiondef.container .breathe-sectiondef-title { 45 | background: #2980b9; 46 | color: #FFFFFF; 47 | padding: 4px; 48 | margin: -10px -10px 0 -10px; 49 | } 50 | .breathe-sectiondef.container .function, 51 | .breathe-sectiondef.container .member, 52 | .breathe-sectiondef.container .class, 53 | .breathe-sectiondef.container .type { 54 | border-bottom: 1px solid #efefef; 55 | } 56 | .breathe-sectiondef.container .function:last-child, 57 | .breathe-sectiondef.container .member:last-child, 58 | .breathe-sectiondef.container .class:last-child, 59 | .breathe-sectiondef.container .type:last-child { 60 | border-bottom: none; 61 | margin-bottom: 0; 62 | } 63 | 64 | /*# sourceMappingURL=common.css.map */ 65 | -------------------------------------------------------------------------------- /docs/user-manual/how-it-works.rst: -------------------------------------------------------------------------------- 1 | .. _how_it_works: 2 | 3 | How it works 4 | ============ 5 | 6 | LwJSON fully complies with *RFC 4627* memo and supports ``2`` types of parsing: 7 | 8 | * Parsing with full data available as single linear memory (primary option) 9 | * Stream parsing with partial available bytes at any given point of time - advanced state machine 10 | 11 | When full data are available, standard parsing is used with tokens, 12 | that contain references to start/stop indexes of the strings and other primitives and provide 13 | full device tree - sort of custom hash-map value. 14 | 15 | When JSON is successfully parsed, there are several tokens used, one for each JSON data type. 16 | Each token consists of: 17 | 18 | * Token type 19 | * Token parameter name (*key*) and its length 20 | * Token value or pointer to first child (in case of *object* or *array* types) 21 | 22 | As an example, JSON text ``{"mykey":"myvalue"}`` will be parsed into ``2`` tokens: 23 | 24 | * First token is the opening bracket and has type *object* as it holds children tokens 25 | * Second token has name ``mykey``, its type is *string* with value set as ``myvalue`` 26 | 27 | .. warning:: 28 | When JSON input string is parsed, create tokens use input string as a reference. 29 | This means that until JSON parsed tokens are being used, original text must stay as-is. 30 | Any modification of source JSON input may destroy references from the token tree and hence generate wrong output for the user 31 | 32 | .. tip:: 33 | See :ref:`stream` for implementation of streaming parser where full data do not need to be available at any given time. 34 | 35 | .. toctree:: 36 | :maxdepth: 2 -------------------------------------------------------------------------------- /examples/example_stream.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | /* Test string to parser */ 5 | static const char* json_str = "{\"k1\":\"v1\",\"k2\":[true, false]}"; 6 | 7 | /* LwJSON stream parser */ 8 | static lwjson_stream_parser_t stream_parser; 9 | 10 | /** 11 | * \brief Callback function for various events 12 | * \param jsp: JSON stream parser object 13 | * \param type: Event type 14 | */ 15 | static void 16 | prv_example_callback_func(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type) { 17 | /* Get a value corresponsing to "k1" key */ 18 | if (jsp->stack_pos >= 2 /* Number of stack entries must be high */ 19 | && lwjson_stack_seq_2(jsp, 0, OBJECT, KEY) && strcmp(jsp->stack[1].meta.name, "k1") == 0) { 20 | printf("Got key '%s' with value '%s'\r\n", jsp->stack[1].meta.name, jsp->data.str.buff); 21 | } 22 | (void)type; 23 | } 24 | 25 | /* Parse JSON */ 26 | void 27 | example_stream_run(void) { 28 | lwjsonr_t res; 29 | printf("\r\n\r\nParsing stream\r\n"); 30 | lwjson_stream_init(&stream_parser, prv_example_callback_func); 31 | 32 | /* Demonstrate as stream inputs */ 33 | for (const char* c = json_str; *c != '\0'; ++c) { 34 | res = lwjson_stream_parse(&stream_parser, *c); 35 | if (res == lwjsonSTREAMINPROG) { 36 | } else if (res == lwjsonSTREAMWAITFIRSTCHAR) { 37 | printf("Waiting first character\r\n"); 38 | } else if (res == lwjsonSTREAMDONE) { 39 | printf("Done\r\n"); 40 | } else { 41 | printf("Error\r\n"); 42 | break; 43 | } 44 | } 45 | printf("Parsing completed\r\n"); 46 | } 47 | -------------------------------------------------------------------------------- /examples/example_traverse.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | /* LwJSON instance and tokens */ 5 | static lwjson_token_t tokens[128]; 6 | static lwjson_t lwjson; 7 | 8 | /* Parse JSON */ 9 | void 10 | example_traverse_run(void) { 11 | /* Initialize and pass statically allocated tokens */ 12 | lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens)); 13 | 14 | /* Try to parse input string */ 15 | if (lwjson_parse(&lwjson, "{\"mykey\":\"myvalue\",\"num\":1,\"obj\":{},\"arr\":[1,2,3,4]}") == lwjsonOK) { 16 | lwjson_token_t* t; 17 | printf("JSON parsed..\r\n"); 18 | 19 | /* Get very first token as top object */ 20 | t = lwjson_get_first_token(&lwjson); 21 | if (t->type == LWJSON_TYPE_ARRAY) { 22 | printf("JSON starts with array..\r\n"); 23 | } else if (t->type == LWJSON_TYPE_OBJECT) { 24 | printf("JSON starts with object..\r\n"); 25 | } else { 26 | printf("This should never happen..\r\n"); 27 | } 28 | 29 | /* Now print all keys in the object */ 30 | for (const lwjson_token_t* tkn = lwjson_get_first_child(t); tkn != NULL; tkn = tkn->next) { 31 | printf("Token: %.*s", (int)tkn->token_name_len, tkn->token_name); 32 | if (tkn->type == LWJSON_TYPE_ARRAY || tkn->type == LWJSON_TYPE_OBJECT) { 33 | printf(": Token is array or object...check children tokens if any, in recursive mode.."); 34 | /* Get first child of token */ 35 | //lwjson_get_first_child(tkn); 36 | } 37 | printf("\n"); 38 | } 39 | 40 | /* Call this when not used anymore */ 41 | lwjson_free(&lwjson); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lwjson/src/include/lwjson/lwjson_opts_template.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | 44 | #endif /* LWJSON_OPTS_HDR_H */ 45 | -------------------------------------------------------------------------------- /tests/test_json_find/lwjson_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | #define LWJSON_CFG_JSON5 1 44 | #define LWJSON_CFG_COMMENTS 1 45 | 46 | #endif /* LWJSON_OPTS_HDR_H */ 47 | -------------------------------------------------------------------------------- /tests/test_json_data_types/lwjson_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | #define LWJSON_CFG_JSON5 1 44 | #define LWJSON_CFG_COMMENTS 1 45 | 46 | #endif /* LWJSON_OPTS_HDR_H */ 47 | -------------------------------------------------------------------------------- /tests/test_json_parse_basic/lwjson_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | #define LWJSON_CFG_JSON5 1 44 | #define LWJSON_CFG_COMMENTS 1 45 | 46 | #endif /* LWJSON_OPTS_HDR_H */ 47 | -------------------------------------------------------------------------------- /tests/test_parse_token_count/lwjson_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | #define LWJSON_CFG_JSON5 1 44 | #define LWJSON_CFG_COMMENTS 1 45 | 46 | #endif /* LWJSON_OPTS_HDR_H */ 47 | -------------------------------------------------------------------------------- /tests/test_stream/lwjson_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | #define LWJSON_CFG_JSON5 1 44 | #define LWJSON_CFG_COMMENTS 1 45 | 46 | #define LWJSON_CFG_STREAM_STRING_MAX_LEN 16 47 | 48 | #endif /* LWJSON_OPTS_HDR_H */ 49 | -------------------------------------------------------------------------------- /tests/test_serializer/lwjson_opts.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opts_template.h 3 | * \brief Template config file 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2025 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPTS_HDR_H 35 | #define LWJSON_OPTS_HDR_H 36 | 37 | /* Rename this file to "lwjson_opts.h" for your application */ 38 | 39 | /* 40 | * Open "include/lwjson/lwjson_opt.h" and 41 | * copy & replace here settings you want to change values 42 | */ 43 | #define LWJSON_CFG_JSON5 1 44 | #define LWJSON_CFG_COMMENTS 1 45 | 46 | #define LWJSON_CFG_STREAM_STRING_MAX_LEN 16 47 | 48 | #endif /* LWJSON_OPTS_HDR_H */ 49 | -------------------------------------------------------------------------------- /docs/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
LwJSON
LwJSON
-------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Develop 4 | 5 | - Added the JSON serialized new feature 6 | 7 | ## 1.8.1 8 | 9 | - Fix the platformio library package description 10 | 11 | ## 1.8.0 12 | 13 | - Rework library CMake with removed INTERFACE type 14 | - Improve the calculation with square-multiply algorithm (@SKlimaRA) 15 | - Improve the input boundary checks (@DKubasekRA) 16 | 17 | ## 1.7.0 18 | 19 | - Add clang-tidy 20 | - Add helper functions for sequence check in stream parsing 21 | - Add support to discard invalid JSON stream 22 | - Fixed some invalid JSON parsing in the streaming module 23 | 24 | ## 1.6.1 25 | 26 | - Fix critical issue - missing correct return when waiting for first character. Should be `lwjsonSTREAMWAITFIRSTCHAR` 27 | 28 | ## 1.6.0 29 | 30 | - Split CMakeLists.txt files between library and executable 31 | - Change license year to 2022 32 | - Fix GCC warning for incompatible comparison types 33 | - Update code style with astyle 34 | - Add support for stream parsing - first version 35 | - Add `.clang-format` 36 | - Add `lwjsonSTREAMDONE` return code when streamer well parsed some JSON and reached end of string 37 | - Add option to reset stream state machine 38 | 39 | ## 1.5.0 40 | 41 | - Add string compare feature 42 | - Add string compare with custom length feature 43 | 44 | ## 1.4.0 45 | 46 | - Add support with input string with length specifier 47 | - Add VSCode project for Win32 compilation 48 | 49 | ## 1.3.0 50 | 51 | - Added support for inline `/* */` comments 52 | 53 | ## v1.2.0 54 | 55 | - Added `lwjson_find_ex` function to accept token pointer as starting reference 56 | - Update of the docs for *find* 57 | - Remove unused reset and add free function for future dynamic allocation support 58 | 59 | ## v1.1.0 60 | 61 | - Improved find algorithm to match array index 62 | - Added more test code 63 | 64 | ## v1.0.2 65 | 66 | - Fix wrong parsing of hex in some corner cases 67 | - Add more robust code to handle errorneous JSON input 68 | 69 | ## v1.0.1 70 | 71 | - Added test code 72 | - Fixed bug with improper string parsing 73 | 74 | ## v1.0.0 75 | 76 | - First stable release 77 | - Compliant with RFC 4627 for JSON 78 | - Full features JSON parser 79 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cppbuild", 6 | "label": "Build project", 7 | "command": "cmake", 8 | "args": ["--build", "${command:cmake.buildDirectory}", "-j", "8"], 9 | "options": { 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | "problemMatcher": ["$gcc"], 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | } 17 | }, 18 | { 19 | "type": "shell", 20 | "label": "Re-build project", 21 | "command": "cmake", 22 | "args": ["--build", "${command:cmake.buildDirectory}", "--clean-first", "-v", "-j", "8"], 23 | "options": { 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | "problemMatcher": ["$gcc"], 27 | }, 28 | { 29 | "type": "shell", 30 | "label": "Clean project", 31 | "command": "cmake", 32 | "args": ["--build", "${command:cmake.buildDirectory}", "--target", "clean"], 33 | "options": { 34 | "cwd": "${workspaceFolder}" 35 | }, 36 | "problemMatcher": [] 37 | }, 38 | { 39 | "type": "shell", 40 | "label": "Run application", 41 | "command": "${command:cmake.launchTargetPath}", 42 | "args": [], 43 | "problemMatcher": [], 44 | }, 45 | { 46 | "label": "Docs: Install python plugins from requirements.txt file", 47 | "type": "shell", 48 | "command": "python -m pip install -r requirements.txt", 49 | "options": { 50 | "cwd": "${workspaceFolder}/docs" 51 | }, 52 | "problemMatcher": [] 53 | }, 54 | { 55 | "label": "Docs: Generate html", 56 | "type": "shell", 57 | "command": ".\\make html", 58 | "options": { 59 | "cwd": "${workspaceFolder}/docs" 60 | }, 61 | "problemMatcher": [] 62 | }, 63 | { 64 | "label": "Docs: Clean build directory", 65 | "type": "shell", 66 | "command": ".\\make clean", 67 | "options": { 68 | "cwd": "${workspaceFolder}/docs" 69 | }, 70 | "problemMatcher": [] 71 | }, 72 | ] 73 | } -------------------------------------------------------------------------------- /lwjson/library.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # LIB_PREFIX: LWJSON 3 | # 4 | # This file provides set of variables for end user 5 | # and also generates one (or more) libraries, that can be added to the project using target_link_libraries(...) 6 | # 7 | # Before this file is included to the root CMakeLists file (using include() function), user can set some variables: 8 | # 9 | # LWJSON_OPTS_FILE: If defined, it is the path to the user options file. If not defined, one will be generated for you automatically 10 | # LWJSON_COMPILE_OPTIONS: If defined, it provide compiler options for generated library. 11 | # LWJSON_COMPILE_DEFINITIONS: If defined, it provides "-D" definitions to the library build 12 | # 13 | 14 | # Custom include directory 15 | set(LWJSON_CUSTOM_INC_DIR ${CMAKE_CURRENT_BINARY_DIR}/lib_inc) 16 | 17 | # Setup generic source files 18 | set(lwjson_core_SRCS 19 | ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson.c 20 | ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson_stream.c 21 | ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson_serializer.c 22 | ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson_utils.c 23 | ) 24 | 25 | # Debug sources 26 | set(lwjson_debug_SRCS 27 | ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson_debug.c 28 | ) 29 | 30 | # Setup include directories 31 | set(lwjson_include_DIRS 32 | ${CMAKE_CURRENT_LIST_DIR}/src/include 33 | ${LWJSON_CUSTOM_INC_DIR} 34 | ) 35 | 36 | # Register core library to the system 37 | add_library(lwjson) 38 | target_sources(lwjson PRIVATE ${lwjson_core_SRCS}) 39 | target_include_directories(lwjson PUBLIC ${lwjson_include_DIRS}) 40 | target_compile_options(lwjson PRIVATE ${LWJSON_COMPILE_OPTIONS}) 41 | target_compile_definitions(lwjson PRIVATE ${LWJSON_COMPILE_DEFINITIONS}) 42 | 43 | # Register lwjson debug module 44 | add_library(lwjson_debug) 45 | target_sources(lwjson_debug PRIVATE ${lwjson_debug_SRCS}) 46 | target_include_directories(lwjson_debug PUBLIC ${lwjson_include_DIRS}) 47 | target_compile_options(lwjson_debug PRIVATE ${LWJSON_COMPILE_OPTIONS}) 48 | target_compile_definitions(lwjson_debug PRIVATE ${LWJSON_COMPILE_DEFINITIONS}) 49 | target_link_libraries(lwjson_debug PUBLIC lwjson) 50 | 51 | # Create config file if user didn't provide one info himself 52 | if(NOT LWJSON_OPTS_FILE) 53 | message(STATUS "Using default lwjson_opts.h file") 54 | set(LWJSON_OPTS_FILE ${CMAKE_CURRENT_LIST_DIR}/src/include/lwjson/lwjson_opts_template.h) 55 | else() 56 | message(STATUS "Using custom lwjson_opts.h file from ${LWJSON_OPTS_FILE}") 57 | endif() 58 | 59 | configure_file(${LWJSON_OPTS_FILE} ${LWJSON_CUSTOM_INC_DIR}/lwjson_opts.h COPYONLY) 60 | -------------------------------------------------------------------------------- /lwjson/src/include/lwjson/lwjson_utils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_utils.h 3 | * \brief JSON string utility functions for character escaping and unescaping 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2025 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_UTILS_HDR_H 35 | #define LWJSON_UTILS_HDR_H 36 | 37 | /* Include system headers */ 38 | #include 39 | #include 40 | 41 | /* Include lwjson library */ 42 | #include "lwjson.h" 43 | #include "lwjson_serializer.h" 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif 48 | 49 | /** 50 | * \ingroup LWJSON 51 | * \{ 52 | */ 53 | 54 | lwjsonr_t lwjson_utils_escape_string(const char* input, size_t input_len, char* output, size_t output_capacity, 55 | size_t* bytes_written); 56 | lwjsonr_t lwjson_utils_escape_string_cb(const char* input, size_t input_len, lwjson_serializer_callback_fn callback, 57 | void* ctx); 58 | lwjsonr_t lwjson_utils_unescape_string(const char* input, size_t input_len, char* output, size_t output_capacity, 59 | size_t* bytes_written); 60 | 61 | /** 62 | * \} 63 | */ 64 | 65 | #ifdef __cplusplus 66 | } 67 | #endif 68 | 69 | #endif /* LWJSON_UTILS_HDR_H */ 70 | -------------------------------------------------------------------------------- /docs/user-manual/stream.rst: -------------------------------------------------------------------------------- 1 | .. _stream: 2 | 3 | Stream parser 4 | ============= 5 | 6 | Streaming parser implementation is alternative option versus standard tokenized one, in the sense that: 7 | 8 | * There is no need to have full JSON available at one time to have successful parsing 9 | * It can be utilized to parse very large JSON strings on very small systems with limited memory 10 | * It allows users to *take* from the stream only necessary parts and store them to local more system-friendly variable 11 | 12 | This type of parser does not utilize use of tokens, rather focuses on the callback function, 13 | where user is in charge to manually understand token structure and get useful data from it. 14 | 15 | Stream parser introduces *stack* mechanism instead - to keep the track of depthness during parsing the process. 16 | ``3`` different element types are stored on *local stack*: 17 | 18 | * Start of object, with ``{`` character 19 | * Start of array, with ``[`` character 20 | * Key from the *object* entry 21 | 22 | .. note:: 23 | Stack is nested as long as JSON input stream is nested in the same way 24 | 25 | Consider this input string: ``{"k1":"v1","k2":[true, false]}``. 26 | During parsing procedure, at some point of time, these events will occur: 27 | 28 | #. Start of *object* detected - *object* pushed to stack 29 | 30 | #. *key* element with name ``k1`` detected and pushed to stack 31 | 32 | #. *string* ``v1`` parsed as *string-value* 33 | #. *key* element with name ``k1`` popped from stack 34 | #. *key* element with name ``k2`` detected and pushed to stack 35 | 36 | #. Start of *array* detected - *array* pushed to stack 37 | 38 | #. ``true`` primitive detected 39 | #. ``false`` primitive detected 40 | #. End of *array* detected - *array* popped from stack 41 | #. *key* element with name ``k2`` popped from stack 42 | #. End of *object* detected - *object* popped from stack 43 | 44 | Each of these events is reported to user in the callback function. 45 | 46 | An example of the stream parsing: 47 | 48 | .. literalinclude:: ../../examples/example_stream.c 49 | :language: c 50 | :linenos: 51 | :caption: Parse JSON data as a stream object 52 | 53 | Example 54 | ******* 55 | 56 | For the purpose of example, the following JSON input... 57 | 58 | .. literalinclude:: ../examples/stream.json 59 | :language: json 60 | :linenos: 61 | :caption: JSON input for streaming 62 | 63 | \... will output the log as: 64 | 65 | .. literalinclude:: ../examples/stream_log.txt 66 | :linenos: 67 | :caption: JSON development log for the various events 68 | 69 | .. toctree:: 70 | :maxdepth: 2 -------------------------------------------------------------------------------- /trial_env/trial_run.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | #include "windows.h" 4 | 5 | /* LwJSON stream parser */ 6 | static lwjson_stream_parser_t stream_parser; 7 | 8 | /** 9 | * \brief Callback function for various events 10 | * \param jsp: JSON stream parser object 11 | * \param type: Event type 12 | */ 13 | static void 14 | prv_example_callback_func(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type) { 15 | /* IO device part of the parsing goes here */ 16 | if (jsp->stack_pos == 4 && lwjson_stack_seq_4(jsp, 0, OBJECT, KEY, OBJECT, KEY)) { 17 | } else if (jsp->stack_pos == 7 && lwjson_stack_seq_7(jsp, 0, OBJECT, KEY, OBJECT, KEY, ARRAY, OBJECT, KEY)) { 18 | } 19 | 20 | (void)type; 21 | /* ... */ 22 | } 23 | 24 | /* Parse JSON */ 25 | void 26 | trial_stream_run(void) { 27 | lwjsonr_t res; 28 | HANDLE f; 29 | DWORD file_size; 30 | char* json_text = NULL; 31 | 32 | printf("\r\n\r\nTrial parsing test stream\r\n"); 33 | lwjson_stream_init(&stream_parser, prv_example_callback_func); 34 | 35 | f = CreateFile(TEXT("trial_env/trial_run.json"), 36 | GENERIC_READ, // open for reading 37 | 0, // do not share 38 | NULL, // no security 39 | OPEN_EXISTING, // existing file only 40 | FILE_ATTRIBUTE_NORMAL, // normal file 41 | NULL); // no attr. template 42 | 43 | if (f == INVALID_HANDLE_VALUE) { 44 | printf("Could not open file..\r\n"); 45 | goto exit; 46 | } 47 | if ((file_size = GetFileSize(f, NULL)) == INVALID_FILE_SIZE) { 48 | printf("Invalid file size..\r\n"); 49 | goto exit; 50 | } else if (file_size == 0) { 51 | printf("File is empty..\r\n"); 52 | goto exit; 53 | } 54 | if ((json_text = calloc((size_t)(file_size + 1), sizeof(*json_text))) == NULL) { 55 | printf("Could not allocate memory..\r\n"); 56 | goto exit; 57 | } 58 | if (ReadFile(f, json_text, file_size, NULL, NULL) == 0) { 59 | printf("Could not read full file..\r\n"); 60 | goto exit; 61 | } 62 | 63 | /* Demonstrate as stream inputs */ 64 | for (const char* c = json_text; *c != '\0'; ++c) { 65 | res = lwjson_stream_parse(&stream_parser, *c); 66 | if (res == lwjsonSTREAMINPROG) { 67 | } else if (res == lwjsonSTREAMWAITFIRSTCHAR) { 68 | printf("Waiting first character\r\n"); 69 | } else if (res == lwjsonSTREAMDONE) { 70 | printf("Done\r\n"); 71 | } else { 72 | printf("Error\r\n"); 73 | break; 74 | } 75 | } 76 | exit: 77 | free(json_text); 78 | printf("Parsing completed\r\n"); 79 | } 80 | -------------------------------------------------------------------------------- /tests/test_parse_token_count/test_parse_token_count.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | #define RUN_TEST(exp_token_count, json_str) \ 5 | do { \ 6 | if (lwjson_parse(&lwjson, (json_str)) == lwjsonOK) { \ 7 | if ((lwjson.next_free_token_pos + 1) == exp_token_count) { \ 8 | ++test_passed; \ 9 | } else { \ 10 | ++test_failed; \ 11 | printf("Test failed for JSON token count on input %s\r\n", (json_str)); \ 12 | } \ 13 | lwjson_free(&lwjson); \ 14 | } else { \ 15 | ++test_failed; \ 16 | printf("Test failed for JSON parse on input %s\r\n", (json_str)); \ 17 | } \ 18 | } while (0) 19 | 20 | /** 21 | * \brief Path and type parse check 22 | */ 23 | typedef struct { 24 | const char* path; /*!< Path in parsed JSON */ 25 | lwjson_type_t type; /*!< expected data type in JSON */ 26 | } test_path_type_t; 27 | 28 | /* LwJSON instance and tokens */ 29 | static lwjson_token_t tokens[4096]; 30 | static lwjson_t lwjson; 31 | 32 | /** 33 | * \brief Run all tests entry point 34 | */ 35 | int 36 | test_run(void) { 37 | size_t test_failed = 0, test_passed = 0; 38 | 39 | printf("---\r\nTest JSON token count..\r\n"); 40 | if (lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens)) != lwjsonOK) { 41 | printf("JSON init failed\r\n"); 42 | return -1; 43 | } 44 | 45 | /* Run token count tests */ 46 | RUN_TEST(2, "{\"k\":1}"); 47 | RUN_TEST(3, "{\"k\":1,\"k\":2}"); 48 | RUN_TEST(4, "{\"k\":1,\"k\":[1]}"); 49 | RUN_TEST(5, "{\"k\":1,\"k\":[1,2]}"); 50 | RUN_TEST(6, "{\"k\":1,\"k\":[1,2,5]}"); 51 | RUN_TEST(12, "{\"k\":1,\"k\":[[1,2],[3,4],[5,6]]}"); 52 | RUN_TEST(4, "{\"k\":{\"k\":{\"k\":[]}}}"); 53 | RUN_TEST(6, "{\"k\":{\"k\":{\"k\":[[[]]]}}}"); 54 | RUN_TEST(6, "{\"k\":[{\"k\":1},{\"k\":2}]}"); 55 | 56 | /* Print results */ 57 | printf("JSON token count test result pass/fail: %d/%d\r\n\r\n", (int)test_passed, (int)test_failed); 58 | return test_failed > 0 ? -1 : 0; 59 | } 60 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | LwJSON |version| documentation 2 | ============================== 3 | 4 | Welcome to the documentation for version |version|. 5 | 6 | LwJSON is a generic JSON parser library optimized for embedded systems. 7 | 8 | .. image:: static/images/logo.svg 9 | :align: center 10 | 11 | .. rst-class:: center 12 | .. rst-class:: index_links 13 | 14 | :ref:`download_library` :ref:`getting_started` `Open Github `_ `Donate `_ 15 | 16 | Features 17 | ^^^^^^^^ 18 | 19 | * Written in C (C11), compatible with ``size_t`` for size data types 20 | * RFC 4627 and RFC 8259 compliant 21 | * Based on static token allocation with optional application dynamic pre-allocation 22 | * No recursion during parse operation 23 | * Re-entrant functions 24 | * Zero-copy, no ``malloc`` or ``free`` functions used 25 | * Supports streaming parsing as secondary option 26 | * Optional support for inline comments with `/* comment... */` syntax between any *blank* region of input string 27 | * JSON serializer separate module 28 | * Advanced find algorithm for tokens 29 | * Tests coverage is available 30 | * User friendly MIT license 31 | 32 | Requirements 33 | ^^^^^^^^^^^^ 34 | 35 | * C compiler 36 | * Few kB of ROM memory 37 | 38 | Contribute 39 | ^^^^^^^^^^ 40 | 41 | Fresh contributions are always welcome. Simple instructions to proceed: 42 | 43 | #. Fork Github repository 44 | #. Respect `C style & coding rules `_ used by the library 45 | #. Create a pull request to ``develop`` branch with new features or bug fixes 46 | 47 | Alternatively you may: 48 | 49 | #. Report a bug 50 | #. Ask for a feature request 51 | 52 | Example code 53 | ^^^^^^^^^^^^ 54 | 55 | .. literalinclude:: ../examples/example_minimal.c 56 | :language: c 57 | :linenos: 58 | :caption: Example code 59 | 60 | License 61 | ^^^^^^^ 62 | 63 | .. literalinclude:: ../LICENSE 64 | 65 | Table of contents 66 | ^^^^^^^^^^^^^^^^^ 67 | 68 | .. toctree:: 69 | :maxdepth: 2 70 | :caption: Contents 71 | 72 | self 73 | get-started/index 74 | user-manual/index 75 | api-reference/index 76 | changelog/index 77 | authors/index 78 | 79 | .. toctree:: 80 | :maxdepth: 2 81 | :caption: Other projects 82 | :hidden: 83 | 84 | LwBTN - Button manager 85 | LwDTC - DateTimeCron 86 | LwESP - ESP-AT library 87 | LwEVT - Event manager 88 | LwGPS - GPS NMEA parser 89 | LwCELL - Cellular modem host AT library 90 | LwJSON - JSON parser 91 | LwMEM - Memory manager 92 | LwOW - OneWire with UART 93 | LwPKT - Packet protocol 94 | LwPRINTF - Printf 95 | LwRB - Ring buffer 96 | LwSHELL - Shell 97 | LwUTIL - Utility functions 98 | LwWDG - RTOS task watchdog 99 | -------------------------------------------------------------------------------- /docs/static/images/json_token_list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
{"k1":"v1","k4":{"a":2},"k5":[1,2]}
[Not supported by viewer]
-------------------------------------------------------------------------------- /examples/example_stream_with_user_data.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "lwjson/lwjson.h" 4 | 5 | typedef struct example_data_struct_t { 6 | uint8_t k1[10]; 7 | char* k2; 8 | int k2_len; 9 | int k2_pos; 10 | } example_data_struct_t; 11 | 12 | /* Test string to parser */ 13 | static const char* json_str = "{\"k1\":\"v1\",\"k2\":[true, false, true]}"; 14 | 15 | /* LwJSON stream parser */ 16 | static lwjson_stream_parser_t stream_parser; 17 | 18 | /** 19 | * \brief Callback function for various events 20 | * \param jsp: JSON stream parser object 21 | * \param type: Event type 22 | */ 23 | static void 24 | prv_example_callback_func(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type) { 25 | //Get the data struct from user data 26 | example_data_struct_t* data = lwjson_stream_get_user_data(jsp); 27 | 28 | if (jsp->stack_pos >= 2 /* Number of stack entries must be high */ 29 | && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT /* First must be object */ 30 | && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY /* We need key to be before */ 31 | && strcmp(jsp->stack[1].meta.name, "k1") == 0) { 32 | printf("Got key '%s' with value '%s'\r\n", jsp->stack[1].meta.name, jsp->data.str.buff); 33 | strncpy((char*)data->k1, jsp->data.str.buff, sizeof(data->k1) - 1); 34 | } 35 | if (jsp->stack_pos >= 2 /* Number of stack entries must be high */ 36 | && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT /* First must be object */ 37 | && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY /* We need key to be before */ 38 | && strcmp(jsp->stack[1].meta.name, "k2") == 0) { 39 | printf("Got key '%s' with value '%s'\r\n", jsp->stack[1].meta.name, jsp->data.str.buff); 40 | if (jsp->stack_pos >= 3 && jsp->stack[2].type == LWJSON_STREAM_TYPE_ARRAY 41 | && jsp->stack[2].meta.index < data->k2_len) { 42 | printf("Got array value '%s' index = %d \r\n", jsp->data.str.buff, jsp->stack[2].meta.index); 43 | data->k2[jsp->stack[2].meta.index] = (strncmp(jsp->data.str.buff, "true", 4) == 0); 44 | data->k2_pos = jsp->stack[2].meta.index + 1; 45 | } 46 | } 47 | (void)type; 48 | } 49 | 50 | /* Parse JSON */ 51 | void 52 | example_stream_run(void) { 53 | lwjsonr_t res; 54 | printf("\r\n\r\nParsing stream\r\n"); 55 | example_data_struct_t data; 56 | char k2_buff[10]; 57 | data.k2 = k2_buff; 58 | data.k2_len = sizeof(k2_buff); 59 | data.k2_pos = 0; 60 | lwjson_stream_init(&stream_parser, prv_example_callback_func); 61 | lwjson_stream_set_user_data(&stream_parser, &data); 62 | /* Demonstrate as stream inputs */ 63 | for (const char* c = json_str; *c != '\0'; ++c) { 64 | res = lwjson_stream_parse(&stream_parser, *c); 65 | if (res == lwjsonSTREAMINPROG) { 66 | } else if (res == lwjsonSTREAMWAITFIRSTCHAR) { 67 | printf("Waiting first character\r\n"); 68 | } else if (res == lwjsonSTREAMDONE) { 69 | printf("Done\r\n"); 70 | } else { 71 | printf("Error\r\n"); 72 | break; 73 | } 74 | } 75 | printf("Parsing completed\r\n"); 76 | printf("data: k1 = '%s'\r\n", data.k1); 77 | for (int i = 0; i < data.k2_pos; i++) { 78 | printf("data: k2[%d] = %d\r\n", i, data.k2[i]); 79 | } 80 | } 81 | 82 | int 83 | main(void) { 84 | example_stream_run(); 85 | return 0; 86 | } -------------------------------------------------------------------------------- /docs/static/dark-light/common-dark-light.css: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright 2019 Google LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | :root { 18 | --heading-color: red; 19 | --duration: 0.5s; 20 | --timing: ease; 21 | } 22 | 23 | *, 24 | ::before, 25 | ::after { 26 | box-sizing: border-box; 27 | } 28 | 29 | body { 30 | margin: 0; 31 | transition: 32 | color var(--duration) var(--timing), 33 | background-color var(--duration) var(--timing); 34 | font-family: sans-serif; 35 | font-size: 12pt; 36 | background-color: var(--background-color); 37 | color: var(--text-color); 38 | display: flex; 39 | justify-content: center; 40 | } 41 | 42 | main { 43 | margin: 1rem; 44 | max-width: 30rem; 45 | position: relative; 46 | } 47 | 48 | h1 { 49 | color: var(--heading-color); 50 | text-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color); 51 | transition: text-shadow var(--duration) var(--timing); 52 | } 53 | 54 | img { 55 | max-width: 100%; 56 | height: auto; 57 | transition: filter var(--duration) var(--timing); 58 | } 59 | 60 | p { 61 | line-height: 1.5; 62 | word-wrap: break-word; 63 | overflow-wrap: break-word; 64 | hyphens: auto; 65 | } 66 | 67 | fieldset { 68 | border: solid 0.1rem; 69 | box-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color); 70 | transition: box-shadow var(--duration) var(--timing); 71 | } 72 | 73 | div { 74 | padding: 0.5rem; 75 | } 76 | 77 | aside { 78 | position: absolute; 79 | right: 0; 80 | padding: 0.5rem; 81 | } 82 | 83 | aside:nth-of-type(1) { 84 | top: 0; 85 | } 86 | 87 | aside:nth-of-type(2) { 88 | top: 3rem; 89 | } 90 | 91 | aside:nth-of-type(3) { 92 | top: 7rem; 93 | } 94 | 95 | aside:nth-of-type(4) { 96 | top: 12rem; 97 | } 98 | 99 | #content select, 100 | #content button, 101 | #content input[type="text"], 102 | #content input[type="search"] { 103 | width: 15rem; 104 | } 105 | 106 | dark-mode-toggle { 107 | --dark-mode-toggle-remember-icon-checked: url("checked.svg"); 108 | --dark-mode-toggle-remember-icon-unchecked: url("unchecked.svg"); 109 | --dark-mode-toggle-remember-font: 0.75rem "Helvetica"; 110 | --dark-mode-toggle-legend-font: bold 0.85rem "Helvetica"; 111 | --dark-mode-toggle-label-font: 0.85rem "Helvetica"; 112 | --dark-mode-toggle-color: var(--text-color); 113 | --dark-mode-toggle-background-color: none; 114 | 115 | margin-bottom: 1.5rem; 116 | } 117 | 118 | #dark-mode-toggle-1 { 119 | --dark-mode-toggle-dark-icon: url("sun.png"); 120 | --dark-mode-toggle-light-icon: url("moon.png"); 121 | } 122 | 123 | #dark-mode-toggle-2 { 124 | --dark-mode-toggle-dark-icon: url("sun.svg"); 125 | --dark-mode-toggle-light-icon: url("moon.svg"); 126 | --dark-mode-toggle-icon-size: 2rem; 127 | --dark-mode-toggle-icon-filter: invert(100%); 128 | } 129 | 130 | #dark-mode-toggle-3, 131 | #dark-mode-toggle-4 { 132 | --dark-mode-toggle-dark-icon: url("moon.png"); 133 | --dark-mode-toggle-light-icon: url("sun.png"); 134 | } 135 | 136 | #dark-mode-toggle-3 { 137 | --dark-mode-toggle-remember-filter: invert(100%); 138 | } 139 | 140 | #dark-mode-toggle-4 { 141 | --dark-mode-toggle-active-mode-background-color: var(--accent-color); 142 | --dark-mode-toggle-remember-filter: invert(100%); 143 | } 144 | -------------------------------------------------------------------------------- /lwjson/src/lwjson/lwjson_debug.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_debug.c 3 | * \brief Debug and print function for tokens 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #include 35 | #include 36 | #include "lwjson/lwjson.h" 37 | 38 | /** 39 | * \brief Token print instance 40 | */ 41 | typedef struct { 42 | size_t indent; /*!< Indent level for token print */ 43 | } lwjson_token_print_t; 44 | 45 | /** 46 | * \brief Print token value 47 | * \param[in] prt: Token print instance 48 | * \param[in] token: Token to print 49 | */ 50 | static void 51 | prv_print_token(lwjson_token_print_t* prt, const lwjson_token_t* token) { 52 | #define print_indent() printf("%.*s", (int)((prt->indent)), "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"); 53 | 54 | if (token == NULL) { 55 | return; 56 | } 57 | 58 | /* Check if token has a name */ 59 | print_indent(); 60 | if (token->token_name != NULL) { 61 | printf("\"%.*s\":", (int)token->token_name_len, token->token_name); 62 | } 63 | 64 | /* Print different types */ 65 | switch (token->type) { 66 | case LWJSON_TYPE_OBJECT: 67 | case LWJSON_TYPE_ARRAY: { 68 | printf("%c", token->type == LWJSON_TYPE_OBJECT ? '{' : '['); 69 | if (token->u.first_child != NULL) { 70 | printf("\n"); 71 | ++prt->indent; 72 | for (const lwjson_token_t* t = lwjson_get_first_child(token); t != NULL; t = t->next) { 73 | prv_print_token(prt, t); 74 | } 75 | --prt->indent; 76 | print_indent(); 77 | } 78 | printf("%c", token->type == LWJSON_TYPE_OBJECT ? '}' : ']'); 79 | break; 80 | } 81 | case LWJSON_TYPE_STRING: { 82 | printf("\"%.*s\"", (int)lwjson_get_val_string_length(token), lwjson_get_val_string(token, NULL)); 83 | break; 84 | } 85 | case LWJSON_TYPE_NUM_INT: { 86 | printf("%lld", (long long)lwjson_get_val_int(token)); 87 | break; 88 | } 89 | case LWJSON_TYPE_NUM_REAL: { 90 | printf("%f", (double)lwjson_get_val_real(token)); 91 | break; 92 | } 93 | case LWJSON_TYPE_TRUE: { 94 | printf("true"); 95 | break; 96 | } 97 | case LWJSON_TYPE_FALSE: { 98 | printf("false"); 99 | break; 100 | } 101 | case LWJSON_TYPE_NULL: { 102 | printf("NULL"); 103 | break; 104 | } 105 | default: break; 106 | } 107 | if (token->next != NULL) { 108 | printf(","); 109 | } 110 | printf("\n"); 111 | } 112 | 113 | /** 114 | * \brief Prints and outputs token data to the stream output 115 | * \note This function is not re-entrant 116 | * \param[in] token: Token to print 117 | */ 118 | void 119 | lwjson_print_token(const lwjson_token_t* token) { 120 | lwjson_token_print_t prt = {0}; 121 | prv_print_token(&prt, token); 122 | } 123 | 124 | /** 125 | * \brief Prints and outputs full parsed LwJSON instance 126 | * \note This function is not re-entrant 127 | * \param[in] lwobj: LwJSON instance to print 128 | */ 129 | void 130 | lwjson_print_json(const lwjson_t* lwobj) { 131 | lwjson_token_print_t prt = {0}; 132 | prv_print_token(&prt, lwjson_get_first_token(lwobj)); 133 | } 134 | -------------------------------------------------------------------------------- /lwjson/src/include/lwjson/lwjson_opt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_opt.h 3 | * \brief LwJSON options 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_OPT_HDR_H 35 | #define LWJSON_OPT_HDR_H 36 | 37 | /* Uncomment to ignore user options (or set macro in compiler flags) */ 38 | /* #define LWJSON_IGNORE_USER_OPTS */ 39 | 40 | /* Include application options */ 41 | #ifndef LWJSON_IGNORE_USER_OPTS 42 | #include "lwjson_opts.h" 43 | #endif /* LWJSON_IGNORE_USER_OPTS */ 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif /* __cplusplus */ 48 | 49 | /** 50 | * \defgroup LWJSON_OPT Configuration 51 | * \brief LwJSON options 52 | * \{ 53 | */ 54 | 55 | /** 56 | * \brief Real data type used to parse numbers with floating point number 57 | * \note Data type must be signed, normally `float` or `double` 58 | * 59 | * This is used for numbers in \ref LWJSON_TYPE_NUM_REAL token data type. 60 | */ 61 | #ifndef LWJSON_CFG_REAL_TYPE 62 | #define LWJSON_CFG_REAL_TYPE float 63 | #endif 64 | 65 | /** 66 | * \brief Integer type used to parse numbers 67 | * \note Data type must be signed integer 68 | * 69 | * This is used for numbers in \ref LWJSON_TYPE_NUM_INT token data type. 70 | */ 71 | #ifndef LWJSON_CFG_INT_TYPE 72 | #define LWJSON_CFG_INT_TYPE long long 73 | #endif 74 | 75 | /** 76 | * \brief Enables `1` or disables `0` support for inline comments 77 | * 78 | * Default set to `0` to be JSON compliant 79 | */ 80 | #ifndef LWJSON_CFG_COMMENTS 81 | #define LWJSON_CFG_COMMENTS 0 82 | #endif 83 | 84 | /** 85 | * \brief Memory set function 86 | * 87 | * \note Function footprint is the same as \ref memset 88 | */ 89 | #ifndef LWJSON_MEMSET 90 | #define LWJSON_MEMSET(dst, val, len) memset((dst), (val), (len)) 91 | #endif 92 | 93 | /** 94 | * \brief Memory copy function 95 | * 96 | * \note Function footprint is the same as \ref memcpy 97 | */ 98 | #ifndef LWJSON_MEMCPY 99 | #define LWJSON_MEMCPY(dst, src, len) memcpy((dst), (src), (len)) 100 | #endif 101 | 102 | /** 103 | * \defgroup LWJSON_OPT_STREAM JSON stream 104 | * \brief JSON streaming confiuration 105 | * \{ 106 | */ 107 | 108 | /** 109 | * \brief Max length of token key (object key name) to be available for stack storage 110 | * 111 | */ 112 | #ifndef LWJSON_CFG_STREAM_KEY_MAX_LEN 113 | #define LWJSON_CFG_STREAM_KEY_MAX_LEN 32 114 | #endif 115 | 116 | /** 117 | * \brief Max stack size (depth) in units of \ref lwjson_stream_stack_t structure 118 | * 119 | */ 120 | #ifndef LWJSON_CFG_STREAM_STACK_SIZE 121 | #define LWJSON_CFG_STREAM_STACK_SIZE 16 122 | #endif 123 | 124 | /** 125 | * \brief Max size of string for single parsing in units of bytes 126 | * 127 | */ 128 | #ifndef LWJSON_CFG_STREAM_STRING_MAX_LEN 129 | #define LWJSON_CFG_STREAM_STRING_MAX_LEN 256 130 | #endif 131 | 132 | /** 133 | * \brief Max number of bytes used to parse primitive. 134 | * 135 | * Primitives are all numbers and logical values (null, true, false) 136 | */ 137 | #ifndef LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN 138 | #define LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN 32 139 | #endif 140 | 141 | /** 142 | * \} 143 | */ 144 | 145 | /** 146 | * \defgroup LWJSON_OPT_SERIALIZER JSON serializer 147 | * \brief JSON serialization configuration 148 | * \{ 149 | */ 150 | 151 | /** 152 | * \brief Maximum depth for nested objects and arrays 153 | */ 154 | #ifndef LWJSON_CFG_SERIALIZER_MAX_STACK_DEPTH 155 | #define LWJSON_CFG_SERIALIZER_MAX_STACK_DEPTH 8 156 | #endif 157 | 158 | /** 159 | * \} 160 | */ 161 | 162 | /** 163 | * \} 164 | */ 165 | 166 | #ifdef __cplusplus 167 | } 168 | #endif /* __cplusplus */ 169 | 170 | #endif /* LWJSON_OPT_HDR_H */ 171 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | from sphinx.builders.html import StandaloneHTMLBuilder 17 | import subprocess, os 18 | 19 | # Run doxygen first 20 | # read_the_docs_build = os.environ.get('READTHEDOCS', None) == 'True' 21 | # if read_the_docs_build: 22 | subprocess.call('doxygen doxyfile.doxy', shell=True) 23 | # -- Project information ----------------------------------------------------- 24 | 25 | project = 'LwJSON' 26 | copyright = '2025, Tilen MAJERLE' 27 | author = 'Tilen MAJERLE' 28 | 29 | # Try to get branch at which this is running 30 | # and try to determine which version to display in sphinx 31 | # Version is using git tag if on master/main or "latest-develop" if on develop branch 32 | version = '' 33 | git_branch = '' 34 | 35 | def cmd_exec_print(t): 36 | print("cmd > ", t, "\n", os.popen(t).read().strip(), "\n") 37 | 38 | # Print demo data here 39 | cmd_exec_print('git branch') 40 | cmd_exec_print('git describe') 41 | cmd_exec_print('git describe --tags') 42 | cmd_exec_print('git describe --tags --abbrev=0') 43 | cmd_exec_print('git describe --tags --abbrev=1') 44 | 45 | # Get current branch 46 | res = os.popen('git branch').read().strip() 47 | for line in res.split("\n"): 48 | if line[0] == '*': 49 | git_branch = line[1:].strip() 50 | 51 | # Decision for display version 52 | git_branch = git_branch.replace('(HEAD detached at ', '').replace(')', '') 53 | if git_branch.find('master') >= 0 or git_branch.find('main') >= 0: 54 | #version = os.popen('git describe --tags --abbrev=0').read().strip() 55 | version = 'latest-stable' 56 | elif git_branch.find('develop-') >= 0 or git_branch.find('develop/') >= 0: 57 | version = 'branch-' + git_branch 58 | elif git_branch == 'develop' or git_branch == 'origin/develop': 59 | version = 'latest-develop' 60 | else: 61 | version = os.popen('git describe --tags --abbrev=0').read().strip() 62 | 63 | # For debugging purpose only 64 | print("GIT BRANCH: " + git_branch) 65 | print("PROJ VERSION: " + version) 66 | 67 | # -- General configuration --------------------------------------------------- 68 | 69 | # Add any Sphinx extension module names here, as strings. They can be 70 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 71 | # ones. 72 | extensions = [ 73 | 'sphinx.ext.autodoc', 74 | 'sphinx.ext.intersphinx', 75 | 'sphinx.ext.autosectionlabel', 76 | 'sphinx.ext.todo', 77 | 'sphinx.ext.coverage', 78 | 'sphinx.ext.mathjax', 79 | 'sphinx.ext.ifconfig', 80 | 'sphinx.ext.viewcode', 81 | 'sphinx_sitemap', 82 | 83 | 'breathe', 84 | ] 85 | 86 | # Add any paths that contain templates here, relative to this directory. 87 | templates_path = ['templates'] 88 | 89 | # List of patterns, relative to source directory, that match files and 90 | # directories to ignore when looking for source files. 91 | # This pattern also affects html_static_path and html_extra_path. 92 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 93 | 94 | highlight_language = 'c' 95 | 96 | # -- Options for HTML output ------------------------------------------------- 97 | 98 | # The theme to use for HTML and HTML Help pages. See the documentation for 99 | # a list of builtin themes. 100 | # 101 | html_theme = 'sphinx_rtd_theme' 102 | html_theme_options = { 103 | 'canonical_url': '', 104 | 'analytics_id': '', # Provided by Google in your dashboard 105 | 'display_version': True, 106 | 'prev_next_buttons_location': 'bottom', 107 | 'style_external_links': False, 108 | 109 | 'logo_only': False, 110 | 111 | # Toc options 112 | 'collapse_navigation': True, 113 | 'sticky_navigation': True, 114 | 'navigation_depth': 4, 115 | 'includehidden': True, 116 | 'titles_only': False 117 | } 118 | html_logo = 'static/images/logo.svg' 119 | github_url = 'https://github.com/MaJerle/lwjson' 120 | html_baseurl = 'https://docs.majerle.eu/projects/lwjson/' 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['static'] 126 | html_css_files = [ 127 | 'css/common.css', 128 | 'css/custom.css', 129 | 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css', 130 | ] 131 | html_js_files = [ 132 | '' 133 | ] 134 | 135 | # Master index file 136 | master_doc = 'index' 137 | 138 | # --- Breathe configuration ----------------------------------------------------- 139 | breathe_projects = { 140 | "lwjson": "_build/xml/" 141 | } 142 | breathe_default_project = "lwjson" 143 | breathe_default_members = ('members', 'undoc-members') 144 | breathe_show_enumvalue_initializer = True -------------------------------------------------------------------------------- /docs/user-manual/data-access.rst: -------------------------------------------------------------------------------- 1 | .. _data_access: 2 | 3 | Access to data 4 | ============== 5 | 6 | Once application successfully parses input JSON string, 7 | LwJSON creates set of tokens in hierarchical order with tree for children tokens. 8 | 9 | To simplify data extraction and to quickly find/access-to given object and token, 10 | LwJSON implements simple *find* algorithm based on path formatting. 11 | 12 | Traverse object 13 | *************** 14 | 15 | Valid JSON input starts with *object* (``{``) or *array* (``[``). Anything else is invalid JSON object. 16 | When :cpp:func:`lwjson_parse` successfully processes input data, application may access JSON tokens 17 | with simple loop. Every token is part of linked list and has *tree* organization for children objects. 18 | 19 | .. note :: 20 | Children objects are only available for *object* and *array* types 21 | 22 | To traverse through all elements, application must first get top object. 23 | It can then loop through all in linked list until it reaches zero. 24 | 25 | If the token type is *object* or *array*, application must check children nodes for more token data. 26 | 27 | .. literalinclude:: ../../examples/example_traverse.c 28 | :language: c 29 | :linenos: 30 | :caption: Traverse through all tokens 31 | 32 | .. tip :: 33 | Check :cpp:func:`lwjson_print_json` to print data on stream output 34 | 35 | Find token in JSON tree 36 | *********************** 37 | 38 | Instead of manually traversing through all tokens, LwJSON implements simple search algorithm 39 | to quickly get token from application standpoint. 40 | 41 | Let's consider following JSON as input: 42 | 43 | .. code:: 44 | 45 | { 46 | "name":"John", 47 | "born": { 48 | "city": "Munich", 49 | "year": 1993 50 | }, 51 | "cars":[ 52 | { 53 | "brand":"Porsche", 54 | "year":2018 55 | }, 56 | { 57 | "brand":"Range", 58 | "year":2020, 59 | "repainted":true 60 | } 61 | ] 62 | } 63 | 64 | There is one *John*, born in *Munich* in ``1993`` and has ``2`` cars, *Porsche* and *Range*. 65 | 66 | * ``name`` is string with value ``John`` 67 | * ``born`` is object with ``2`` fields 68 | * ``cars`` is array of ``2`` objects 69 | 70 | * object 1 71 | 72 | * ``brand`` is set to *Porsche* 73 | * ``year`` is set to ``2018`` 74 | * object 2 75 | 76 | * ``brand`` is set to *Range* 77 | * ``year`` is set to ``2020`` 78 | * ``repainted`` is set to ``true`` as this car was recently repainted 79 | 80 | To find the person's name, application must first ``name`` key and print its value. 81 | This can be done by scanning entire object and check which token matches ``name`` keyword. 82 | 83 | LwJSON implements *find* functionality to find the token in simple way. 84 | This is done by providing full path to token in JSON tree, separated by *dot* ``.`` character. 85 | 86 | To find person name, application would then simply call :cpp:func:`lwjson_find` and pass ``name`` as path parameter. 87 | If token exists, function will return handle of the token where application can print its value. 88 | 89 | If application is interested in *city of birth*, it will set path as ``born.city`` and search 90 | algorithm will: 91 | 92 | * First search for ``born`` token and check if it is *object* 93 | * It will enter the object and search for ``city`` token and return it on match 94 | * It will return ``NULL`` of object is not found 95 | 96 | .. tip:: 97 | Application shall use :cpp:func:`lwjson_find` to get the token based on input path. 98 | 99 | When JSON contains arrays (these do not have keys), special character ``#`` may be used, 100 | indicating *any* element in array to be checked until first match is found. 101 | 102 | * ``cars.#.brand`` will return first token matching path, the one with string value ``Porsche`` in first object 103 | * ``cars.#.repainted`` will return first token matching path, the one with boolean value ``true`` in second object 104 | 105 | In first case, ``brand`` keyword exists already in first object, so find function will get the match immediately. 106 | Because in second case, ``repainted`` only exists in second object, function will return value from second object. 107 | 108 | Access array index 109 | ****************** 110 | 111 | It is possible to access specific array index by adding decimal number after hash character, in format ``#[0-9]+`` 112 | 113 | * ``cars.#0.brand`` will return ``brand`` token from *first* object in array (``index = 0``) with value set to *Porsche* 114 | * ``cars.#1.brand`` will return ``brand`` token from *second* object in array (``index = 1``) with value set to *Range* 115 | 116 | To retrieve full object of the array, application may only apply ``#[0.9]+`` in search pattern. 117 | 118 | * ``cars.#0`` will return first object token in array 119 | * ``cars.#1`` will return second object token in array 120 | * ``cars.#`` will return error as there is no valid index. Use ``cars`` to retrieve full array 121 | 122 | .. warning:: 123 | Passing path in format ``path.to.cars.#`` (hashtag as last element without index number) will always return ``NULL`` 124 | as this is considered invalid path. To retrieve full array, pass path to array ``path.to.cars`` only, without trailling ``#``. 125 | 126 | .. toctree:: 127 | :maxdepth: 2 128 | -------------------------------------------------------------------------------- /lwjson/src/include/lwjson/lwjson_serializer.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_serializer.h 3 | * \brief JSON serializer for building JSON strings incrementally 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2025 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_SERIALIZER_HDR_H 35 | #define LWJSON_SERIALIZER_HDR_H 36 | 37 | /* Include system headers */ 38 | #include 39 | #include 40 | 41 | /* Include lwjson library */ 42 | #include "lwjson.h" 43 | 44 | #ifdef __cplusplus 45 | extern "C" { 46 | #endif 47 | 48 | /** 49 | * \defgroup LWJSON_SERIALIZER JSON serializer 50 | * \brief JSON serializer 51 | * \{ 52 | */ 53 | 54 | /** 55 | * \brief Stack element types for tracking nesting 56 | */ 57 | typedef enum { 58 | LWJSON_SERIALIZER_TYPE_OBJECT = 0, /*!< Object type on stack */ 59 | LWJSON_SERIALIZER_TYPE_ARRAY, /*!< Array type on stack */ 60 | } lwjson_serializer_stack_type_t; 61 | 62 | /** 63 | * \brief Callback function type for serialization 64 | * \param[in] ctx: User context passed to callback 65 | * \param[in] str: String to serialize 66 | * \param[in] str_len: Length in bytes of string to serialize 67 | * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise 68 | */ 69 | typedef lwjsonr_t (*lwjson_serializer_callback_fn)(void* ctx, const char* str, size_t str_len); 70 | 71 | /** 72 | * \brief Context structure for default buffered output 73 | */ 74 | typedef struct { 75 | char* buffer; /*!< Pointer to the buffer for serialized output */ 76 | size_t capacity; /*!< Maximum capacity of the buffer */ 77 | size_t length; /*!< Current length of the serialized output */ 78 | } lwjson_serializer_default_ctx_t; 79 | 80 | /** 81 | * \brief JSON serializer structure 82 | */ 83 | typedef struct { 84 | lwjson_serializer_stack_type_t stack[LWJSON_CFG_SERIALIZER_MAX_STACK_DEPTH]; /*!< Stack for tracking nesting */ 85 | int32_t top; /*!< Current top of stack */ 86 | uint8_t need_comma; /*!< Flag indicating if comma is needed before next element */ 87 | lwjson_serializer_callback_fn callback; /*!< Callback function for serialization */ 88 | void* ctx; /*!< User context passed to callback */ 89 | lwjson_serializer_default_ctx_t default_ctx; /*!< Default context for buffered output */ 90 | } lwjson_serializer_t; 91 | 92 | lwjsonr_t lwjson_serializer_init(lwjson_serializer_t* serializer, char* user_buffer, size_t buffer_size); 93 | lwjsonr_t lwjson_serializer_finalize(lwjson_serializer_t* serializer, size_t* total_length); 94 | lwjsonr_t lwjson_serializer_init_callback(lwjson_serializer_t* serializer, lwjson_serializer_callback_fn callback, 95 | void* ctx); 96 | lwjsonr_t lwjson_serializer_start_object(lwjson_serializer_t* serializer, const char* key, size_t key_len); 97 | lwjsonr_t lwjson_serializer_start_array(lwjson_serializer_t* serializer, const char* key, size_t key_len); 98 | lwjsonr_t lwjson_serializer_end_object(lwjson_serializer_t* serializer); 99 | lwjsonr_t lwjson_serializer_end_array(lwjson_serializer_t* serializer); 100 | lwjsonr_t lwjson_serializer_add_string(lwjson_serializer_t* serializer, const char* key, size_t key_len, 101 | const char* value, size_t value_len); 102 | lwjsonr_t lwjson_serializer_add_uint(lwjson_serializer_t* serializer, const char* key, size_t key_len, uint64_t value); 103 | lwjsonr_t lwjson_serializer_add_int(lwjson_serializer_t* serializer, const char* key, size_t key_len, int64_t value); 104 | lwjsonr_t lwjson_serializer_add_float(lwjson_serializer_t* serializer, const char* key, size_t key_len, double value); 105 | lwjsonr_t lwjson_serializer_add_bool(lwjson_serializer_t* serializer, const char* key, size_t key_len, uint8_t value); 106 | lwjsonr_t lwjson_serializer_add_null(lwjson_serializer_t* serializer, const char* key, size_t key_len); 107 | 108 | /** 109 | * \} 110 | */ 111 | 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | 116 | #endif /* LWJSON_SERIALIZER_HDR_H */ 117 | -------------------------------------------------------------------------------- /tests/test_json_parse_basic/test_json_parse_basic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | #define RUN_TEST(exp_res, json_str) \ 5 | if (lwjson_parse(&lwjson, (json_str)) == (exp_res)) { \ 6 | ++test_passed; \ 7 | } else { \ 8 | ++test_failed; \ 9 | printf("Test failed for input %s on line %d\r\n", json_str, __LINE__); \ 10 | } 11 | #define RUN_TEST_EX(exp_res, json_str, len) \ 12 | if (lwjson_parse_ex(&lwjson, (json_str), (len)) == (exp_res)) { \ 13 | ++test_passed; \ 14 | } else { \ 15 | ++test_failed; \ 16 | printf("Test failed for input %s on line %d\r\n", json_str, __LINE__); \ 17 | } 18 | 19 | /** 20 | * \brief Path and type parse check 21 | */ 22 | typedef struct { 23 | const char* path; /*!< Path in parsed JSON */ 24 | lwjson_type_t type; /*!< expected data type in JSON */ 25 | } test_path_type_t; 26 | 27 | /* LwJSON instance and tokens */ 28 | static lwjson_token_t tokens[4096]; 29 | static lwjson_t lwjson; 30 | 31 | /** 32 | * \brief Run all tests entry point 33 | */ 34 | int 35 | test_run(void) { 36 | size_t test_failed = 0, test_passed = 0; 37 | 38 | printf("JSON parse test\r\n"); 39 | if (lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens)) != lwjsonOK) { 40 | printf("JSON init failed\r\n"); 41 | return -1; 42 | } 43 | 44 | /* Run JSON parse tests that must succeed */ 45 | RUN_TEST(lwjsonOK, "{}"); 46 | RUN_TEST(lwjsonOK, "{ }"); 47 | RUN_TEST(lwjsonOK, "{}\r\n"); 48 | RUN_TEST(lwjsonOK, "{ }\r\n"); 49 | RUN_TEST(lwjsonOK, "{\t}\r\n"); 50 | RUN_TEST(lwjsonOK, "{\t }\r\n"); 51 | RUN_TEST(lwjsonOK, "[1,2,3,4]"); 52 | RUN_TEST(lwjsonOK, "{\"k\":[]}"); 53 | RUN_TEST(lwjsonOK, "{\"k\":[1]}"); 54 | RUN_TEST(lwjsonOK, "{\"k\":[1,2]}"); 55 | RUN_TEST(lwjsonOK, "{\"k\":[1,]}"); 56 | RUN_TEST(lwjsonOK, "{\"k\":[1,[1,2]]}"); 57 | RUN_TEST(lwjsonOK, "{\"k\":false}"); 58 | RUN_TEST(lwjsonOK, "{\"k\":true}"); 59 | RUN_TEST(lwjsonOK, "{\"k\":null}"); 60 | RUN_TEST(lwjsonOK, "{\"k\" :null}"); 61 | RUN_TEST(lwjsonOK, "{\"k\" : null}"); 62 | RUN_TEST(lwjsonOK, "{ \"k\": null }"); 63 | RUN_TEST(lwjsonOK, "{ \"k\": null }"); 64 | RUN_TEST(lwjsonOK, "{\"k\":\"Stringgg\"}"); 65 | RUN_TEST(lwjsonOK, "{\"k\":\"Stri\\\"nggg with quote inside\"}"); 66 | RUN_TEST(lwjsonOK, "{\"k\":{\"b\":1E5,\t\r\n\"c\":1.3E5\r\n}\r\n}"); 67 | 68 | /* Arrays */ 69 | RUN_TEST(lwjsonOK, "[]"); 70 | RUN_TEST(lwjsonOK, "[ ]"); 71 | RUN_TEST(lwjsonOK, "[[],[]]"); 72 | RUN_TEST(lwjsonOK, "[[],[],{}]"); 73 | RUN_TEST(lwjsonERRJSON, "["); 74 | RUN_TEST(lwjsonERRJSON, "[\"abc\":\"test\"]"); 75 | RUN_TEST(lwjsonERRJSON, "]"); 76 | RUN_TEST(lwjsonERRJSON, "[[,[]]"); 77 | RUN_TEST(lwjsonERRJSON, "[,[]]"); 78 | RUN_TEST(lwjsonERRJSON, "[[],[,{}]"); 79 | RUN_TEST(lwjsonERRJSON, "{[0,1,2]}"); 80 | RUN_TEST(lwjsonERRJSON, "{1,2}"); 81 | 82 | /* Check specials */ 83 | RUN_TEST(lwjsonOK, "{\"k\":\"\\t\"}"); 84 | RUN_TEST(lwjsonOK, "{\"k\":\"\\b\"}"); 85 | RUN_TEST(lwjsonOK, "{\"k\":\"\\r\"}"); 86 | RUN_TEST(lwjsonOK, "{\"k\":\"\\n\"}"); 87 | RUN_TEST(lwjsonOK, "{\"k\":\"\\f\"}"); 88 | RUN_TEST(lwjsonOK, "{\"k\":\"\\\\\"}"); 89 | RUN_TEST(lwjsonOK, "{\"k\":\"\\u1234\"}"); 90 | RUN_TEST(lwjsonOK, "{\"k\":\"\\uabcd\"}"); 91 | RUN_TEST(lwjsonOK, "{\"k\":\"\\uAbCd\"}"); 92 | RUN_TEST(lwjsonOK, "{\"k\":\"\\u1abc\"}"); 93 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\u1aGc\"}"); 94 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\u\t\n\n\n\"}"); 95 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\u\"}"); 96 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\u1\"}"); 97 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\u12\"}"); 98 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\u123\"}"); 99 | RUN_TEST(lwjsonERRJSON, "{\"k\":\"\\a\"}"); 100 | 101 | /* Run JSON tests to fail */ 102 | RUN_TEST(lwjsonERRPAR, ""); 103 | RUN_TEST(lwjsonERRJSON, "{[]}"); /* Array without key inside object */ 104 | RUN_TEST(lwjsonERRJSON, "{\"k\":False}"); /* False value must be all lowercase */ 105 | RUN_TEST(lwjsonERRJSON, "{\"k\":True}"); /* True value must be all lowercase */ 106 | RUN_TEST(lwjsonERRJSON, "{\"k\":nUll}"); /* Null value must be all lowercase */ 107 | RUN_TEST(lwjsonERRJSON, "{\"k\"1}"); /* Missing separator */ 108 | RUN_TEST(lwjsonERRJSON, "{\"k\"1}"); /* Missing separator */ 109 | RUN_TEST(lwjsonERRJSON, "{k:1}"); /* Property name must be string */ 110 | RUN_TEST(lwjsonERRJSON, "{k:0.}"); /* Wrong number format */ 111 | 112 | /* Tests with custom len */ 113 | RUN_TEST_EX(lwjsonOK, "[1,2,3,4]abc", 9); /* Limit input len to JSON-only */ 114 | RUN_TEST_EX(lwjsonERR, "[1,2,3,4]abc", 10); /* Too long input for JSON string.. */ 115 | RUN_TEST_EX(lwjsonOK, "[1,2,3,4]", 116 | 15); /* String ends earlier than what is input data len indicating = OK if JSON is valid */ 117 | 118 | /* Print results */ 119 | printf("JSON parse test result pass/fail: %d/%d\r\n\r\n", (int)test_passed, (int)test_failed); 120 | return test_failed > 0 ? -1 : 0; 121 | } 122 | -------------------------------------------------------------------------------- /docs/get-started/index.rst: -------------------------------------------------------------------------------- 1 | .. _getting_started: 2 | 3 | Getting started 4 | =============== 5 | 6 | Getting started may be the most challenging part of every new library. 7 | This guide is describing how to start with the library quickly and effectively 8 | 9 | .. _download_library: 10 | 11 | Download library 12 | ^^^^^^^^^^^^^^^^ 13 | 14 | Library is primarly hosted on `Github `_. 15 | 16 | You can get it by: 17 | 18 | * Downloading latest release from `releases area `_ on Github 19 | * Cloning ``main`` branch for latest stable version 20 | * Cloning ``develop`` branch for latest development 21 | 22 | Download from releases 23 | ********************** 24 | 25 | All releases are available on Github `releases area `_. 26 | 27 | Clone from Github 28 | ***************** 29 | 30 | First-time clone 31 | """""""""""""""" 32 | 33 | This is used when you do not have yet local copy on your machine. 34 | 35 | * Make sure ``git`` is installed. 36 | * Open console and navigate to path in the system to clone repository to. Use command ``cd your_path`` 37 | * Clone repository with one of available options below 38 | 39 | * Run ``git clone --recurse-submodules https://github.com/MaJerle/lwjson`` command to clone entire repository, including submodules 40 | * Run ``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwjson`` to clone `development` branch, including submodules 41 | * Run ``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwjson`` to clone `latest stable` branch, including submodules 42 | 43 | * Navigate to ``examples`` directory and run favourite example 44 | 45 | Update cloned to latest version 46 | """"""""""""""""""""""""""""""" 47 | 48 | * Open console and navigate to path in the system where your repository is located. Use command ``cd your_path`` 49 | * Run ``git pull origin main`` command to get latest changes on ``main`` branch 50 | * Run ``git pull origin develop`` command to get latest changes on ``develop`` branch 51 | * Run ``git submodule update --init --remote`` to update submodules to latest version 52 | 53 | .. note:: 54 | This is preferred option to use when you want to evaluate library and run prepared examples. 55 | Repository consists of multiple submodules which can be automatically downloaded when cloning and pulling changes from root repository. 56 | 57 | Add library to project 58 | ^^^^^^^^^^^^^^^^^^^^^^ 59 | 60 | At this point it is assumed that you have successfully download library, either with ``git clone`` command or with manual download from the library releases page. 61 | Next step is to add the library to the project, by means of source files to compiler inputs and header files in search path. 62 | 63 | *CMake* is the main supported build system. Package comes with the ``CMakeLists.txt`` and ``library.cmake`` files, both located in the ``lwjson`` directory: 64 | 65 | * ``library.cmake``: It is a fully configured set of variables and with library definition. User can include this file to the project file with ``include(path/to/library.cmake)`` and then manually use the variables provided by the file, such as list of source files, include paths or necessary compiler definitions. It is up to the user to properly use the this file on its own. 66 | * ``CMakeLists.txt``: It is a wrapper-only file and includes ``library.cmake`` file. It is used for when user wants to include the library to the main project by simply calling *CMake* ``add_subdirectory`` command, followed by ``target_link_libraries`` to link external library to the final project. 67 | 68 | .. tip:: 69 | Open ``library.cmake`` and analyze the provided information. Among variables, you can also find list of all possible exposed libraries for the user. 70 | 71 | If you do not use the *CMake*, you can do the following: 72 | 73 | * Copy ``lwjson`` folder to your project, it contains library files 74 | * Add ``lwjson/src/include`` folder to `include path` of your toolchain. This is where `C/C++` compiler can find the files during compilation process. Usually using ``-I`` flag 75 | * Add source files from ``lwjson/src/`` folder to toolchain build. These files are built by `C/C++` compiler 76 | * Copy ``lwjson/src/include/lwjson/lwjson_opts_template.h`` to project folder and rename it to ``lwjson_opts.h`` 77 | * Build the project 78 | 79 | Configuration file 80 | ^^^^^^^^^^^^^^^^^^ 81 | 82 | Configuration file is used to overwrite default settings defined for the essential use case. 83 | Library comes with template config file, which can be modified according to the application needs. 84 | and it should be copied (or simply renamed in-place) and named ``lwjson_opts.h`` 85 | 86 | .. note:: 87 | Default configuration template file location: ``lwjson/src/include/lwjson/lwjson_opts_template.h``. 88 | File must be renamed to ``lwjson_opts.h`` first and then copied to the project directory where compiler 89 | include paths have access to it by using ``#include "lwjson_opts.h"``. 90 | 91 | .. tip:: 92 | If you are using *CMake* build system, define the variable ``LWJSON_OPTS_FILE`` before adding library's directory to the *CMake* project. 93 | Variable must contain the path to the user options file. If not provided and to avoid build error, one will be generated in the build directory. 94 | 95 | Configuration options list is available available in the :ref:`api_lwjson_opt` section. 96 | If any option is about to be modified, it should be done in configuration file 97 | 98 | .. literalinclude:: ../../lwjson/src/include/lwjson/lwjson_opts_template.h 99 | :language: c 100 | :linenos: 101 | :caption: Template configuration file 102 | 103 | .. note:: 104 | If you prefer to avoid using configuration file, application must define 105 | a global symbol ``LWJSON_IGNORE_USER_OPTS``, visible across entire application. 106 | This can be achieved with ``-D`` compiler option. 107 | 108 | Minimal example code 109 | ^^^^^^^^^^^^^^^^^^^^ 110 | 111 | To verify proper library setup, minimal example has been prepared. 112 | Run it in your main application file to verify its proper execution 113 | 114 | .. literalinclude:: ../../examples/example_minimal.c 115 | :language: c 116 | :linenos: 117 | :caption: Absolute minimum example -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Language part removed. With clang-format >=20.1, the C and Cpp are separately handled, 2 | # so either there is no language at all, or we need to create 2 formats for C and Cpp, separately 3 | 4 | --- 5 | # Language: Cpp 6 | # BasedOnStyle: LLVM 7 | AccessModifierOffset: -2 8 | AlignAfterOpenBracket: Align 9 | AlignArrayOfStructures: None 10 | AlignConsecutiveMacros: 11 | Enabled: true 12 | AcrossEmptyLines: true 13 | AcrossComments: true 14 | AlignConsecutiveAssignments: None 15 | AlignConsecutiveBitFields: 16 | Enabled: true 17 | AcrossEmptyLines: true 18 | AcrossComments: true 19 | AlignConsecutiveDeclarations: None 20 | AlignEscapedNewlines: Right 21 | AlignOperands: Align 22 | SortIncludes: true 23 | InsertBraces: true # Control statements must have curly brackets 24 | AlignTrailingComments: true 25 | AllowAllArgumentsOnNextLine: true 26 | AllowAllParametersOfDeclarationOnNextLine: true 27 | AllowShortEnumsOnASingleLine: true 28 | AllowShortBlocksOnASingleLine: Empty 29 | AllowShortCaseLabelsOnASingleLine: true 30 | AllowShortFunctionsOnASingleLine: All 31 | AllowShortLambdasOnASingleLine: All 32 | AllowShortIfStatementsOnASingleLine: Never 33 | AllowShortLoopsOnASingleLine: false 34 | AlwaysBreakAfterDefinitionReturnType: None 35 | AlwaysBreakAfterReturnType: AllDefinitions 36 | AlwaysBreakBeforeMultilineStrings: false 37 | AlwaysBreakTemplateDeclarations: Yes 38 | AttributeMacros: 39 | - __capability 40 | BinPackArguments: true 41 | BinPackParameters: true 42 | BraceWrapping: 43 | AfterCaseLabel: false 44 | AfterClass: false 45 | AfterControlStatement: Never 46 | AfterEnum: false 47 | AfterFunction: false 48 | AfterNamespace: false 49 | AfterObjCDeclaration: false 50 | AfterStruct: false 51 | AfterUnion: false 52 | AfterExternBlock: false 53 | BeforeCatch: false 54 | BeforeElse: false 55 | BeforeLambdaBody: false 56 | BeforeWhile: false 57 | IndentBraces: false 58 | SplitEmptyFunction: true 59 | SplitEmptyRecord: true 60 | SplitEmptyNamespace: true 61 | BreakBeforeBinaryOperators: NonAssignment 62 | BreakBeforeConceptDeclarations: true 63 | BreakBeforeBraces: Attach 64 | BreakBeforeInheritanceComma: false 65 | BreakInheritanceList: BeforeColon 66 | BreakBeforeTernaryOperators: true 67 | BreakConstructorInitializersBeforeComma: false 68 | BreakConstructorInitializers: BeforeColon 69 | BreakAfterJavaFieldAnnotations: false 70 | BreakStringLiterals: true 71 | ColumnLimit: 120 72 | CommentPragmas: "^ IWYU pragma:" 73 | QualifierAlignment: Leave 74 | CompactNamespaces: false 75 | ConstructorInitializerIndentWidth: 4 76 | ContinuationIndentWidth: 4 77 | Cpp11BracedListStyle: true 78 | DeriveLineEnding: true 79 | DerivePointerAlignment: false 80 | DisableFormat: false 81 | EmptyLineAfterAccessModifier: Never 82 | EmptyLineBeforeAccessModifier: LogicalBlock 83 | ExperimentalAutoDetectBinPacking: false 84 | PackConstructorInitializers: BinPack 85 | BasedOnStyle: "" 86 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 87 | AllowAllConstructorInitializersOnNextLine: true 88 | FixNamespaceComments: true 89 | ForEachMacros: 90 | - foreach 91 | - Q_FOREACH 92 | - BOOST_FOREACH 93 | IfMacros: 94 | - KJ_IF_MAYBE 95 | IncludeBlocks: Preserve 96 | IncludeCategories: 97 | - Regex: "^<(.*)>" 98 | Priority: 0 99 | - Regex: '^"(.*)"' 100 | Priority: 1 101 | - Regex: "(.*)" 102 | Priority: 2 103 | IncludeIsMainRegex: "(Test)?$" 104 | IncludeIsMainSourceRegex: "" 105 | IndentAccessModifiers: false 106 | IndentCaseLabels: true 107 | IndentCaseBlocks: false 108 | IndentGotoLabels: true 109 | IndentPPDirectives: None 110 | IndentExternBlock: AfterExternBlock 111 | IndentRequires: true 112 | IndentWidth: 4 113 | IndentWrappedFunctionNames: false 114 | InsertTrailingCommas: None 115 | JavaScriptQuotes: Leave 116 | JavaScriptWrapImports: true 117 | KeepEmptyLinesAtTheStartOfBlocks: true 118 | LambdaBodyIndentation: Signature 119 | MacroBlockBegin: "" 120 | MacroBlockEnd: "" 121 | MaxEmptyLinesToKeep: 1 122 | NamespaceIndentation: None 123 | ObjCBinPackProtocolList: Auto 124 | ObjCBlockIndentWidth: 2 125 | ObjCBreakBeforeNestedBlockParam: true 126 | ObjCSpaceAfterProperty: false 127 | ObjCSpaceBeforeProtocolList: true 128 | PenaltyBreakAssignment: 2 129 | PenaltyBreakBeforeFirstCallParameter: 19 130 | PenaltyBreakComment: 300 131 | PenaltyBreakFirstLessLess: 120 132 | PenaltyBreakOpenParenthesis: 0 133 | PenaltyBreakString: 1000 134 | PenaltyBreakTemplateDeclaration: 10 135 | PenaltyExcessCharacter: 1000000 136 | PenaltyReturnTypeOnItsOwnLine: 60 137 | PenaltyIndentedWhitespace: 0 138 | PointerAlignment: Left 139 | PPIndentWidth: -1 140 | ReferenceAlignment: Pointer 141 | ReflowComments: false 142 | RemoveBracesLLVM: false 143 | SeparateDefinitionBlocks: Always 144 | ShortNamespaceLines: 1 145 | SortJavaStaticImport: Before 146 | SortUsingDeclarations: true 147 | SpaceAfterCStyleCast: false 148 | SpaceAfterLogicalNot: false 149 | SpaceAfterTemplateKeyword: true 150 | SpaceBeforeAssignmentOperators: true 151 | SpaceBeforeCaseColon: false 152 | SpaceBeforeParens: ControlStatements 153 | SpaceBeforeParensOptions: 154 | AfterControlStatements: true 155 | AfterForeachMacros: true 156 | AfterFunctionDefinitionName: false 157 | AfterFunctionDeclarationName: false 158 | AfterIfMacros: true 159 | AfterOverloadedOperator: false 160 | BeforeNonEmptyParentheses: false 161 | SpaceAroundPointerQualifiers: Default 162 | SpaceBeforeRangeBasedForLoopColon: true 163 | SpaceInEmptyBlock: false 164 | SpaceInEmptyParentheses: false 165 | SpacesBeforeTrailingComments: 1 166 | SpacesInAngles: Never 167 | SpacesInConditionalStatement: false 168 | SpacesInContainerLiterals: true 169 | SpacesInCStyleCastParentheses: false 170 | SpacesInLineCommentPrefix: 171 | Minimum: 1 172 | Maximum: -1 173 | SpacesInParentheses: false 174 | SpacesInSquareBrackets: false 175 | SpaceBeforeSquareBrackets: false 176 | BitFieldColonSpacing: Both 177 | Standard: Latest 178 | StatementAttributeLikeMacros: 179 | - Q_EMIT 180 | StatementMacros: 181 | - Q_UNUSED 182 | - QT_REQUIRE_VERSION 183 | TabWidth: 8 184 | UseCRLF: false 185 | UseTab: Never 186 | WhitespaceSensitiveMacros: 187 | - STRINGIZE 188 | - PP_STRINGIZE 189 | - BOOST_PP_STRINGIZE 190 | - NS_SWIFT_NAME 191 | - CF_SWIFT_NAME 192 | SpaceBeforeCpp11BracedList: false 193 | SpaceBeforeCtorInitializerColon: true 194 | SpaceBeforeInheritanceColon: true 195 | --- 196 | 197 | -------------------------------------------------------------------------------- /tests/test_json_data_types/test_json_data_types.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | /** 5 | * \brief Path and type parse check 6 | */ 7 | typedef struct { 8 | const char* path; /*!< Path in parsed JSON */ 9 | lwjson_type_t type; /*!< expected data type in JSON */ 10 | } test_path_type_t; 11 | 12 | /* LwJSON instance and tokens */ 13 | static lwjson_token_t tokens[4096]; 14 | static lwjson_t lwjson; 15 | 16 | /** 17 | * \brief Run all tests entry point 18 | */ 19 | int 20 | test_run(void) { 21 | const lwjson_token_t* t; 22 | size_t test_failed = 0, test_passed = 0; 23 | 24 | /* Input JSON string */ 25 | const char* json_complete = "" 26 | "{" 27 | " \"int\": {" 28 | " \"num1\": 1234," 29 | " \"num2\": -1234," 30 | " \"num3\": 0" 31 | " }," 32 | #if LWJSON_CFG_COMMENTS 33 | " /* This is my comment... */" 34 | #endif /* LWJSON_CFG_COMMENTS */ 35 | " \"real\": {" 36 | " \"num1\":123.4," 37 | " \"num2\":-123.4," 38 | " \"num3\":123E3," 39 | " \"num4\":123e4," 40 | " \"num5\":-123E3," 41 | " \"num6\":-123e4," 42 | " \"num7\":123E-3," 43 | " \"num8\":123e-4," 44 | " \"num9\":-123E-3," 45 | " \"num10\":-123e-4," 46 | " \"num11\":123.12E3," 47 | " \"num12\":123.1e4," 48 | " \"num13\":-123.0E3," 49 | " \"num14\":-123.1e4," 50 | " \"num15\":123.1E-3," 51 | " \"num16\":123.1235e-4," 52 | " \"num17\":-123.324342E-3," 53 | " \"num18\":-123.3232e-4," 54 | " }," 55 | " \"obj\": {" 56 | " \"obj1\":{}," 57 | " \"obj2\":[]," 58 | " \"obj3\":{" 59 | " \"key1\":[]," 60 | " \"key2\":\"string\"," 61 | " }," 62 | " }," 63 | " \"bool\": {" 64 | " \"true\":true," 65 | " \"false\":false" 66 | " }," 67 | " \"null\":null," 68 | " \"array\":[]," 69 | "}"; 70 | 71 | /* JSON paths */ 72 | const test_path_type_t paths_types[] = { 73 | /* Integer types */ 74 | {"int", LWJSON_TYPE_OBJECT}, 75 | {"int.num1", LWJSON_TYPE_NUM_INT}, 76 | {"int.num2", LWJSON_TYPE_NUM_INT}, 77 | {"int.num3", LWJSON_TYPE_NUM_INT}, 78 | 79 | /* Real types */ 80 | {"real", LWJSON_TYPE_OBJECT}, 81 | {"real.num1", LWJSON_TYPE_NUM_REAL}, 82 | {"real.num2", LWJSON_TYPE_NUM_REAL}, 83 | {"real.num3", LWJSON_TYPE_NUM_REAL}, 84 | {"real.num4", LWJSON_TYPE_NUM_REAL}, 85 | {"real.num5", LWJSON_TYPE_NUM_REAL}, 86 | {"real.num6", LWJSON_TYPE_NUM_REAL}, 87 | {"real.num7", LWJSON_TYPE_NUM_REAL}, 88 | {"real.num8", LWJSON_TYPE_NUM_REAL}, 89 | {"real.num9", LWJSON_TYPE_NUM_REAL}, 90 | {"real.num10", LWJSON_TYPE_NUM_REAL}, 91 | {"real.num11", LWJSON_TYPE_NUM_REAL}, 92 | {"real.num12", LWJSON_TYPE_NUM_REAL}, 93 | {"real.num13", LWJSON_TYPE_NUM_REAL}, 94 | {"real.num14", LWJSON_TYPE_NUM_REAL}, 95 | {"real.num15", LWJSON_TYPE_NUM_REAL}, 96 | {"real.num16", LWJSON_TYPE_NUM_REAL}, 97 | {"real.num17", LWJSON_TYPE_NUM_REAL}, 98 | {"real.num18", LWJSON_TYPE_NUM_REAL}, 99 | 100 | /* Object */ 101 | {"obj", LWJSON_TYPE_OBJECT}, 102 | {"obj.obj1", LWJSON_TYPE_OBJECT}, 103 | {"obj.obj2", LWJSON_TYPE_ARRAY}, 104 | {"obj.obj3", LWJSON_TYPE_OBJECT}, 105 | {"obj.obj3.key1", LWJSON_TYPE_ARRAY}, 106 | {"obj.obj3.key2", LWJSON_TYPE_STRING}, 107 | 108 | /* Boolean */ 109 | {"bool", LWJSON_TYPE_OBJECT}, 110 | {"bool.true", LWJSON_TYPE_TRUE}, 111 | {"bool.false", LWJSON_TYPE_FALSE}, 112 | 113 | /* Null check */ 114 | {"null", LWJSON_TYPE_NULL}, 115 | 116 | /* Array check */ 117 | {"array", LWJSON_TYPE_ARRAY}, 118 | }; 119 | 120 | printf("---\r\nTest JSON data types..\r\n"); 121 | if (lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens)) != lwjsonOK) { 122 | printf("JSON init failed\r\n"); 123 | return -1; 124 | } 125 | 126 | /* First parse JSON */ 127 | if (lwjson_parse(&lwjson, json_complete) != lwjsonOK) { 128 | printf("Could not parse LwJSON data types..\r\n"); 129 | return -1; 130 | } 131 | 132 | /* Now that it is parsed, check all input keys */ 133 | for (size_t idx = 0; idx < LWJSON_ARRAYSIZE(paths_types); ++idx) { 134 | t = lwjson_find(&lwjson, paths_types[idx].path); 135 | if (t == NULL) { 136 | printf("IDX = %u: Could not find entry for path \"%s\"\r\n", (unsigned)idx, paths_types[idx].path); 137 | ++test_failed; 138 | continue; 139 | } 140 | if (t->type == paths_types[idx].type) { 141 | ++test_passed; 142 | } else { 143 | printf("IDX = %u: Type mismatch for path \"%s\"\r\n", (unsigned)idx, paths_types[idx].path); 144 | ++test_failed; 145 | } 146 | } 147 | 148 | /* Call this once JSON usage is finished */ 149 | lwjson_free(&lwjson); 150 | 151 | /* Print results */ 152 | printf("Data type test result pass/fail: %d/%d\r\n\r\n", (int)test_passed, (int)test_failed); 153 | return test_failed > 0 ? -1 : 0; 154 | } 155 | -------------------------------------------------------------------------------- /tests/test_json_find/test_json_find.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lwjson/lwjson.h" 3 | 4 | #define RUN_TEST(c) \ 5 | if ((c)) { \ 6 | ++test_passed; \ 7 | } else { \ 8 | printf("Test failed on line %d\r\n", __LINE__); \ 9 | ++test_failed; \ 10 | } 11 | 12 | /** 13 | * \brief Path and type parse check 14 | */ 15 | typedef struct { 16 | const char* path; /*!< Path in parsed JSON */ 17 | lwjson_type_t type; /*!< expected data type in JSON */ 18 | } test_path_type_t; 19 | 20 | /* LwJSON instance and tokens */ 21 | static lwjson_token_t tokens[4096]; 22 | static lwjson_t lwjson; 23 | 24 | /** 25 | * \brief Run all tests entry point 26 | */ 27 | int 28 | test_run(void) { 29 | size_t test_failed = 0, test_passed = 0; 30 | const lwjson_token_t* token; 31 | const char* json_str = "\ 32 | {\ 33 | \"my_arr\":[\ 34 | {\"num\":1,\"str\":\"first_entry\"},\ 35 | {\"num\":2,\"str\":\"second_entry\"},\ 36 | {\"num\":3,\"str\":\"third_entry\"},\ 37 | [\"abc\", \"def\"],\ 38 | [true, false, null],\ 39 | [123, -123, 987]\ 40 | ],\ 41 | \"my_obj\": {\ 42 | \"key_true\": true,\ 43 | \"key_true\": false,\ 44 | \"arr\": [\ 45 | [1, 2, 3],\ 46 | [true, false, null],\ 47 | [{\"my_key\":\"my_text\"}]\ 48 | ],\ 49 | \"ustr\":\"\\t\\u1234abc\"\ 50 | }\ 51 | }\ 52 | "; 53 | 54 | printf("---\r\nTest JSON find..\r\n"); 55 | if (lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens)) != lwjsonOK) { 56 | printf("JSON init failed\r\n"); 57 | return -1; 58 | } 59 | 60 | /* First parse JSON */ 61 | if (lwjson_parse(&lwjson, json_str) != lwjsonOK) { 62 | printf("Could not parse JSON string..\r\n"); 63 | return -1; 64 | } 65 | 66 | /* Run all tests */ 67 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr")) != NULL && token->type == LWJSON_TYPE_ARRAY); 68 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#")) == NULL); 69 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.")) == NULL); 70 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#0")) != NULL && token->type == LWJSON_TYPE_OBJECT); 71 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#1")) != NULL && token->type == LWJSON_TYPE_OBJECT); 72 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#2")) != NULL && token->type == LWJSON_TYPE_OBJECT); 73 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#0.str")) != NULL && token->type == LWJSON_TYPE_STRING 74 | && strncmp(token->u.str.token_value, "first_entry", 11) == 0); 75 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#1.str")) != NULL && token->type == LWJSON_TYPE_STRING 76 | && strncmp(token->u.str.token_value, "second_entry", 12) == 0); 77 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#2.str")) != NULL && token->type == LWJSON_TYPE_STRING 78 | && strncmp(token->u.str.token_value, "third_entry", 11) == 0); 79 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#3")) != NULL && token->type == LWJSON_TYPE_ARRAY); 80 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#3.#1")) != NULL && token->type == LWJSON_TYPE_STRING 81 | && strncmp(token->u.str.token_value, "def", 3) == 0); 82 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#3.#0")) != NULL && token->type == LWJSON_TYPE_STRING 83 | && strncmp(token->u.str.token_value, "abc", 3) == 0); 84 | RUN_TEST((token = lwjson_find(&lwjson, "my_arr.#3.#")) == NULL); 85 | 86 | /* Use EX version to search for tokens */ 87 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj")) != NULL /* First search for my_obj in root JSON */ 88 | && (token = lwjson_find_ex(&lwjson, token, "key_true")) 89 | != NULL /* Use token for relative search and search for key_true only */ 90 | && token->type == LWJSON_TYPE_TRUE); 91 | 92 | /* Deep search */ 93 | 94 | /* Search for first match in any array */ 95 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.arr.#.#.my_key")) != NULL 96 | && token->type == LWJSON_TYPE_STRING && strncmp(token->u.str.token_value, "my_text", 7) == 0); 97 | 98 | /* Search for match in specific array keys */ 99 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.arr.#2.#0.my_key")) != NULL 100 | && token->type == LWJSON_TYPE_STRING && strncmp(token->u.str.token_value, "my_text", 7) == 0); 101 | 102 | /* Search for match in specific array keys = must return NULL, no index = 1 in second array */ 103 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.arr.#2.#1.my_key")) == NULL); 104 | 105 | /* Use partial searches */ 106 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj")) != NULL 107 | && (token = lwjson_find_ex(&lwjson, token, "arr")) != NULL 108 | && (token = lwjson_find_ex(&lwjson, token, "#2")) != NULL 109 | && (token = lwjson_find_ex(&lwjson, token, "#0")) != NULL 110 | && (token = lwjson_find_ex(&lwjson, token, "my_key")) != NULL && token->type == LWJSON_TYPE_STRING 111 | && strncmp(token->u.str.token_value, "my_text", 7) == 0); 112 | 113 | /* Search for match in specific array keys and check for string length field */ 114 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.ustr")) != NULL && token->type == LWJSON_TYPE_STRING 115 | && lwjson_get_val_string_length(token) == 11 116 | && strncmp(token->u.str.token_value, "\\t\\u1234abc", 11) == 0); 117 | 118 | /* Check string compare */ 119 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.arr.#.#.my_key")) != NULL 120 | && lwjson_string_compare(token, "my_text")); 121 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.arr.#.#.my_key")) != NULL 122 | && lwjson_string_compare_n(token, "my_text", 3)); 123 | RUN_TEST((token = lwjson_find_ex(&lwjson, NULL, "my_obj.arr.#.#.my_key")) != NULL 124 | && !lwjson_string_compare_n(token, "my_stext", 4)); /* Must be a fail */ 125 | 126 | /* Call this once JSON usage is finished */ 127 | lwjson_free(&lwjson); 128 | 129 | /* Print results */ 130 | printf("Find function test result pass/fail: %d/%d\r\n\r\n", (int)test_passed, (int)test_failed); 131 | return test_failed > 0 ? -1 : 0; 132 | } 133 | -------------------------------------------------------------------------------- /lwjson/src/lwjson/lwjson_utils.c: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson_utils.c 3 | * \brief JSON string utility functions for character escaping and unescaping 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2025 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | /* Include lwjson headers */ 35 | #include "lwjson/lwjson_utils.h" 36 | 37 | /** 38 | * \brief Mapping escape characters to their JSON escape sequences 39 | * [0] = character that needs escaping 40 | * [1] = second char of escape sequence 41 | */ 42 | static const char escape_map[][2] = { 43 | {'"', '"'}, /* " -> \" */ 44 | {'/', '/'}, /* / -> \/ */ 45 | {'\\', '\\'}, /* \ -> \\ */ 46 | {'\b', 'b'}, /* backspace -> \b */ 47 | {'\f', 'f'}, /* form feed -> \f */ 48 | {'\n', 'n'}, /* newline -> \n */ 49 | {'\r', 'r'}, /* carriage return -> \r */ 50 | {'\t', 't'}, /* tab -> \t */ 51 | }; 52 | 53 | /** 54 | * \brief Number of escape character mappings 55 | */ 56 | #define ESCAPE_CHARS_COUNT (sizeof(escape_map) / sizeof(escape_map[0])) 57 | 58 | /** 59 | * \brief Check if character needs to be escaped 60 | * \param[in,out] ch: Pointer to character to check. If needs escaping, will be replaced with escape char 61 | * \return `1` if character needs escaping, `0` otherwise 62 | */ 63 | static uint8_t 64 | prv_char_needs_escape(char* ch) { 65 | for (size_t i = 0; i < ESCAPE_CHARS_COUNT; ++i) { 66 | if (*ch == escape_map[i][0]) { 67 | *ch = escape_map[i][1]; 68 | return 1; 69 | } 70 | } 71 | return 0; 72 | } 73 | 74 | /** 75 | * \brief Escape string for JSON format 76 | * \param[in] input: Input string to escape. Must not be `NULL`. 77 | * \param[in] input_len: Length of input string in bytes. 78 | * \param[out] output: Buffer for escaped output string. Must not be `NULL`. 79 | * \param[in] output_capacity: Maximum capacity of output buffer in bytes. 80 | * \param[out] bytes_written: Number of bytes written to output buffer. Must not be `NULL`. 81 | * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise 82 | */ 83 | lwjsonr_t 84 | lwjson_utils_escape_string(const char* input, size_t input_len, char* output, size_t output_capacity, size_t* bytes_written) { 85 | /* Parameter validation */ 86 | if (input == NULL || output == NULL || bytes_written == NULL) { 87 | return lwjsonERRNULL; 88 | } 89 | 90 | *bytes_written = 0; 91 | 92 | for (size_t i = 0; i < input_len; ++i) { 93 | char escape_char = input[i]; 94 | if (prv_char_needs_escape(&escape_char)) { 95 | if (*bytes_written >= output_capacity) { 96 | return lwjsonERRBUF; 97 | } 98 | output[*bytes_written] = '\\'; 99 | ++(*bytes_written); 100 | } 101 | if (*bytes_written >= output_capacity) { 102 | return lwjsonERRBUF; 103 | } 104 | output[*bytes_written] = escape_char; 105 | ++(*bytes_written); 106 | } 107 | return lwjsonOK; 108 | } 109 | 110 | /** 111 | * \brief Escape string for JSON format using a streaming callback 112 | * \param[in] input: Input string to escape. Must not be `NULL`. 113 | * \param[in] input_len: Length of input string in bytes. 114 | * \param[in] callback: Callback function for streaming output. Must not be `NULL`. 115 | * \param[in] ctx: User context passed to callback. Can be `NULL`. 116 | * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise 117 | */ 118 | lwjsonr_t 119 | lwjson_utils_escape_string_cb(const char* input, size_t input_len, lwjson_serializer_callback_fn callback, void* ctx) { 120 | lwjsonr_t res = lwjsonOK; 121 | 122 | /* Parameter validation */ 123 | if (input == NULL || callback == NULL) { 124 | return lwjsonERRNULL; 125 | } 126 | 127 | for (size_t i = 0; i < input_len; ++i) { 128 | char escape_char = input[i]; 129 | if (prv_char_needs_escape(&escape_char)) { 130 | res = callback(ctx, "\\", 1); 131 | if (res != lwjsonOK) { 132 | return res; 133 | } 134 | } 135 | res = callback(ctx, &escape_char, 1); 136 | if (res != lwjsonOK) { 137 | return res; 138 | } 139 | } 140 | return lwjsonOK; 141 | } 142 | 143 | /** 144 | * \brief Unescape string from JSON format 145 | * \param[in] input: Input string to unescape. Must not be `NULL`. 146 | * \param[in] input_len: Length of input string in bytes. 147 | * \param[out] output: Buffer for unescaped output string. Must not be `NULL`. 148 | * \param[in] output_capacity: Maximum capacity of output buffer in bytes. 149 | * \param[out] bytes_written: Number of bytes written to output buffer. Must not be `NULL`. 150 | * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise 151 | */ 152 | lwjsonr_t 153 | lwjson_utils_unescape_string(const char* input, size_t input_len, char* output, size_t output_capacity, size_t* bytes_written) { 154 | uint8_t escaped = 0; 155 | 156 | /* Parameter validation */ 157 | if (input == NULL || output == NULL || bytes_written == NULL) { 158 | return lwjsonERRNULL; 159 | } 160 | 161 | /* Initialize return value */ 162 | *bytes_written = 0; 163 | 164 | /* Main loop */ 165 | for (size_t i = 0; i < input_len; ++i) { 166 | if (*bytes_written >= output_capacity) { 167 | return lwjsonERRBUF; 168 | } 169 | if (escaped) { 170 | for (size_t j = 0; j < ESCAPE_CHARS_COUNT; ++j) { 171 | if (input[i] == escape_map[j][1]) { 172 | output[*bytes_written] = escape_map[j][0]; 173 | (*bytes_written)++; 174 | escaped = 0; 175 | break; 176 | } 177 | } 178 | if (escaped) { /*Check if character is in escape map */ 179 | return lwjsonERRJSON; 180 | } 181 | } else { 182 | if (input[i] == '\\') { 183 | escaped = 1; 184 | } else { 185 | output[*bytes_written] = input[i]; 186 | (*bytes_written)++; 187 | } 188 | } 189 | } 190 | return lwjsonOK; 191 | } 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Build Keil files 2 | *.rar 3 | *.o 4 | *.d 5 | *.crf 6 | *.htm 7 | *.dep 8 | *.map 9 | *.bak 10 | *.axf 11 | *.lnp 12 | *.lst 13 | *.ini 14 | *.scvd 15 | *.iex 16 | *.sct 17 | *.MajerleT 18 | *.tjuln 19 | *.tilen 20 | *.dbgconf 21 | *.uvguix 22 | *.uvoptx 23 | *.__i 24 | *.i 25 | *.txt 26 | !docs/*.txt 27 | !CMakeLists.txt 28 | RTE/ 29 | 30 | *debug 31 | 32 | # IAR Settings 33 | **/settings/*.crun 34 | **/settings/*.dbgdt 35 | **/settings/*.cspy 36 | **/settings/*.cspy.* 37 | **/settings/*.xcl 38 | **/settings/*.dni 39 | **/settings/*.wsdt 40 | **/settings/*.wspos 41 | 42 | # IAR Debug Exe 43 | **/Exe/*.sim 44 | 45 | # IAR Debug Obj 46 | **/Obj/*.pbd 47 | **/Obj/*.pbd.* 48 | **/Obj/*.pbi 49 | **/Obj/*.pbi.* 50 | 51 | *.TMP 52 | /docs_src/x_Doxyfile.doxy 53 | 54 | .DS_Store 55 | 56 | ## Ignore Visual Studio temporary files, build results, and 57 | ## files generated by popular Visual Studio add-ons. 58 | ## 59 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 60 | 61 | # User-specific files 62 | *.suo 63 | *.user 64 | *.userosscache 65 | *.sln.docstates 66 | 67 | # User-specific files (MonoDevelop/Xamarin Studio) 68 | *.userprefs 69 | 70 | # Build results 71 | [Dd]ebug/ 72 | [Dd]ebugPublic/ 73 | [Rr]elease/ 74 | [Rr]eleases/ 75 | [Dd]ebug*/ 76 | x64/ 77 | x86/ 78 | bld/ 79 | [Bb]in/ 80 | [Oo]bj/ 81 | [Ll]og/ 82 | _build/ 83 | build/ 84 | __build__/ 85 | 86 | # Visual Studio 2015/2017 cache/options directory 87 | .vs/ 88 | # Uncomment if you have tasks that create the project's static files in wwwroot 89 | #wwwroot/ 90 | 91 | # Visual Studio 2017 auto generated files 92 | Generated\ Files/ 93 | 94 | # MSTest test Results 95 | [Tt]est[Rr]esult*/ 96 | [Bb]uild[Ll]og.* 97 | 98 | # NUNIT 99 | *.VisualState.xml 100 | TestResult.xml 101 | 102 | # Build Results of an ATL Project 103 | [Dd]ebugPS/ 104 | [Rr]eleasePS/ 105 | dlldata.c 106 | 107 | # Benchmark Results 108 | BenchmarkDotNet.Artifacts/ 109 | 110 | # .NET Core 111 | project.lock.json 112 | project.fragment.lock.json 113 | artifacts/ 114 | **/Properties/launchSettings.json 115 | 116 | # StyleCop 117 | StyleCopReport.xml 118 | 119 | # Files built by Visual Studio 120 | *_i.c 121 | *_p.c 122 | *_i.h 123 | *.ilk 124 | *.meta 125 | *.obj 126 | *.pch 127 | *.pdb 128 | *.pgc 129 | *.pgd 130 | *.rsp 131 | *.sbr 132 | *.tlb 133 | *.tli 134 | *.tlh 135 | *.tmp 136 | *.tmp_proj 137 | *.log 138 | *.vspscc 139 | *.vssscc 140 | .builds 141 | *.pidb 142 | *.svclog 143 | *.scc 144 | *.out 145 | *.sim 146 | 147 | # Chutzpah Test files 148 | _Chutzpah* 149 | 150 | # Visual C++ cache files 151 | ipch/ 152 | *.aps 153 | *.ncb 154 | *.opendb 155 | *.opensdf 156 | *.sdf 157 | *.cachefile 158 | *.VC.db 159 | *.VC.VC.opendb 160 | 161 | # Visual Studio profiler 162 | *.psess 163 | *.vsp 164 | *.vspx 165 | *.sap 166 | 167 | # Visual Studio Trace Files 168 | *.e2e 169 | 170 | # TFS 2012 Local Workspace 171 | $tf/ 172 | 173 | # Guidance Automation Toolkit 174 | *.gpState 175 | 176 | # ReSharper is a .NET coding add-in 177 | _ReSharper*/ 178 | *.[Rr]e[Ss]harper 179 | *.DotSettings.user 180 | 181 | # JustCode is a .NET coding add-in 182 | .JustCode 183 | 184 | # TeamCity is a build add-in 185 | _TeamCity* 186 | 187 | # DotCover is a Code Coverage Tool 188 | *.dotCover 189 | 190 | # AxoCover is a Code Coverage Tool 191 | .axoCover/* 192 | !.axoCover/settings.json 193 | 194 | # Visual Studio code coverage results 195 | *.coverage 196 | *.coveragexml 197 | 198 | # NCrunch 199 | _NCrunch_* 200 | .*crunch*.local.xml 201 | nCrunchTemp_* 202 | 203 | # MightyMoose 204 | *.mm.* 205 | AutoTest.Net/ 206 | 207 | # Web workbench (sass) 208 | .sass-cache/ 209 | 210 | # Installshield output folder 211 | [Ee]xpress/ 212 | 213 | # DocProject is a documentation generator add-in 214 | DocProject/buildhelp/ 215 | DocProject/Help/*.HxT 216 | DocProject/Help/*.HxC 217 | DocProject/Help/*.hhc 218 | DocProject/Help/*.hhk 219 | DocProject/Help/*.hhp 220 | DocProject/Help/Html2 221 | DocProject/Help/html 222 | 223 | # Click-Once directory 224 | publish/ 225 | 226 | # Publish Web Output 227 | *.[Pp]ublish.xml 228 | *.azurePubxml 229 | # Note: Comment the next line if you want to checkin your web deploy settings, 230 | # but database connection strings (with potential passwords) will be unencrypted 231 | *.pubxml 232 | *.publishproj 233 | 234 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 235 | # checkin your Azure Web App publish settings, but sensitive information contained 236 | # in these scripts will be unencrypted 237 | PublishScripts/ 238 | 239 | # NuGet Packages 240 | *.nupkg 241 | # The packages folder can be ignored because of Package Restore 242 | **/[Pp]ackages/* 243 | # except build/, which is used as an MSBuild target. 244 | !**/[Pp]ackages/build/ 245 | # Uncomment if necessary however generally it will be regenerated when needed 246 | #!**/[Pp]ackages/repositories.config 247 | # NuGet v3's project.json files produces more ignorable files 248 | *.nuget.props 249 | *.nuget.targets 250 | 251 | # Microsoft Azure Build Output 252 | csx/ 253 | *.build.csdef 254 | 255 | # Microsoft Azure Emulator 256 | ecf/ 257 | rcf/ 258 | 259 | # Windows Store app package directories and files 260 | AppPackages/ 261 | BundleArtifacts/ 262 | Package.StoreAssociation.xml 263 | _pkginfo.txt 264 | *.appx 265 | 266 | # Visual Studio cache files 267 | # files ending in .cache can be ignored 268 | *.[Cc]ache 269 | # but keep track of directories ending in .cache 270 | !*.[Cc]ache/ 271 | 272 | # Others 273 | ClientBin/ 274 | ~$* 275 | *~ 276 | *.dbmdl 277 | *.dbproj.schemaview 278 | *.jfm 279 | *.pfx 280 | *.publishsettings 281 | orleans.codegen.cs 282 | 283 | # Including strong name files can present a security risk 284 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 285 | #*.snk 286 | 287 | # Since there are multiple workflows, uncomment next line to ignore bower_components 288 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 289 | #bower_components/ 290 | 291 | # RIA/Silverlight projects 292 | Generated_Code/ 293 | 294 | # Backup & report files from converting an old project file 295 | # to a newer Visual Studio version. Backup files are not needed, 296 | # because we have git ;-) 297 | _UpgradeReport_Files/ 298 | Backup*/ 299 | UpgradeLog*.XML 300 | UpgradeLog*.htm 301 | 302 | # SQL Server files 303 | *.mdf 304 | *.ldf 305 | *.ndf 306 | 307 | # Business Intelligence projects 308 | *.rdl.data 309 | *.bim.layout 310 | *.bim_*.settings 311 | 312 | # Microsoft Fakes 313 | FakesAssemblies/ 314 | 315 | # GhostDoc plugin setting file 316 | *.GhostDoc.xml 317 | 318 | # Node.js Tools for Visual Studio 319 | .ntvs_analysis.dat 320 | node_modules/ 321 | 322 | # TypeScript v1 declaration files 323 | typings/ 324 | 325 | # Visual Studio 6 build log 326 | *.plg 327 | 328 | # Visual Studio 6 workspace options file 329 | *.opt 330 | 331 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 332 | *.vbw 333 | 334 | # Visual Studio LightSwitch build output 335 | **/*.HTMLClient/GeneratedArtifacts 336 | **/*.DesktopClient/GeneratedArtifacts 337 | **/*.DesktopClient/ModelManifest.xml 338 | **/*.Server/GeneratedArtifacts 339 | **/*.Server/ModelManifest.xml 340 | _Pvt_Extensions 341 | 342 | # Paket dependency manager 343 | .paket/paket.exe 344 | paket-files/ 345 | 346 | # FAKE - F# Make 347 | .fake/ 348 | 349 | # JetBrains Rider 350 | .idea/ 351 | *.sln.iml 352 | 353 | # CodeRush 354 | .cr/ 355 | 356 | # Python Tools for Visual Studio (PTVS) 357 | __pycache__/ 358 | *.pyc 359 | 360 | # Cake - Uncomment if you are using it 361 | # tools/** 362 | # !tools/packages.config 363 | 364 | # Tabs Studio 365 | *.tss 366 | 367 | # Telerik's JustMock configuration file 368 | *.jmconfig 369 | 370 | # BizTalk build output 371 | *.btp.cs 372 | *.btm.cs 373 | *.odx.cs 374 | *.xsd.cs 375 | 376 | # OpenCover UI analysis results 377 | OpenCover/ 378 | 379 | # Azure Stream Analytics local run output 380 | ASALocalRun/ 381 | 382 | # MSBuild Binary and Structured Log 383 | *.binlog 384 | 385 | log_file.txt 386 | .metadata/ 387 | .mxproject 388 | .settings/ 389 | project.ioc 390 | mx.scratch 391 | *.tilen majerle 392 | 393 | 394 | # Altium 395 | Project outputs* 396 | History/ 397 | *.SchDocPreview 398 | *.$$$Preview 399 | 400 | # VSCode projects 401 | project_vscode_compiled.exe -------------------------------------------------------------------------------- /tests/test_stream/test_stream.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "lwjson/lwjson.h" 9 | #include "test.h" 10 | 11 | #define RUN_TEST(exp_res_must_be_true) \ 12 | if ((exp_res_must_be_true)) { \ 13 | ++test_passed; \ 14 | } else { \ 15 | ++test_failed; \ 16 | printf("Test failed on line %d\r\n", __LINE__); \ 17 | } 18 | 19 | /* Not implemented yet */ 20 | 21 | /** 22 | * \brief Setup the data type to hold values 23 | */ 24 | typedef struct { 25 | int numbers_array[3]; 26 | int numbers_obj_num1; 27 | int numbers_obj_num2; 28 | int numbers_array_array[2][3]; 29 | 30 | float numbers_real; 31 | 32 | char long_string[512]; 33 | 34 | size_t event_counter; 35 | bool event_error; 36 | } parsed_data_t; 37 | 38 | typedef struct { 39 | lwjson_stream_type_t type; 40 | size_t stack_pos; 41 | } event_data_t; 42 | 43 | #define LONG_STRING_TEST \ 44 | "this is a very long string because why we wouldn't do it if we can and because this has to be tested tested" 45 | 46 | /* JSON string to parse */ 47 | static const char* json_stream_to_parse = "\ 48 | {\ 49 | \"numbers_array\": [123, -123, 987],\ 50 | \"numbers_obj\": {\ 51 | \"num1\": 123, \ 52 | \"num2\": 456, \ 53 | },\ 54 | \"numbers_arr2\": [\ 55 | [1, 2, 3],\ 56 | [4, 5, 6]\ 57 | ],\ 58 | \"numbers_real\": 3.5,\ 59 | \"long_string\":\"" LONG_STRING_TEST "\"\ 60 | }\ 61 | "; 62 | 63 | // clang-format off 64 | static const event_data_t expected_events[] = { 65 | {LWJSON_STREAM_TYPE_OBJECT, 0}, 66 | {LWJSON_STREAM_TYPE_KEY, 1}, 67 | {LWJSON_STREAM_TYPE_ARRAY, 2}, 68 | {LWJSON_STREAM_TYPE_NUMBER, 3}, 69 | {LWJSON_STREAM_TYPE_NUMBER, 3}, 70 | {LWJSON_STREAM_TYPE_NUMBER, 3}, 71 | {LWJSON_STREAM_TYPE_ARRAY_END, 2}, 72 | {LWJSON_STREAM_TYPE_KEY, 1}, 73 | {LWJSON_STREAM_TYPE_OBJECT, 2}, 74 | {LWJSON_STREAM_TYPE_KEY, 3}, 75 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 76 | {LWJSON_STREAM_TYPE_KEY, 3}, 77 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 78 | {LWJSON_STREAM_TYPE_OBJECT_END, 2}, 79 | {LWJSON_STREAM_TYPE_KEY, 1}, 80 | {LWJSON_STREAM_TYPE_ARRAY, 2}, 81 | {LWJSON_STREAM_TYPE_ARRAY, 3}, 82 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 83 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 84 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 85 | {LWJSON_STREAM_TYPE_ARRAY_END, 3}, 86 | {LWJSON_STREAM_TYPE_ARRAY, 3}, 87 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 88 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 89 | {LWJSON_STREAM_TYPE_NUMBER, 4}, 90 | {LWJSON_STREAM_TYPE_ARRAY_END, 3}, 91 | {LWJSON_STREAM_TYPE_ARRAY_END, 2}, 92 | {LWJSON_STREAM_TYPE_KEY, 1}, 93 | {LWJSON_STREAM_TYPE_NUMBER, 2}, 94 | {LWJSON_STREAM_TYPE_KEY, 1}, 95 | // 8 calls to transfer 107 characters in chunks of 15 96 | {LWJSON_STREAM_TYPE_STRING, 2}, 97 | {LWJSON_STREAM_TYPE_STRING, 2}, 98 | {LWJSON_STREAM_TYPE_STRING, 2}, 99 | {LWJSON_STREAM_TYPE_STRING, 2}, 100 | {LWJSON_STREAM_TYPE_STRING, 2}, 101 | {LWJSON_STREAM_TYPE_STRING, 2}, 102 | {LWJSON_STREAM_TYPE_STRING, 2}, 103 | {LWJSON_STREAM_TYPE_STRING, 2}, 104 | {LWJSON_STREAM_TYPE_OBJECT_END, 0}, 105 | }; 106 | // clang-format on 107 | 108 | static const size_t num_expected_events = sizeof(expected_events) / sizeof(expected_events[0]); 109 | 110 | static lwjson_stream_parser_t parser; 111 | static parsed_data_t parsed_data; 112 | 113 | /** 114 | * \brief Parser callback to process user data 115 | * 116 | * \param jsp 117 | * \param type 118 | */ 119 | static void 120 | prv_parser_callback(struct lwjson_stream_parser* jsp, lwjson_stream_type_t type) { 121 | /* Check if event data is as expected, but complain only for the first error */ 122 | if (!parsed_data.event_error) { 123 | if (parsed_data.event_counter < num_expected_events) { 124 | event_data_t expected_event = expected_events[parsed_data.event_counter]; 125 | if (type != expected_event.type || jsp->stack_pos != expected_event.stack_pos) { 126 | printf("ERROR for event #%u: Expected %s with stack_pos %u, got %s with stack_pos %u\n", 127 | (unsigned)(parsed_data.event_counter + 1), lwjson_type_strings[expected_event.type], 128 | (unsigned)expected_event.stack_pos, lwjson_type_strings[type], (unsigned)jsp->stack_pos); 129 | parsed_data.event_error = true; 130 | } 131 | } else { 132 | printf("ERROR: Received more events than expected\n"); 133 | parsed_data.event_error = true; 134 | } 135 | } 136 | parsed_data.event_counter++; 137 | 138 | /* To process array values */ 139 | if (jsp->stack_pos == 3 && lwjson_stack_seq_3(jsp, 0, OBJECT, KEY, ARRAY)) { 140 | if (type == LWJSON_STREAM_TYPE_NUMBER) { 141 | if (strcmp(jsp->stack[1].meta.name, "numbers_array") == 0) { 142 | parsed_data.numbers_array[jsp->stack[2].meta.index] = strtol(jsp->data.prim.buff, NULL, 0); 143 | } 144 | } 145 | } 146 | 147 | /* Process keys */ 148 | if (jsp->stack_pos == 4 && lwjson_stack_seq_4(jsp, 0, OBJECT, KEY, OBJECT, KEY) 149 | && type == LWJSON_STREAM_TYPE_NUMBER) { 150 | if (strcmp(jsp->stack[1].meta.name, "numbers_obj") == 0) { 151 | if (strcmp(jsp->stack[3].meta.name, "num1") == 0) { 152 | parsed_data.numbers_obj_num1 = strtol(jsp->data.prim.buff, NULL, 0); 153 | } else if (strcmp(jsp->stack[3].meta.name, "num2") == 0) { 154 | parsed_data.numbers_obj_num2 = strtol(jsp->data.prim.buff, NULL, 0); 155 | } 156 | } 157 | } 158 | 159 | /* Process the object that has 2 arrays with numbers*/ 160 | if (jsp->stack_pos == 4 && lwjson_stack_seq_4(jsp, 0, OBJECT, KEY, ARRAY, ARRAY) 161 | && type == LWJSON_STREAM_TYPE_NUMBER) { 162 | if (strcmp(jsp->stack[1].meta.name, "numbers_arr2") == 0) { 163 | /* Store the value to the group array */ 164 | parsed_data.numbers_array_array[jsp->stack[2].meta.index][jsp->stack[3].meta.index] = 165 | strtol(jsp->data.prim.buff, NULL, 0); 166 | } 167 | } 168 | 169 | if (jsp->stack_pos == 2 && lwjson_stack_seq_2(jsp, 0, OBJECT, KEY)) { 170 | /* Parse number */ 171 | if (type == LWJSON_STREAM_TYPE_NUMBER) { 172 | if (strcmp(jsp->stack[1].meta.name, "numbers_real") == 0) { 173 | parsed_data.numbers_real = (float)strtod(jsp->data.prim.buff, NULL); 174 | } 175 | #if 1 176 | } else if (type == LWJSON_STREAM_TYPE_STRING) { 177 | if (strcmp(jsp->stack[1].meta.name, "long_string") == 0) { 178 | strncat(parsed_data.long_string, jsp->data.str.buff, jsp->data.str.buff_pos); 179 | } 180 | #endif 181 | } 182 | } 183 | } 184 | 185 | int 186 | test_run(void) { 187 | size_t test_failed = 0, test_passed = 0; 188 | 189 | printf("---\r\nTest JSON stream\r\n"); 190 | 191 | memset(&parsed_data, 0x00, sizeof(parsed_data)); 192 | lwjson_stream_init(&parser, prv_parser_callback); 193 | 194 | /* Run the parser through all */ 195 | const char* ptr = json_stream_to_parse; 196 | printf("Starting the parser...\r\n"); 197 | while (*ptr) { 198 | lwjsonr_t res = lwjson_stream_parse(&parser, *ptr++); 199 | if (res == lwjsonSTREAMDONE) { 200 | break; 201 | } else if (res == lwjsonERR) { 202 | printf("Stream returned error\r\n"); 203 | break; 204 | } 205 | } 206 | printf("Parser completed\r\n"); 207 | printf("Analyze the data here\r\n"); 208 | 209 | /* Simple array */ 210 | RUN_TEST(parsed_data.numbers_array[0] == 123); 211 | RUN_TEST(parsed_data.numbers_array[1] == -123); 212 | RUN_TEST(parsed_data.numbers_array[2] == 987); 213 | 214 | /* A value in a key */ 215 | RUN_TEST(parsed_data.numbers_obj_num1 == 123); 216 | RUN_TEST(parsed_data.numbers_obj_num2 == 456); 217 | 218 | /* 2 array set */ 219 | RUN_TEST(parsed_data.numbers_array_array[0][0] == 1); 220 | RUN_TEST(parsed_data.numbers_array_array[0][1] == 2); 221 | RUN_TEST(parsed_data.numbers_array_array[0][2] == 3); 222 | RUN_TEST(parsed_data.numbers_array_array[1][0] == 4); 223 | RUN_TEST(parsed_data.numbers_array_array[1][1] == 5); 224 | RUN_TEST(parsed_data.numbers_array_array[1][2] == 6); 225 | 226 | /* Check for real number */ 227 | RUN_TEST(FLOAT_IS_EQUAL(parsed_data.numbers_real, 3.5f)); 228 | 229 | /* Check string */ 230 | RUN_TEST(strcmp(LONG_STRING_TEST, parsed_data.long_string) == 0); 231 | 232 | /* Check event types */ 233 | RUN_TEST(parsed_data.event_counter == num_expected_events); 234 | RUN_TEST(!parsed_data.event_error); 235 | 236 | return test_failed > 0 ? -1 : 0; 237 | } -------------------------------------------------------------------------------- /tests/test_serializer/test_serializer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "lwjson/lwjson.h" 8 | #include "lwjson/lwjson_serializer.h" 9 | #include "lwjson/lwjson_utils.h" 10 | #include "test.h" 11 | 12 | #define RUN_TEST(exp_res_must_be_true) \ 13 | if ((exp_res_must_be_true)) { \ 14 | ++test_passed; \ 15 | } else { \ 16 | ++test_failed; \ 17 | printf("Test failed on line %d\r\n", __LINE__); \ 18 | } 19 | 20 | /** 21 | * \brief Test data structure to serialize 22 | */ 23 | typedef struct { 24 | int numbers_array[3]; 25 | struct { 26 | int num1; 27 | int num2; 28 | } numbers_obj; 29 | int numbers_array_array[2][3]; 30 | lwjson_real_t number_real; 31 | char long_string[128]; 32 | uint8_t bool_value; 33 | } test_data_t; 34 | 35 | /* Test data to serialize */ 36 | static test_data_t test_data = { 37 | .numbers_array = {123, -123, 987}, 38 | .numbers_obj = { 39 | .num1 = 123, 40 | .num2 = 456 41 | }, 42 | .numbers_array_array = { 43 | {1, 2, 3}, 44 | {4, 5, 6} 45 | }, 46 | .number_real = 3.5f, 47 | .long_string = "this is a test string for serialization", 48 | .bool_value = 1 49 | }; 50 | 51 | /** 52 | * \brief Serialize test data to JSON string using lwjson_serializer 53 | * \param[out] json_str: Output buffer for JSON string 54 | * \param[in] max_len: Maximum length of output buffer 55 | * \return Length of serialized JSON or -1 on error 56 | */ 57 | static int 58 | serialize_test_data(char* json_str, size_t max_len) { 59 | lwjson_serializer_t serializer; 60 | lwjsonr_t res; 61 | size_t outputBytes = 0; 62 | 63 | /* Initialize serializer with output buffer */ 64 | res = lwjson_serializer_init(&serializer, json_str, max_len); 65 | if (res != lwjsonOK) { 66 | printf("Failed to initialize serializer: %d\r\n", res); 67 | return -1; 68 | } 69 | 70 | /* Start root object */ 71 | res = lwjson_serializer_start_object(&serializer, NULL, 0); 72 | if (res != lwjsonOK) { 73 | printf("Failed to start object: %d\r\n", res); 74 | return -1; 75 | } 76 | 77 | /* Serialize numbers_array */ 78 | res = lwjson_serializer_start_array(&serializer, "numbers_array", 13); 79 | if (res != lwjsonOK) { 80 | printf("Failed to start numbers_array: %d\r\n", res); 81 | return -1; 82 | } 83 | 84 | for (int i = 0; i < 3; i++) { 85 | res = lwjson_serializer_add_int(&serializer, NULL, 0, test_data.numbers_array[i]); 86 | if (res != lwjsonOK) { 87 | printf("Failed to add numbers_array[%d]: %d\r\n", i, res); 88 | return -1; 89 | } 90 | } 91 | 92 | res = lwjson_serializer_end_array(&serializer); 93 | if (res != lwjsonOK) { 94 | printf("Failed to end numbers_array: %d\r\n", res); 95 | return -1; 96 | } 97 | 98 | /* Serialize numbers_obj */ 99 | res = lwjson_serializer_start_object(&serializer, "numbers_obj", 11); 100 | if (res != lwjsonOK) { 101 | printf("Failed to start numbers_obj: %d\r\n", res); 102 | return -1; 103 | } 104 | 105 | res = lwjson_serializer_add_int(&serializer, "num1", 4, test_data.numbers_obj.num1); 106 | if (res != lwjsonOK) { 107 | printf("Failed to add num1: %d\r\n", res); 108 | return -1; 109 | } 110 | 111 | res = lwjson_serializer_add_int(&serializer, "num2", 4, test_data.numbers_obj.num2); 112 | if (res != lwjsonOK) { 113 | printf("Failed to add num2: %d\r\n", res); 114 | return -1; 115 | } 116 | 117 | res = lwjson_serializer_end_object(&serializer); 118 | if (res != lwjsonOK) { 119 | printf("Failed to end numbers_obj: %d\r\n", res); 120 | return -1; 121 | } 122 | 123 | /* Serialize numbers_array_array */ 124 | res = lwjson_serializer_start_array(&serializer, "numbers_array_array", 19); 125 | if (res != lwjsonOK) { 126 | printf("Failed to start numbers_array_array: %d\r\n", res); 127 | return -1; 128 | } 129 | 130 | for (int i = 0; i < 2; i++) { 131 | res = lwjson_serializer_start_array(&serializer, NULL, 0); 132 | if (res != lwjsonOK) { 133 | printf("Failed to start inner array[%d]: %d\r\n", i, res); 134 | return -1; 135 | } 136 | 137 | for (int j = 0; j < 3; j++) { 138 | res = lwjson_serializer_add_int(&serializer, NULL, 0, test_data.numbers_array_array[i][j]); 139 | if (res != lwjsonOK) { 140 | printf("Failed to add numbers_array_array[%d][%d]: %d\r\n", i, j, res); 141 | return -1; 142 | } 143 | } 144 | 145 | res = lwjson_serializer_end_array(&serializer); 146 | if (res != lwjsonOK) { 147 | printf("Failed to end inner array[%d]: %d\r\n", i, res); 148 | return -1; 149 | } 150 | } 151 | 152 | res = lwjson_serializer_end_array(&serializer); 153 | if (res != lwjsonOK) { 154 | printf("Failed to end numbers_array_array: %d\r\n", res); 155 | return -1; 156 | } 157 | 158 | /* Serialize number_real */ 159 | res = lwjson_serializer_add_float(&serializer, "number_real", 11, test_data.number_real); 160 | if (res != lwjsonOK) { 161 | printf("Failed to add number_real: %d\r\n", res); 162 | return -1; 163 | } 164 | 165 | /* Serialize long_string */ 166 | res = lwjson_serializer_add_string(&serializer, "long_string", 11, test_data.long_string, strlen(test_data.long_string)); 167 | if (res != lwjsonOK) { 168 | printf("Failed to add long_string: %d\r\n", res); 169 | return -1; 170 | } 171 | 172 | /* Serialize bool_value */ 173 | res = lwjson_serializer_add_bool(&serializer, "bool_value", 10, test_data.bool_value); 174 | if (res != lwjsonOK) { 175 | printf("Failed to add bool_value: %d\r\n", res); 176 | return -1; 177 | } 178 | 179 | /* End root object */ 180 | res = lwjson_serializer_end_object(&serializer); 181 | if (res != lwjsonOK) { 182 | printf("Failed to end object: %d\r\n", res); 183 | return -1; 184 | } 185 | 186 | res = lwjson_serializer_finalize(&serializer, &outputBytes); 187 | if (res != lwjsonOK) { 188 | printf("Failed to finalize serializer: %d\r\n", res); 189 | return -1; 190 | } 191 | return outputBytes; 192 | } 193 | 194 | /** 195 | * \brief Test lwjson_utils string escaping functions 196 | * \return 0 on success, -1 on error 197 | */ 198 | static int 199 | test_string_utils(void) { 200 | char input[] = "Hello \"World\"\t\n\r\\Test - 中文 ČĞ\xc5\xbe"; 201 | char output[256]; 202 | size_t bytes_written = 0; 203 | lwjsonr_t res; 204 | char unescaped[256]; 205 | size_t unescaped_len = 0; 206 | 207 | printf("Testing string escape utilities...\r\n"); 208 | printf("Input string: '%s'\r\n", input); 209 | 210 | /* Test string escaping */ 211 | res = lwjson_utils_escape_string(input, strlen(input), output, sizeof(output), &bytes_written); 212 | if (res != lwjsonOK) { 213 | printf("Failed to escape string: %d\r\n", res); 214 | return -1; 215 | } 216 | 217 | printf("Escaped string: '%.*s' (length: %lu)\r\n", (int)bytes_written, output, (unsigned long)bytes_written); 218 | 219 | /* Test string unescaping */ 220 | res = lwjson_utils_unescape_string(output, bytes_written, unescaped, sizeof(unescaped) - 1, &unescaped_len); 221 | if (res != lwjsonOK) { 222 | printf("Failed to unescape string: %d\r\n", res); 223 | return -1; 224 | } 225 | 226 | unescaped[unescaped_len] = '\0'; /* Null terminate for comparison */ 227 | printf("Unescaped string: '%s' (length: %lu)\r\n", unescaped, (unsigned long)unescaped_len); 228 | 229 | /* Verify round-trip correctness */ 230 | if (strlen(input) == unescaped_len && memcmp(input, unescaped, unescaped_len) == 0) { 231 | printf("String escape/unescape round-trip test PASSED\r\n"); 232 | return 0; 233 | } else { 234 | printf("String escape/unescape round-trip test FAILED\r\n"); 235 | return -1; 236 | } 237 | } 238 | 239 | int 240 | test_run(void) { 241 | size_t test_failed = 0, test_passed = 0; 242 | char serialized_json[1024]; 243 | int json_len; 244 | lwjson_token_t tokens[256]; 245 | lwjson_t lwj; 246 | lwjsonr_t res; 247 | const lwjson_token_t* token; 248 | size_t str_len = 0; 249 | 250 | printf("---\r\nTest JSON serialization and utilities\r\n"); 251 | 252 | /* Test string utilities first */ 253 | printf("\r\n=== Testing lwjson_utils functions ===\r\n"); 254 | RUN_TEST(test_string_utils() == 0); 255 | 256 | /* Test Unicode unescaping (emoji, euro, chinese) */ 257 | { 258 | struct { 259 | const char* input; 260 | const char* expected_utf8; 261 | const char* description; 262 | } unicode_tests[] = { 263 | {"\u20AC", "\xe2\x82\xac", "Euro sign"}, 264 | {"\u4E2D\u6587", "\xe4\xb8\xad\xe6\x96\x87", "Chinese: 中文"}, 265 | {"\u010C\u011E\u0160\u017E", "\xc4\x8c\xc4\x9e\xc5\xa0\xc5\xbe", "Czech: ČĚŠŽ"} 266 | }; 267 | char outbuf[16]; 268 | size_t outlen; 269 | for (size_t i = 0; i < sizeof(unicode_tests)/sizeof(unicode_tests[0]); ++i) { 270 | memset(outbuf, 0, sizeof(outbuf)); 271 | res = lwjson_utils_unescape_string(unicode_tests[i].input, strlen(unicode_tests[i].input), outbuf, sizeof(outbuf), &outlen); 272 | int match = (res == lwjsonOK && outlen == strlen(unicode_tests[i].expected_utf8) && memcmp(outbuf, unicode_tests[i].expected_utf8, outlen) == 0); 273 | printf("Unicode test '%s': %s\r\n", unicode_tests[i].description, match ? "PASSED" : "FAILED"); 274 | RUN_TEST(match); 275 | } 276 | } 277 | 278 | printf("\r\n=== Testing lwjson_serializer functions ===\r\n"); 279 | 280 | /* Initialize lwjson */ 281 | if (lwjson_init(&lwj, tokens, LWJSON_ARRAYSIZE(tokens)) != lwjsonOK) { 282 | printf("JSON init failed\r\n"); 283 | return -1; 284 | } 285 | 286 | /* Test serialization */ 287 | json_len = serialize_test_data(serialized_json, sizeof(serialized_json)); 288 | if (json_len > 0 && json_len < (int)sizeof(serialized_json)) { 289 | serialized_json[json_len] = '\0'; /* Ensure null termination */ 290 | printf("Serialized JSON (%d chars): %s\r\n", json_len, serialized_json); 291 | } else { 292 | printf("ERROR: Invalid serialization length: %d\r\n", json_len); 293 | return -1; 294 | } 295 | 296 | RUN_TEST(json_len > 0); 297 | 298 | /* Test if we can parse the serialized JSON back */ 299 | res = lwjson_parse(&lwj, serialized_json); 300 | RUN_TEST(res == lwjsonOK); 301 | 302 | if (res == lwjsonOK) { 303 | /* Verify array values */ 304 | token = lwjson_find(&lwj, "numbers_array.#0"); 305 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 123); 306 | 307 | token = lwjson_find(&lwj, "numbers_array.#1"); 308 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == -123); 309 | 310 | token = lwjson_find(&lwj, "numbers_array.#2"); 311 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 987); 312 | 313 | /* Verify object values */ 314 | token = lwjson_find(&lwj, "numbers_obj.num1"); 315 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 123); 316 | 317 | token = lwjson_find(&lwj, "numbers_obj.num2"); 318 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 456); 319 | 320 | /* Verify 2D array values */ 321 | token = lwjson_find(&lwj, "numbers_array_array.#0.#0"); 322 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 1); 323 | 324 | token = lwjson_find(&lwj, "numbers_array_array.#0.#1"); 325 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 2); 326 | 327 | token = lwjson_find(&lwj, "numbers_array_array.#1.#2"); 328 | RUN_TEST(token != NULL && lwjson_get_val_int(token) == 6); 329 | 330 | /* Verify real number */ 331 | token = lwjson_find(&lwj, "number_real"); 332 | RUN_TEST(token != NULL && lwjson_get_val_real(token) == 3.5); 333 | 334 | /* Verify string */ 335 | token = lwjson_find(&lwj, "long_string"); 336 | RUN_TEST(token != NULL); 337 | const char* str_val = lwjson_get_val_string(token, &str_len); 338 | RUN_TEST(str_len == strlen("this is a test string for serialization") 339 | && strncmp(str_val, "this is a test string for serialization", str_len) == 0); 340 | 341 | /* Verify boolean */ 342 | token = lwjson_find(&lwj, "bool_value"); 343 | RUN_TEST(token != NULL && token->type == LWJSON_TYPE_TRUE); 344 | 345 | /* Free JSON */ 346 | RUN_TEST(lwjson_free(&lwj) == lwjsonOK); 347 | } 348 | 349 | printf("Serialization tests completed: %zu passed, %zu failed\r\n", test_passed, test_failed); 350 | 351 | return test_failed > 0 ? -1 : 0; 352 | } 353 | -------------------------------------------------------------------------------- /docs/static/dark-light/dark-mode-toggle.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // @license © 2019 Google LLC. Licensed under the Apache License, Version 2.0. 18 | const doc = document; 19 | const store = localStorage; 20 | const PREFERS_COLOR_SCHEME = 'prefers-color-scheme'; 21 | const MEDIA = 'media'; 22 | const LIGHT = 'light'; 23 | const DARK = 'dark'; 24 | const MQ_DARK = `(${PREFERS_COLOR_SCHEME}:${DARK})`; 25 | const MQ_LIGHT = `(${PREFERS_COLOR_SCHEME}:${LIGHT})`; 26 | const LINK_REL_STYLESHEET = 'link[rel=stylesheet]'; 27 | const REMEMBER = 'remember'; 28 | const LEGEND = 'legend'; 29 | const TOGGLE = 'toggle'; 30 | const SWITCH = 'switch'; 31 | const APPEARANCE = 'appearance'; 32 | const PERMANENT = 'permanent'; 33 | const MODE = 'mode'; 34 | const COLOR_SCHEME_CHANGE = 'colorschemechange'; 35 | const PERMANENT_COLOR_SCHEME = 'permanentcolorscheme'; 36 | const ALL = 'all'; 37 | const NOT_ALL = 'not all'; 38 | const NAME = 'dark-mode-toggle'; 39 | const DEFAULT_URL = 'https://googlechromelabs.github.io/dark-mode-toggle/demo/'; 40 | 41 | // See https://html.spec.whatwg.org/multipage/common-dom-interfaces.html ↵ 42 | // #reflecting-content-attributes-in-idl-attributes. 43 | const installStringReflection = (obj, attrName, propName = attrName) => { 44 | Object.defineProperty(obj, propName, { 45 | enumerable: true, 46 | get() { 47 | const value = this.getAttribute(attrName); 48 | return value === null ? '' : value; 49 | }, 50 | set(v) { 51 | this.setAttribute(attrName, v); 52 | }, 53 | }); 54 | }; 55 | 56 | const installBoolReflection = (obj, attrName, propName = attrName) => { 57 | Object.defineProperty(obj, propName, { 58 | enumerable: true, 59 | get() { 60 | return this.hasAttribute(attrName); 61 | }, 62 | set(v) { 63 | if (v) { 64 | this.setAttribute(attrName, ''); 65 | } else { 66 | this.removeAttribute(attrName); 67 | } 68 | }, 69 | }); 70 | }; 71 | 72 | const template = doc.createElement('template'); 73 | // ⚠️ Note: this is a minified version of `src/template-contents.tpl`. 74 | // Compress the CSS with https://cssminifier.com/, then paste it here. 75 | // eslint-disable-next-line max-len 76 | template.innerHTML = `
`; 77 | 78 | export class DarkModeToggle extends HTMLElement { 79 | static get observedAttributes() { 80 | return [MODE, APPEARANCE, PERMANENT, LEGEND, LIGHT, DARK, REMEMBER]; 81 | } 82 | 83 | constructor() { 84 | super(); 85 | 86 | installStringReflection(this, MODE); 87 | installStringReflection(this, APPEARANCE); 88 | installStringReflection(this, LEGEND); 89 | installStringReflection(this, LIGHT); 90 | installStringReflection(this, DARK); 91 | installStringReflection(this, REMEMBER); 92 | 93 | installBoolReflection(this, PERMANENT); 94 | 95 | this._darkCSS = null; 96 | this._lightCSS = null; 97 | 98 | doc.addEventListener(COLOR_SCHEME_CHANGE, (event) => { 99 | this.mode = event.detail.colorScheme; 100 | this._updateRadios(); 101 | this._updateCheckbox(); 102 | }); 103 | 104 | doc.addEventListener(PERMANENT_COLOR_SCHEME, (event) => { 105 | this.permanent = event.detail.permanent; 106 | this._permanentCheckbox.checked = this.permanent; 107 | }); 108 | 109 | this._initializeDOM(); 110 | } 111 | 112 | _initializeDOM() { 113 | const shadowRoot = this.attachShadow({mode: 'open'}); 114 | shadowRoot.appendChild(template.content.cloneNode(true)); 115 | 116 | // We need to support `media="(prefers-color-scheme: dark)"` (with space) 117 | // and `media="(prefers-color-scheme:dark)"` (without space) 118 | this._darkCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${DARK}"]`); 119 | this._lightCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${LIGHT}"]`); 120 | 121 | // Get DOM references. 122 | this._lightRadio = shadowRoot.querySelector('[part=lightRadio]'); 123 | this._lightLabel = shadowRoot.querySelector('[part=lightLabel]'); 124 | this._darkRadio = shadowRoot.querySelector('[part=darkRadio]'); 125 | this._darkLabel = shadowRoot.querySelector('[part=darkLabel]'); 126 | this._darkCheckbox = shadowRoot.querySelector('[part=toggleCheckbox]'); 127 | this._checkboxLabel = shadowRoot.querySelector('[part=toggleLabel]'); 128 | this._legendLabel = shadowRoot.querySelector('legend'); 129 | this._permanentAside = shadowRoot.querySelector('aside'); 130 | this._permanentCheckbox = 131 | shadowRoot.querySelector('[part=permanentCheckbox]'); 132 | this._permanentLabel = shadowRoot.querySelector('[part=permanentLabel]'); 133 | 134 | // Does the browser support native `prefers-color-scheme`? 135 | const hasNativePrefersColorScheme = 136 | matchMedia(MQ_DARK).media !== NOT_ALL; 137 | // Listen to `prefers-color-scheme` changes. 138 | if (hasNativePrefersColorScheme) { 139 | matchMedia(MQ_DARK).addListener(({matches}) => { 140 | this.mode = matches ? DARK : LIGHT; 141 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 142 | }); 143 | } 144 | // Set initial state, giving preference to a remembered value, then the 145 | // native value (if supported), and eventually defaulting to a light 146 | // experience. 147 | const rememberedValue = store.getItem(NAME); 148 | if (rememberedValue && [DARK, LIGHT].includes(rememberedValue)) { 149 | this.mode = rememberedValue; 150 | this._permanentCheckbox.checked = true; 151 | this.permanent = true; 152 | } else if (hasNativePrefersColorScheme) { 153 | this.mode = matchMedia(MQ_LIGHT).matches ? LIGHT : DARK; 154 | } 155 | if (!this.mode) { 156 | this.mode = LIGHT; 157 | } 158 | if (this.permanent && !rememberedValue) { 159 | store.setItem(NAME, this.mode); 160 | } 161 | 162 | // Default to toggle appearance. 163 | if (!this.appearance) { 164 | this.appearance = TOGGLE; 165 | } 166 | 167 | // Update the appearance to either of toggle or switch. 168 | this._updateAppearance(); 169 | 170 | // Update the radios 171 | this._updateRadios(); 172 | 173 | // Make the checkbox reflect the state of the radios 174 | this._updateCheckbox(); 175 | 176 | // Synchronize the behavior of the radio and the checkbox. 177 | [this._lightRadio, this._darkRadio].forEach((input) => { 178 | input.addEventListener('change', () => { 179 | this.mode = this._lightRadio.checked ? LIGHT : DARK; 180 | this._updateCheckbox(); 181 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 182 | }); 183 | }); 184 | this._darkCheckbox.addEventListener('change', () => { 185 | this.mode = this._darkCheckbox.checked ? DARK : LIGHT; 186 | this._updateRadios(); 187 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 188 | }); 189 | 190 | // Make remembering the last mode optional 191 | this._permanentCheckbox.addEventListener('change', () => { 192 | this.permanent = this._permanentCheckbox.checked; 193 | this._dispatchEvent(PERMANENT_COLOR_SCHEME, { 194 | permanent: this.permanent, 195 | }); 196 | }); 197 | 198 | // Finally update the mode and let the world know what's going on 199 | this._updateMode(); 200 | this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode}); 201 | this._dispatchEvent(PERMANENT_COLOR_SCHEME, { 202 | permanent: this.permanent, 203 | }); 204 | } 205 | 206 | attributeChangedCallback(name, oldValue, newValue) { 207 | if (name === MODE) { 208 | if (![LIGHT, DARK].includes(newValue)) { 209 | throw new RangeError(`Allowed values: "${LIGHT}" and "${DARK}".`); 210 | } 211 | // Only show the dialog programmatically on devices not capable of hover 212 | // and only if there is a label 213 | if (matchMedia('(hover:none)').matches && this.remember) { 214 | this._showPermanentAside(); 215 | } 216 | if (this.permanent) { 217 | store.setItem(NAME, this.mode); 218 | } 219 | this._updateRadios(); 220 | this._updateCheckbox(); 221 | this._updateMode(); 222 | } else if (name === APPEARANCE) { 223 | if (![TOGGLE, SWITCH].includes(newValue)) { 224 | throw new RangeError(`Allowed values: "${TOGGLE}" and "${SWITCH}".`); 225 | } 226 | this._updateAppearance(); 227 | } else if (name === PERMANENT) { 228 | if (this.permanent) { 229 | store.setItem(NAME, this.mode); 230 | } else { 231 | store.removeItem(NAME); 232 | } 233 | this._permanentCheckbox.checked = this.permanent; 234 | } else if (name === LEGEND) { 235 | this._legendLabel.textContent = newValue; 236 | } else if (name === REMEMBER) { 237 | this._permanentLabel.textContent = newValue; 238 | } else if (name === LIGHT) { 239 | this._lightLabel.textContent = newValue; 240 | if (this.mode === LIGHT) { 241 | this._checkboxLabel.textContent = newValue; 242 | } 243 | } else if (name === DARK) { 244 | this._darkLabel.textContent = newValue; 245 | if (this.mode === DARK) { 246 | this._checkboxLabel.textContent = newValue; 247 | } 248 | } 249 | } 250 | 251 | _dispatchEvent(type, value) { 252 | this.dispatchEvent(new CustomEvent(type, { 253 | bubbles: true, 254 | composed: true, 255 | detail: value, 256 | })); 257 | } 258 | 259 | _updateAppearance() { 260 | // Hide or show the light-related affordances dependent on the appearance, 261 | // which can be "switch" or "toggle". 262 | const appearAsToggle = this.appearance === TOGGLE; 263 | this._lightRadio.hidden = appearAsToggle; 264 | this._lightLabel.hidden = appearAsToggle; 265 | this._darkRadio.hidden = appearAsToggle; 266 | this._darkLabel.hidden = appearAsToggle; 267 | this._darkCheckbox.hidden = !appearAsToggle; 268 | this._checkboxLabel.hidden = !appearAsToggle; 269 | } 270 | 271 | _updateRadios() { 272 | if (this.mode === LIGHT) { 273 | this._lightRadio.checked = true; 274 | } else { 275 | this._darkRadio.checked = true; 276 | } 277 | } 278 | 279 | _updateCheckbox() { 280 | if (this.mode === LIGHT) { 281 | this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`, 282 | `var(--${NAME}-light-icon,url("${DEFAULT_URL}moon.png"))`); 283 | this._checkboxLabel.textContent = this.light; 284 | if (!this.light) { 285 | this._checkboxLabel.ariaLabel = DARK; 286 | } 287 | this._darkCheckbox.checked = false; 288 | } else { 289 | this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`, 290 | `var(--${NAME}-dark-icon,url("${DEFAULT_URL}sun.png"))`); 291 | this._checkboxLabel.textContent = this.dark; 292 | if (!this.dark) { 293 | this._checkboxLabel.ariaLabel = LIGHT; 294 | } 295 | this._darkCheckbox.checked = true; 296 | } 297 | } 298 | 299 | _updateMode() { 300 | if (this.mode === LIGHT) { 301 | this._lightCSS.forEach((link) => { 302 | link.media = ALL; 303 | link.disabled = false; 304 | }); 305 | this._darkCSS.forEach((link) => { 306 | link.media = NOT_ALL; 307 | link.disabled = true; 308 | }); 309 | } else { 310 | this._darkCSS.forEach((link) => { 311 | link.media = ALL; 312 | link.disabled = false; 313 | }); 314 | this._lightCSS.forEach((link) => { 315 | link.media = NOT_ALL; 316 | link.disabled = true; 317 | }); 318 | } 319 | } 320 | 321 | _showPermanentAside() { 322 | this._permanentAside.style.visibility = 'visible'; 323 | setTimeout(() => { 324 | this._permanentAside.style.visibility = 'hidden'; 325 | }, 3000); 326 | } 327 | } 328 | 329 | customElements.define(NAME, DarkModeToggle); -------------------------------------------------------------------------------- /lwjson/src/include/lwjson/lwjson.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file lwjson.h 3 | * \brief LwJSON - Lightweight JSON format parser 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2024 Tilen MAJERLE 8 | * 9 | * Permission is hereby granted, free of charge, to any person 10 | * obtaining a copy of this software and associated documentation 11 | * files (the "Software"), to deal in the Software without restriction, 12 | * including without limitation the rights to use, copy, modify, merge, 13 | * publish, distribute, sublicense, and/or sell copies of the Software, 14 | * and to permit persons to whom the Software is furnished to do so, 15 | * subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 23 | * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | * 29 | * This file is part of LwJSON - Lightweight JSON format parser. 30 | * 31 | * Author: Tilen MAJERLE 32 | * Version: v1.8.1 33 | */ 34 | #ifndef LWJSON_HDR_H 35 | #define LWJSON_HDR_H 36 | 37 | #include 38 | #include 39 | #include "lwjson/lwjson_opt.h" 40 | 41 | #ifdef __cplusplus 42 | extern "C" { 43 | #endif /* __cplusplus */ 44 | 45 | /** 46 | * \defgroup LWJSON Lightweight JSON format parser 47 | * \brief LwJSON - Lightweight JSON format parser 48 | * \{ 49 | */ 50 | 51 | /** 52 | * \brief Get size of statically allocated array 53 | * \param[in] x: Object to get array size of 54 | * \return Number of elements in array 55 | */ 56 | #define LWJSON_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) 57 | 58 | /** 59 | * \brief List of supported JSON types 60 | */ 61 | typedef enum { 62 | LWJSON_TYPE_STRING, /*!< String/Text format. Everything that has beginning and ending quote character */ 63 | LWJSON_TYPE_NUM_INT, /*!< Number type for integer */ 64 | LWJSON_TYPE_NUM_REAL, /*!< Number type for real number */ 65 | LWJSON_TYPE_OBJECT, /*!< Object data type */ 66 | LWJSON_TYPE_ARRAY, /*!< Array data type */ 67 | LWJSON_TYPE_TRUE, /*!< True boolean value */ 68 | LWJSON_TYPE_FALSE, /*!< False boolean value */ 69 | LWJSON_TYPE_NULL, /*!< Null value */ 70 | } lwjson_type_t; 71 | 72 | #if defined(LWJSON_DEV) 73 | extern const char* const lwjson_type_strings[]; 74 | #endif 75 | 76 | /** 77 | * \brief Real data type 78 | */ 79 | typedef LWJSON_CFG_REAL_TYPE lwjson_real_t; 80 | 81 | /** 82 | * \brief Integer data type 83 | */ 84 | typedef LWJSON_CFG_INT_TYPE lwjson_int_t; 85 | 86 | /** 87 | * \brief JSON token 88 | */ 89 | typedef struct lwjson_token { 90 | struct lwjson_token* next; /*!< Next token on a list */ 91 | lwjson_type_t type; /*!< Token type */ 92 | const char* token_name; /*!< Token name (if exists) */ 93 | size_t token_name_len; /*!< Length of token name (this is needed to support const input strings to parse) */ 94 | 95 | union { 96 | struct { 97 | const char* token_value; /*!< Pointer to the beginning of the string */ 98 | size_t 99 | token_value_len; /*!< Length of token value (this is needed to support const input strings to parse) */ 100 | } str; /*!< String data */ 101 | 102 | lwjson_real_t num_real; /*!< Real number format */ 103 | lwjson_int_t num_int; /*!< Int number format */ 104 | struct lwjson_token* first_child; /*!< First children object for object or array type */ 105 | } u; /*!< Union with different data types */ 106 | } lwjson_token_t; 107 | 108 | /** 109 | * \brief JSON result enumeration 110 | */ 111 | typedef enum { 112 | lwjsonOK = 0x00, /*!< Function returns successfully */ 113 | lwjsonERR, /*!< Generic error message */ 114 | lwjsonERRJSON, /*!< Error JSON format */ 115 | lwjsonERRMEM, /*!< Memory error */ 116 | lwjsonERRPAR, /*!< Parameter error */ 117 | 118 | lwjsonSTREAMWAITFIRSTCHAR, /*!< Streaming parser did not yet receive first valid character 119 | indicating start of JSON sequence */ 120 | lwjsonSTREAMDONE, /*!< Streaming parser is done, 121 | closing character matched the stream opening one */ 122 | lwjsonSTREAMINPROG, /*!< Stream parsing is still in progress */ 123 | 124 | lwjsonERRNULL, /*!< NULL pointer provided as parameter */ 125 | lwjsonERRINVAL, /*!< Invalid input parameter, other than NULL */ 126 | lwjsonERRBUF, /*!< Output buffer is too small */ 127 | lwjsonERRESC, /*!< Invalid escape sequence in string */ 128 | } lwjsonr_t; 129 | 130 | /** 131 | * \brief LwJSON instance 132 | */ 133 | typedef struct { 134 | lwjson_token_t* tokens; /*!< Pointer to array of tokens */ 135 | size_t tokens_len; /*!< Size of all tokens */ 136 | size_t next_free_token_pos; /*!< Position of next free token instance */ 137 | lwjson_token_t first_token; /*!< First token on a list */ 138 | 139 | struct { 140 | uint8_t parsed : 1; /*!< Flag indicating JSON parsing has finished successfully */ 141 | } flags; /*!< List of flags */ 142 | } lwjson_t; 143 | 144 | lwjsonr_t lwjson_init(lwjson_t* lwobj, lwjson_token_t* tokens, size_t tokens_len); 145 | lwjsonr_t lwjson_parse_ex(lwjson_t* lwobj, const void* json_data, size_t len); 146 | lwjsonr_t lwjson_parse(lwjson_t* lwobj, const char* json_str); 147 | const lwjson_token_t* lwjson_find(lwjson_t* lwobj, const char* path); 148 | const lwjson_token_t* lwjson_find_ex(lwjson_t* lwobj, const lwjson_token_t* token, const char* path); 149 | lwjsonr_t lwjson_free(lwjson_t* lwobj); 150 | 151 | void lwjson_print_token(const lwjson_token_t* token); 152 | void lwjson_print_json(const lwjson_t* lwobj); 153 | 154 | /** 155 | * \brief Object type for streaming parser 156 | */ 157 | typedef enum { 158 | LWJSON_STREAM_TYPE_NONE, /*!< No entry - not used */ 159 | LWJSON_STREAM_TYPE_OBJECT, /*!< Object indication */ 160 | LWJSON_STREAM_TYPE_OBJECT_END, /*!< Object end indication */ 161 | LWJSON_STREAM_TYPE_ARRAY, /*!< Array indication */ 162 | LWJSON_STREAM_TYPE_ARRAY_END, /*!< Array end indication */ 163 | LWJSON_STREAM_TYPE_KEY, /*!< Key string */ 164 | LWJSON_STREAM_TYPE_STRING, /*!< Strin type */ 165 | LWJSON_STREAM_TYPE_TRUE, /*!< True primitive */ 166 | LWJSON_STREAM_TYPE_FALSE, /*!< False primitive */ 167 | LWJSON_STREAM_TYPE_NULL, /*!< Null primitive */ 168 | LWJSON_STREAM_TYPE_NUMBER, /*!< Generic number */ 169 | } lwjson_stream_type_t; 170 | 171 | /** 172 | * \brief Stream parsing stack object 173 | */ 174 | typedef struct { 175 | lwjson_stream_type_t type; /*!< Streaming type - current value */ 176 | 177 | union { 178 | char name[LWJSON_CFG_STREAM_KEY_MAX_LEN 179 | + 1]; /*!< Last known key name, used only for \ref LWJSON_STREAM_TYPE_KEY type */ 180 | uint16_t index; /*!< Current index when type is an array */ 181 | } meta; /*!< Meta information */ 182 | } lwjson_stream_stack_t; 183 | 184 | typedef enum { 185 | LWJSON_STREAM_STATE_WAITINGFIRSTCHAR = 0x00, /*!< State to wait for very first opening character */ 186 | LWJSON_STREAM_STATE_PARSING, /*!< In parsing of the first char state - detecting next character state */ 187 | LWJSON_STREAM_STATE_PARSING_STRING, /*!< Parse string primitive */ 188 | LWJSON_STREAM_STATE_PARSING_PRIMITIVE, /*!< Parse any primitive that is non-string, either "true", "false", "null" or a number */ 189 | LWJSON_STREAM_STATE_EXPECTING_COMMA_OR_END, /*!< Expecting ',', '}' or ']' */ 190 | LWJSON_STREAM_STATE_EXPECTING_COLON, /*!< Expecting ':' */ 191 | } lwjson_stream_state_t; 192 | 193 | /* Forward declaration */ 194 | struct lwjson_stream_parser; 195 | 196 | /** 197 | * \brief Callback function for various events 198 | * 199 | */ 200 | typedef void (*lwjson_stream_parser_callback_fn)(struct lwjson_stream_parser* jsp, lwjson_stream_type_t type); 201 | 202 | /** 203 | * \brief LwJSON streaming structure 204 | */ 205 | typedef struct lwjson_stream_parser { 206 | lwjson_stream_stack_t 207 | stack[LWJSON_CFG_STREAM_STACK_SIZE]; /*!< Stack used for parsing. TODO: Add conditional compilation flag */ 208 | size_t stack_pos; /*!< Current stack position */ 209 | 210 | lwjson_stream_state_t parse_state; /*!< Parser state */ 211 | 212 | lwjson_stream_parser_callback_fn evt_fn; /*!< Event function for user */ 213 | 214 | void* user_data; /*!< User data for callback function */ 215 | 216 | /* State */ 217 | union { 218 | struct { 219 | char buff[LWJSON_CFG_STREAM_STRING_MAX_LEN 220 | + 1]; /*!< Buffer to write temporary data. TODO: Size to be variable with define */ 221 | size_t buff_pos; /*!< Buffer position for next write (length of bytes in buffer) */ 222 | size_t buff_total_pos; /*!< Total buffer position used up to now (in several data chunks) */ 223 | uint8_t is_last; /*!< Status indicates if this is the last part of the string */ 224 | } str; /*!< String structure. It is only used for keys and string objects. 225 | Use primitive part for all other options */ 226 | 227 | struct { 228 | char buff[LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN + 1]; /*!< Temporary write buffer */ 229 | size_t buff_pos; /*!< Buffer position for next write */ 230 | } prim; /*!< Primitive object. Used for all types, except key or string */ 231 | 232 | /* Todo: Add other types */ 233 | } data; /*!< Data union used to parse various */ 234 | 235 | char prev_c; /*!< History of characters */ 236 | } lwjson_stream_parser_t; 237 | 238 | lwjsonr_t lwjson_stream_init(lwjson_stream_parser_t* jsp, lwjson_stream_parser_callback_fn evt_fn); 239 | lwjsonr_t lwjson_stream_set_user_data(lwjson_stream_parser_t* jsp, void* user_data); 240 | void* lwjson_stream_get_user_data(lwjson_stream_parser_t* jsp); 241 | lwjsonr_t lwjson_stream_reset(lwjson_stream_parser_t* jsp); 242 | lwjsonr_t lwjson_stream_parse(lwjson_stream_parser_t* jsp, char c); 243 | 244 | /** 245 | * \brief Get number of tokens used to parse JSON 246 | * \param[in] lwobj: Pointer to LwJSON instance 247 | * \return Number of tokens used to parse JSON 248 | */ 249 | #define lwjson_get_tokens_used(lwobj) (((lwobj) != NULL) ? ((lwobj)->next_free_token_pos + 1) : 0) 250 | 251 | /** 252 | * \brief Get very first token of LwJSON instance 253 | * \param[in] lwobj: Pointer to LwJSON instance 254 | * \return Pointer to first token 255 | */ 256 | #define lwjson_get_first_token(lwobj) (((lwobj) != NULL) ? (&(lwobj)->first_token) : NULL) 257 | 258 | /** 259 | * \brief Get token value for \ref LWJSON_TYPE_NUM_INT type 260 | * \param[in] token: token with integer type 261 | * \return Int number if type is integer, `0` otherwise 262 | */ 263 | #define lwjson_get_val_int(token) \ 264 | ((lwjson_int_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_INT) ? (token)->u.num_int : 0)) 265 | 266 | /** 267 | * \brief Get token value for \ref LWJSON_TYPE_NUM_REAL type 268 | * \param[in] token: token with real type 269 | * \return Real numbeer if type is real, `0` otherwise 270 | */ 271 | #define lwjson_get_val_real(token) \ 272 | ((lwjson_real_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_REAL) ? (token)->u.num_real : 0)) 273 | 274 | /** 275 | * \brief Get first child token for \ref LWJSON_TYPE_OBJECT or \ref LWJSON_TYPE_ARRAY types 276 | * \param[in] token: token with integer type 277 | * \return Pointer to first child or `NULL` if parent token is not object or array 278 | */ 279 | #define lwjson_get_first_child(token) \ 280 | (const void*)(((token) != NULL && ((token)->type == LWJSON_TYPE_OBJECT || (token)->type == LWJSON_TYPE_ARRAY)) \ 281 | ? (token)->u.first_child \ 282 | : NULL) 283 | 284 | /** 285 | * \brief Get string value from JSON token 286 | * \param[in] token: Token with string type 287 | * \param[out] str_len: Pointer to variable holding length of string. 288 | * Set to `NULL` if not used 289 | * \return Pointer to string or `NULL` if invalid token type 290 | */ 291 | static inline const char* 292 | lwjson_get_val_string(const lwjson_token_t* token, size_t* str_len) { 293 | if (token != NULL && token->type == LWJSON_TYPE_STRING) { 294 | if (str_len != NULL) { 295 | *str_len = token->u.str.token_value_len; 296 | } 297 | return token->u.str.token_value; 298 | } 299 | return NULL; 300 | } 301 | 302 | /** 303 | * \brief Get length of string for \ref LWJSON_TYPE_STRING token type 304 | * \param[in] token: token with string type 305 | * \return Length of string in units of bytes 306 | */ 307 | #define lwjson_get_val_string_length(token) \ 308 | ((size_t)(((token) != NULL && (token)->type == LWJSON_TYPE_STRING) ? (token)->u.str.token_value_len : 0)) 309 | 310 | /** 311 | * \brief Compare string token with user input string for a case-sensitive match 312 | * \param[in] token: Token with string type 313 | * \param[in] str: NULL-terminated string to compare 314 | * \return `1` if equal, `0` otherwise 315 | */ 316 | static inline uint8_t 317 | lwjson_string_compare(const lwjson_token_t* token, const char* str) { 318 | if (token != NULL && token->type == LWJSON_TYPE_STRING) { 319 | return strncmp(token->u.str.token_value, str, token->u.str.token_value_len) == 0; 320 | } 321 | return 0; 322 | } 323 | 324 | /** 325 | * \brief Compare string token with user input string for a case-sensitive match 326 | * \param[in] token: Token with string type 327 | * \param[in] str: NULL-terminated string to compare 328 | * \param[in] len: Length of the string in bytes 329 | * \return `1` if equal, `0` otherwise 330 | */ 331 | static inline uint8_t 332 | lwjson_string_compare_n(const lwjson_token_t* token, const char* str, size_t len) { 333 | if (token != NULL && token->type == LWJSON_TYPE_STRING && len <= token->u.str.token_value_len) { 334 | return strncmp(token->u.str.token_value, str, len) == 0; 335 | } 336 | return 0; 337 | } 338 | 339 | /** 340 | * \name LWJSON_STREAM_SEQ 341 | * \brief Helper functions for stack analysis in a callback function 342 | * \note Useful exclusively for streaming functions 343 | * \{ 344 | */ 345 | 346 | /** 347 | * \brief Check the sequence of JSON stack, starting from start_number index 348 | * \note This applies only to one sequence element. Other macros, starting with 349 | * `lwjson_stack_seq_X` (where X is the sequence length), provide 350 | * more parameters for longer sequences. 351 | * 352 | * \param[in] jsp: LwJSON stream instance 353 | * \param[in] start_num: Start number in the stack. Typically starts with `0`, but user may choose another 354 | * number, if intention is to check partial sequence only 355 | * \param[in] sp0: Stream stack type. Value of \ref lwjson_stream_type_t, but only last part of the enum. 356 | * If user is interested in the \ref LWJSON_STREAM_TYPE_OBJECT, 357 | * you should only write `OBJECT` as parameter. 358 | * Idea behind is to make code smaller and easier to read, especially when 359 | * using other sequence values with more parameters. 360 | * \return `0` if sequence doesn't match, non-zero otherwise 361 | */ 362 | #define lwjson_stack_seq_1(jsp, start_num, sp0) ((jsp)->stack[(start_num)].type == LWJSON_STREAM_TYPE_##sp0) 363 | #define lwjson_stack_seq_2(jsp, start_num, sp0, sp1) \ 364 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_1((jsp), (start_num) + 1, sp1)) 365 | #define lwjson_stack_seq_3(jsp, start_num, sp0, sp1, sp2) \ 366 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_2((jsp), (start_num) + 1, sp1, sp2)) 367 | #define lwjson_stack_seq_4(jsp, start_num, sp0, sp1, sp2, sp3) \ 368 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_3((jsp), (start_num) + 1, sp1, sp2, sp3)) 369 | #define lwjson_stack_seq_5(jsp, start_num, sp0, sp1, sp2, sp3, sp4) \ 370 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) && lwjson_stack_seq_4((jsp), (start_num) + 1, sp1, sp2, sp3, sp4)) 371 | #define lwjson_stack_seq_6(jsp, start_num, sp0, sp1, sp2, sp3, sp4, sp5) \ 372 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) \ 373 | && lwjson_stack_seq_5((jsp), (start_num) + 1, sp1, sp2, sp3, sp4, sp5)) 374 | #define lwjson_stack_seq_7(jsp, start_num, sp0, sp1, sp2, sp3, sp4, sp5, sp6) \ 375 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) \ 376 | && lwjson_stack_seq_6((jsp), (start_num) + 1, sp1, sp2, sp3, sp4, sp5, sp6)) 377 | #define lwjson_stack_seq_8(jsp, start_num, sp0, sp1, sp2, sp3, sp4, sp5, sp6, sp7) \ 378 | (lwjson_stack_seq_1((jsp), (start_num) + 0, sp0) \ 379 | && lwjson_stack_seq_7((jsp), (start_num) + 1, sp1, sp2, sp3, sp4, sp5, sp6, sp7)) 380 | 381 | /** 382 | * \} 383 | */ 384 | 385 | /** 386 | * \} 387 | */ 388 | 389 | #ifdef __cplusplus 390 | } 391 | #endif /* __cplusplus */ 392 | 393 | #endif /* LWJSON_HDR_H */ 394 | --------------------------------------------------------------------------------