diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..1fad350
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,193 @@
+---
+Language: Cpp
+# BasedOnStyle: LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: None
+AlignConsecutiveMacros:
+ Enabled: true
+ AcrossEmptyLines: true
+ AcrossComments: true
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields:
+ Enabled: true
+ AcrossEmptyLines: true
+ AcrossComments: true
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Right
+AlignOperands: Align
+SortIncludes: false
+InsertBraces: true # Control statements must have curly brackets
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Empty
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: AllDefinitions
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+ - __capability
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 120
+CommentPragmas: "^ IWYU pragma:"
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle: ""
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: "^<(.*)>"
+ Priority: 0
+ - Regex: '^"(.*)"'
+ Priority: 1
+ - Regex: "(.*)"
+ Priority: 2
+IncludeIsMainRegex: "(Test)?$"
+IncludeIsMainSourceRegex: ""
+IndentAccessModifiers: false
+IndentCaseLabels: true
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires: true
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ""
+MacroBlockEnd: ""
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth: -1
+ReferenceAlignment: Pointer
+ReflowComments: false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Always
+ShortNamespaceLines: 1
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+ AfterControlStatements: true
+ AfterForeachMacros: true
+ AfterFunctionDefinitionName: false
+ AfterFunctionDeclarationName: false
+ AfterIfMacros: true
+ AfterOverloadedOperator: false
+ BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: Latest
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+---
+
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c4e4ac0..f43a9c1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -22,6 +22,6 @@ jobs:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
- See the CHANGELOG.md
+ See the [CHANGELOG](CHANGELOG.md)
draft: false
prerelease: false
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a872aad..4674b8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,26 +24,29 @@
*.i
*.txt
!docs/*.txt
+!CMakeLists.txt
RTE/
-# IAR Settings
-**/settings/*.crun
-**/settings/*.dbgdt
-**/settings/*.cspy
-**/settings/*.cspy.*
-**/settings/*.xcl
-**/settings/*.dni
-**/settings/*.wsdt
-**/settings/*.wspos
-
-# IAR Debug Exe
-**/Exe/*.sim
-
-# IAR Debug Obj
-**/Obj/*.pbd
-**/Obj/*.pbd.*
-**/Obj/*.pbi
-**/Obj/*.pbi.*
+*debug
+
+# IAR Settings
+**/settings/*.crun
+**/settings/*.dbgdt
+**/settings/*.cspy
+**/settings/*.cspy.*
+**/settings/*.xcl
+**/settings/*.dni
+**/settings/*.wsdt
+**/settings/*.wspos
+
+# IAR Debug Exe
+**/Exe/*.sim
+
+# IAR Debug Obj
+**/Obj/*.pbd
+**/Obj/*.pbd.*
+**/Obj/*.pbi
+**/Obj/*.pbi.*
*.TMP
/docs_src/x_Doxyfile.doxy
@@ -69,6 +72,7 @@ RTE/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
+[Dd]ebug*/
x64/
x86/
bld/
@@ -76,6 +80,7 @@ bld/
[Oo]bj/
[Ll]og/
_build/
+build/
# Visual Studio 2015/2017 cache/options directory
.vs/
@@ -274,7 +279,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs
-# Including strong name files can present a security risk
+# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
@@ -370,7 +375,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/
-# Azure Stream Analytics local run output
+# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
@@ -383,3 +388,13 @@ log_file.txt
project.ioc
mx.scratch
*.tilen majerle
+
+
+# Altium
+Project outputs*
+History/
+*.SchDocPreview
+*.$$$Preview
+
+# VSCode projects
+project_vscode_compiled.exe
\ No newline at end of file
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
index f32f08f..4f457d1 100644
--- a/.vscode/c_cpp_properties.json
+++ b/.vscode/c_cpp_properties.json
@@ -1,22 +1,14 @@
{
+ "version": 4,
"configurations": [
{
+ /*
+ * Full configuration is provided by CMake plugin for vscode,
+ * that shall be installed by user
+ */
"name": "Win32",
- "includePath": [
- "${workspaceFolder}\\lwjson\\src\\include",
- "${workspaceFolder}\\dev\\VisualStudio",
- "${workspaceFolder}"
- ],
- "defines": [
- "_DEBUG",
- "UNICODE",
- "_UNICODE"
- ],
- "compilerPath": "C:\\MinGW\\bin\\gcc.exe",
- "cStandard": "gnu17",
- "cppStandard": "gnu++14",
- "intelliSenseMode": "windows-gcc-x86"
+ "intelliSenseMode": "${default}",
+ "configurationProvider": "ms-vscode.cmake-tools"
}
- ],
- "version": 4
+ ]
}
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..6a07920
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+ "recommendations": [
+ "ms-vscode.cpptools",
+ "ms-vscode.cmake-tools",
+ "twxs.cmake",
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index cd91592..c76ad22 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,26 +1,16 @@
{
"version": "0.2.0",
"configurations": [
- {
- "name": "g++.exe - Launch program",
- "type": "cppdbg",
- "request": "launch",
- "program": "${workspaceFolder}\\Debug\\output.exe",
- "args": [],
- "stopAtEntry": true,
- "cwd": "${workspaceFolder}",
- "environment": [],
- "externalConsole": false,
- "MIMode": "gdb",
- "miDebuggerPath": "C:\\MinGW\\bin\\gdb.exe",
- "setupCommands": [
- {
- "description": "Enable pretty-printing for gdb",
- "text": "-enable-pretty-printing",
- "ignoreFailures": true
- }
- ],
- "preLaunchTask": "g++.exe - Launch program"
- }
+ {
+ /* GDB must in be in the PATH environment */
+ "name": "(Windows) Launch",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${command:cmake.launchTargetPath}",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "${fileDirname}",
+ "environment": []
+ }
]
- }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 83aff2f..e696cf0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,11 @@
{
"files.associations": {
+ "lwevt_types.h": "c",
+ "lwevt_type.h": "c",
+ "lwevt.h": "c",
+ "string.h": "c",
+ "lwevt_opt.h": "c",
"lwjson.h": "c"
- }
+ },
+ "esbonio.sphinx.confDir": ""
}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 2e9fb60..b15064b 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -4,23 +4,12 @@
{
"type": "cppbuild",
"label": "Build project",
- "command": "C:\\MinGW\\bin\\gcc.exe",
- "args": [
- "-g",
- "${workspaceFolder}\\lwjson\\src\\lwjson\\*.c",
- "${workspaceFolder}\\dev\\VisualStudio\\main.c",
- "${workspaceFolder}\\test\\*.c",
- "-I${workspaceFolder}\\dev\\VisualStudio\\",
- "-I${workspaceFolder}\\lwjson\\src\\include\\",
- "-o",
- "${workspaceFolder}\\Debug\\output.exe"
- ],
+ "command": "cmake",
+ "args": ["--build", "${command:cmake.buildDirectory}", "-j", "8"],
"options": {
"cwd": "${workspaceFolder}"
},
- "problemMatcher": [
- "$gcc"
- ],
+ "problemMatcher": ["$gcc"],
"group": {
"kind": "build",
"isDefault": true
@@ -28,12 +17,57 @@
},
{
"type": "shell",
- "label": "Run built code",
- "command": "${workspaceFolder}\\Debug\\output.exe",
+ "label": "Re-build project",
+ "command": "cmake",
+ "args": ["--build", "${command:cmake.buildDirectory}", "--clean-first", "-v", "-j", "8"],
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "problemMatcher": ["$gcc"],
+ },
+ {
+ "type": "shell",
+ "label": "Clean project",
+ "command": "cmake",
+ "args": ["--build", "${command:cmake.buildDirectory}", "--target", "clean"],
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "problemMatcher": []
+ },
+ {
+ "type": "shell",
+ "label": "Run application",
+ "command": "${command:cmake.launchTargetPath}",
+ "args": [],
"problemMatcher": [],
- "dependsOn": [
- "Build project"
- ]
- }
+ },
+ {
+ "label": "Docs: Install python plugins from requirements.txt file",
+ "type": "shell",
+ "command": "python -m pip install -r requirements.txt",
+ "options": {
+ "cwd": "${workspaceFolder}/docs"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Docs: Generate html",
+ "type": "shell",
+ "command": ".\\make html",
+ "options": {
+ "cwd": "${workspaceFolder}/docs"
+ },
+ "problemMatcher": []
+ },
+ {
+ "label": "Docs: Clean build directory",
+ "type": "shell",
+ "command": ".\\make clean",
+ "options": {
+ "cwd": "${workspaceFolder}/docs"
+ },
+ "problemMatcher": []
+ },
]
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4041a17..ec0f1b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
## Develop
+## 1.6.0
+
+- Split CMakeLists.txt files between library and executable
+- Change license year to 2022
+- Fix GCC warning for incompatible comparison types
+- Update code style with astyle
+- Add support for stream parsing - first version
+- Add `.clang-format`
+- Add `lwjsonSTREAMDONE` return code when streamer well parsed some JSON and reached end of string
+- Add option to reset stream state machine
+
## 1.5.0
- Add string compare feature
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..02fa3a3
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,45 @@
+cmake_minimum_required(VERSION 3.22)
+
+# Setup project
+project(LwLibPROJECT)
+
+if(NOT PROJECT_IS_TOP_LEVEL)
+ add_subdirectory(lwjson)
+else()
+ # Set as executable
+ add_executable(${PROJECT_NAME})
+
+ # Add key executable block
+ target_sources(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_LIST_DIR}/dev/main.c
+ ${CMAKE_CURRENT_LIST_DIR}/test/test.c
+ ${CMAKE_CURRENT_LIST_DIR}/examples/example_minimal.c
+ ${CMAKE_CURRENT_LIST_DIR}/examples/example_traverse.c
+ ${CMAKE_CURRENT_LIST_DIR}/examples/example_stream.c
+ )
+
+ # Add key include paths
+ target_include_directories(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_LIST_DIR}/dev
+ )
+
+ # Compilation definition information
+ target_compile_definitions(${PROJECT_NAME} PUBLIC
+ WIN32
+ _DEBUG
+ CONSOLE
+ LWJSON_DEV
+ )
+
+ # Compiler options
+ target_compile_options(${PROJECT_NAME} PRIVATE
+ -Wall
+ -Wextra
+ -Wpedantic
+ )
+
+ # Add subdir with lwjson and link to project
+ add_subdirectory(lwjson)
+ target_link_libraries(${PROJECT_NAME} lwjson)
+ target_link_libraries(${PROJECT_NAME} lwjson_debug)
+endif()
\ No newline at end of file
diff --git a/CMakePresets.json b/CMakePresets.json
new file mode 100644
index 0000000..f99154c
--- /dev/null
+++ b/CMakePresets.json
@@ -0,0 +1,40 @@
+{
+ "version": 3,
+ "configurePresets": [
+ {
+ "name": "default",
+ "hidden": true,
+ "generator": "Ninja",
+ "binaryDir": "${sourceDir}/build/${presetName}",
+ "cacheVariables": {
+ "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
+ }
+ },
+ {
+ "name": "Win32-Debug",
+ "inherits": "default",
+ "toolchainFile": "${sourceDir}/cmake/i686-w64-mingw32-gcc.cmake",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Debug"
+ }
+ },
+ {
+ "name": "Win64-Debug",
+ "inherits": "default",
+ "toolchainFile": "${sourceDir}/cmake/x86_64-w64-mingw32-gcc.cmake",
+ "cacheVariables": {
+ "CMAKE_BUILD_TYPE": "Debug"
+ }
+ }
+ ],
+ "buildPresets": [
+ {
+ "name": "Win32-Debug",
+ "configurePreset": "Win32-Debug"
+ },
+ {
+ "name": "Win64-Debug",
+ "configurePreset": "Win64-Debug"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index aa60317..5625f63 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 Tilen MAJERLE
+Copyright (c) 2022 Tilen MAJERLE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index e968961..7fe1c98 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# Lightweight JSON text parser
-Library provides generic JSON text parser.
+Library provides generic JSON text parser, that is optimized for embedded systems.
+Supports `streaming` parsing or classic parsing with full JSON data available in one big linear memory.
+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
@@ -12,6 +14,7 @@ Library provides generic JSON text parser.
* No recursion during parse operation
* Re-entrant functions
* Zero-copy, no ``malloc`` or ``free`` functions used
+* Supports streaming parsing as secondary option
* Optional support for inline comments with `/* comment... */` syntax between any *blank* region of input string
* Advanced find algorithm for tokens
* Test coverage is available
@@ -28,4 +31,4 @@ Fresh contributions are always welcome. Simple instructions to proceed::
Alternatively you may:
1. Report a bug
-2. Ask for a feature request
\ No newline at end of file
+2. Ask for a feature request
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..404fec6
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,4 @@
+# TODO
+
+- Stream parser: split large strings to multiple calbacks
+- Stream parser: ignore comments (optional)
\ No newline at end of file
diff --git a/cmake/i686-w64-mingw32-gcc.cmake b/cmake/i686-w64-mingw32-gcc.cmake
new file mode 100644
index 0000000..334d580
--- /dev/null
+++ b/cmake/i686-w64-mingw32-gcc.cmake
@@ -0,0 +1,7 @@
+set(CMAKE_SYSTEM_NAME Windows)
+
+# Some default GCC settings
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+
+set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
diff --git a/cmake/x86_64-w64-mingw32-gcc.cmake b/cmake/x86_64-w64-mingw32-gcc.cmake
new file mode 100644
index 0000000..1d82433
--- /dev/null
+++ b/cmake/x86_64-w64-mingw32-gcc.cmake
@@ -0,0 +1,7 @@
+set(CMAKE_SYSTEM_NAME Windows)
+
+# Some default GCC settings
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+
+set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
diff --git a/dev/VisualStudio/main.c b/dev/VisualStudio/main.c
deleted file mode 100644
index fc496b2..0000000
--- a/dev/VisualStudio/main.c
+++ /dev/null
@@ -1,83 +0,0 @@
-#include
-#include
-#include
-#include "windows.h"
-#include "lwjson/lwjson.h"
-
-static lwjson_token_t tokens[4096];
-static lwjson_t lwjson;
-
-extern void test_run(void);
-extern void example_minimal_run(void);
-extern void example_traverse_run(void);
-
-int
-main() {
- HANDLE f;
- DWORD file_size;
- size_t token_cnt = 0;
- char* json_text = NULL;
- const lwjson_token_t* tkn;
-
- test_run();
- //example_minimal_run();
- //example_traverse_run();
- return 0;
-
- printf("\n---\n");
- /* Init JSON */
- lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens));
-
- f = CreateFile(TEXT("..\\..\\test\\json\\custom.json"),
- GENERIC_READ, // open for reading
- 0, // do not share
- NULL, // no security
- OPEN_EXISTING, // existing file only
- FILE_ATTRIBUTE_NORMAL, // normal file
- NULL); // no attr. template
-
- if (f == INVALID_HANDLE_VALUE) {
- printf("Could not open file..\r\n");
- goto exit;
- }
- file_size = GetFileSize(f, NULL);
- if (file_size == INVALID_FILE_SIZE) {
- printf("Invalid file size..\r\n");
- goto exit;
- } else if (file_size == 0) {
- printf("File is empty..\r\n");
- goto exit;
- }
- json_text = calloc((size_t)(file_size + 1), sizeof(*json_text));
- if (json_text == NULL) {
- printf("Could not allocate memory..\r\n");
- goto exit;
- }
- if (ReadFile(f, json_text, file_size, NULL, NULL) == 0) {
- printf("Could not read full file..\r\n");
- goto exit;
- }
-
- /* Start parsing */
- if (lwjson_parse(&lwjson, json_text) != lwjsonOK) {
- printf("Could not parse input json\r\n");
- goto exit;
- }
-
- /* Dump result */
- lwjson_print_json(&lwjson);
-
- /* Find token if exists */
- if ((tkn = lwjson_find(&lwjson, "obj.obj2.key1")) != NULL) {
- printf("Found requested token path\r\n");
- lwjson_print_token(tkn);
- } else {
- printf("Could not find requested token path..\r\n");
- }
-exit:
- if (json_text != NULL) {
- free(json_text);
- json_text = NULL;
- }
- return 0;
-}
diff --git a/dev/VisualStudio/lwjson_dev.sln b/dev/lwjson_dev.sln
similarity index 100%
rename from dev/VisualStudio/lwjson_dev.sln
rename to dev/lwjson_dev.sln
diff --git a/dev/VisualStudio/lwjson_dev.vcxproj b/dev/lwjson_dev.vcxproj
similarity index 94%
rename from dev/VisualStudio/lwjson_dev.vcxproj
rename to dev/lwjson_dev.vcxproj
index 6c6711d..adbbd6e 100644
--- a/dev/VisualStudio/lwjson_dev.vcxproj
+++ b/dev/lwjson_dev.vcxproj
@@ -72,7 +72,7 @@
true
- ..\..\lwjson\src\include\;.;$(IncludePath)
+ ..\lwjson\src\include\;.;$(IncludePath)
true
@@ -143,11 +143,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/dev/VisualStudio/lwjson_dev.vcxproj.filters b/dev/lwjson_dev.vcxproj.filters
similarity index 83%
rename from dev/VisualStudio/lwjson_dev.vcxproj.filters
rename to dev/lwjson_dev.vcxproj.filters
index 10e9bd6..f5326a2 100644
--- a/dev/VisualStudio/lwjson_dev.vcxproj.filters
+++ b/dev/lwjson_dev.vcxproj.filters
@@ -24,19 +24,19 @@
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files
-
+
Source Files\Examples
-
+
Source Files\Examples
diff --git a/dev/VisualStudio/lwjson_opts.h b/dev/lwjson_opts.h
similarity index 96%
rename from dev/VisualStudio/lwjson_opts.h
rename to dev/lwjson_opts.h
index 2a27683..80fc50f 100644
--- a/dev/VisualStudio/lwjson_opts.h
+++ b/dev/lwjson_opts.h
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (c) 2020 Tilen MAJERLE
+ * Copyright (c) 2022 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,7 +29,7 @@
* This file is part of LwJSON - Lightweight JSON format parser.
*
* Author: Tilen MAJERLE
- * Version: v1.5.0
+ * Version: v1.6.0
*/
#ifndef LWJSON_HDR_OPTS_H
#define LWJSON_HDR_OPTS_H
diff --git a/dev/main.c b/dev/main.c
new file mode 100644
index 0000000..e293e6e
--- /dev/null
+++ b/dev/main.c
@@ -0,0 +1,335 @@
+#include
+#include
+#include
+#include "windows.h"
+#include "lwjson/lwjson.h"
+
+/* Classic parser */
+static lwjson_token_t tokens[4096];
+static lwjson_t lwjson;
+
+/* Stream parser */
+static lwjson_stream_parser_t stream_parser;
+
+extern void test_run(void);
+extern void example_minimal_run(void);
+extern void example_traverse_run(void);
+extern void example_stream_run(void);
+
+static void jsp_stream_callback(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type);
+
+int
+main() {
+ HANDLE f;
+ DWORD file_size;
+ size_t token_cnt = 0;
+ char* json_text = NULL;
+ const lwjson_token_t* tkn;
+
+ (void)token_cnt;
+#if 0
+ test_run();
+ example_minimal_run();
+ example_traverse_run();
+ example_stream_run();
+ return 0;
+#endif
+
+ printf("\n---\n");
+ /* Init JSON */
+ lwjson_init(&lwjson, tokens, LWJSON_ARRAYSIZE(tokens));
+
+ f = CreateFile(TEXT("test\\json\\custom_stream.json"),
+ GENERIC_READ, // open for reading
+ 0, // do not share
+ NULL, // no security
+ OPEN_EXISTING, // existing file only
+ FILE_ATTRIBUTE_NORMAL, // normal file
+ NULL); // no attr. template
+
+ if (f == INVALID_HANDLE_VALUE) {
+ printf("Could not open file..\r\n");
+ goto exit;
+ }
+ if ((file_size = GetFileSize(f, NULL)) == INVALID_FILE_SIZE) {
+ printf("Invalid file size..\r\n");
+ goto exit;
+ } else if (file_size == 0) {
+ printf("File is empty..\r\n");
+ goto exit;
+ }
+ if ((json_text = calloc((size_t)(file_size + 1), sizeof(*json_text))) == NULL) {
+ printf("Could not allocate memory..\r\n");
+ goto exit;
+ }
+ if (ReadFile(f, json_text, file_size, NULL, NULL) == 0) {
+ printf("Could not read full file..\r\n");
+ goto exit;
+ }
+
+ /* Now parse as a stream */
+ lwjson_stream_init(&stream_parser, jsp_stream_callback);
+ for (const char* str = json_text; str != NULL && *str != '\0'; ++str) {
+ lwjsonr_t res = lwjson_stream_parse(&stream_parser, *str);
+ if (res == lwjsonSTREAMWAITFIRSTCHAR) {
+ printf("Waiting valid JSON start\r\n");
+ } else if (res == lwjsonSTREAMINPROG) {
+ //printf("Stream in progress...\r\n");
+ } else if (res == lwjsonSTREAMDONE) {
+ printf("Stream is completed\r\n");
+ } else {
+ printf("Unknown error...\r\n");
+ break;
+ }
+ }
+ return 0;
+
+ /* Start parsing */
+ printf("Parsing JSON with full text\r\n");
+ if (lwjson_parse(&lwjson, json_text) != lwjsonOK) {
+ printf("Could not parse input JSON\r\n");
+ goto exit;
+ }
+ printf("Full JSON parsed\r\n");
+
+ /* Dump result */
+ lwjson_print_json(&lwjson);
+
+ /* Find token if exists */
+ if ((tkn = lwjson_find(&lwjson, "obj.obj2.key1")) != NULL) {
+ printf("Found requested token path\r\n");
+ lwjson_print_token(tkn);
+ } else {
+ printf("Could not find requested token path..\r\n");
+ }
+exit:
+ if (json_text != NULL) {
+ free(json_text);
+ json_text = NULL;
+ }
+ return 0;
+}
+
+/**
+ * \brief Stream calback demo
+ * \param jsp: JSON Stream parser object
+ * \param type: Primitive type
+ */
+static void
+jsp_stream_callback(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type) {
+ (void)type;
+
+ /*
+ * Very long string demo has been added to the weather object,
+ * in order to test string field that is longer than maximal allowed length.
+ *
+ * This will generate multiple calls for same key.
+ *
+ * For test, we use base64_encoded string - but it can be anything
+ */
+ if (jsp->stack_pos == 2 && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY) {
+ if (strcmp(jsp->stack[1].meta.name, "base64_str") == 0) {
+ printf("Base64_string. Block len: %d, total len: %d, is_last: %d, data: %.*s\r\n",
+ (int)jsp->data.str.buff_pos, (int)jsp->data.str.buff_total_pos, (int)jsp->data.str.is_last,
+ (int)jsp->data.str.buff_pos, jsp->data.str.buff);
+ }
+ }
+
+ /*
+ * Take care of key-value pairs immediately at the start of object
+ *
+ * Values such as latitude and longitude are parsed here
+ */
+ if (jsp->stack_pos == 2 && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY) {
+
+ if (strcmp(jsp->stack[1].meta.name, "lat") == 0) {
+ printf("Latitude weather: %f\r\n", strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[1].meta.name, "lon") == 0) {
+ printf("Longitude weather: %f\r\n", strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[1].meta.name, "timezone_offset") == 0) {
+ printf("Timezone offset: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[1].meta.name, "timezone") == 0) {
+ printf("Timezone: %s\r\n", jsp->data.prim.buff);
+ }
+ }
+
+ /*
+ * Handle current object - single object with multiple key-value pairs
+ */
+ if (jsp->stack_pos >= 4 && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY && jsp->stack[2].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[3].type == LWJSON_STREAM_TYPE_KEY) {
+ /* Check for current weather */
+ if (strcmp(jsp->stack[1].meta.name, "current") == 0) {
+ /*
+ * Process the "weather part"
+ */
+ if (jsp->stack_pos >= 7 && jsp->stack[4].type == LWJSON_STREAM_TYPE_ARRAY
+ && jsp->stack[5].type == LWJSON_STREAM_TYPE_OBJECT && jsp->stack[6].type == LWJSON_STREAM_TYPE_KEY
+ && strcmp(jsp->stack[3].meta.name, "weather") == 0) {
+
+ if (strcmp(jsp->stack[6].meta.name, "id") == 0) {
+ printf("Current weather %d id: %u\r\n", (int)jsp->stack[4].meta.index,
+ (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[6].meta.name, "main") == 0) {
+ printf("Current weather %d main: %s\r\n", (int)jsp->stack[4].meta.index, jsp->data.str.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "description") == 0) {
+ printf("Current weather %d description: %s\r\n", (int)jsp->stack[4].meta.index, jsp->data.str.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "icon") == 0) {
+ printf("Current weather %d icon: %s\r\n", (int)jsp->stack[4].meta.index, jsp->data.str.buff);
+ }
+ } else if (strcmp(jsp->stack[3].meta.name, "dt") == 0) {
+ printf("Current weather dt: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "sunrise") == 0) {
+ printf("Current weather sunrise: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "sunset") == 0) {
+ printf("Current weather sunset: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "temp") == 0) {
+ printf("Current weather temp: %f\r\n", strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[3].meta.name, "feels_like") == 0) {
+ printf("Current weather feels_like: %f\r\n", strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[3].meta.name, "pressure") == 0) {
+ printf("Current weather pressure: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "humidity") == 0) {
+ printf("Current weather humidity: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "uvi") == 0) {
+ printf("Current weather uvi: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "clouds") == 0) {
+ printf("Current weather clouds: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "visibility") == 0) {
+ printf("Current weather visibility: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[3].meta.name, "wind_speed") == 0) {
+ printf("Current weather wind_speed: %f\r\n", strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[3].meta.name, "wind_deg") == 0) {
+ printf("Current weather wind_deg: %u\r\n", (unsigned)atoll(jsp->data.prim.buff));
+ }
+ }
+ }
+
+ /*
+ * Process the various object in specific JSON order
+ */
+ if (jsp->stack_pos >= 5
+ /* First build the order... */
+ && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY
+ && jsp->stack[2].type == LWJSON_STREAM_TYPE_ARRAY && jsp->stack[3].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[4].type == LWJSON_STREAM_TYPE_KEY) {
+
+ /*
+ * Handle daily forecast objects
+ */
+ if (strcmp(jsp->stack[1].meta.name, "daily") == 0) {
+ /* Analyze objects for temp and feels like object */
+ if (jsp->stack_pos >= 7 && jsp->stack[5].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[6].type == LWJSON_STREAM_TYPE_KEY) {
+ if (strcmp(jsp->stack[4].meta.name, "temp") == 0) {
+ /* Parsing of temp object */
+ if (strcmp(jsp->stack[6].meta.name, "min") == 0) {
+ printf("Day %2d temp min: %s\r\n", (int)jsp->stack[2].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "max") == 0) {
+ printf("Day %2d temp max: %s\r\n", (int)jsp->stack[2].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "day") == 0) {
+ printf("Day %2d temp day: %s\r\n", (int)jsp->stack[2].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "night") == 0) {
+ printf("Day %2d temp night: %s\r\n", (int)jsp->stack[2].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "eve") == 0) {
+ printf("Day %2d temp eve: %s\r\n", (int)jsp->stack[2].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "morn") == 0) {
+ printf("Day %2d temp morn: %s\r\n", (int)jsp->stack[2].meta.index, jsp->data.prim.buff);
+ }
+ } else if (strcmp(jsp->stack[4].meta.name, "feels_like") == 0) {
+ /* Parsing of feels-like objects */
+ if (strcmp(jsp->stack[6].meta.name, "day") == 0) {
+ printf("Day %2d temp_feels_like day: %s\r\n", (int)jsp->stack[2].meta.index,
+ jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "night") == 0) {
+ printf("Day %2d temp_feels_like night: %s\r\n", (int)jsp->stack[2].meta.index,
+ jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "eve") == 0) {
+ printf("Day %2d temp_feels_like eve: %s\r\n", (int)jsp->stack[2].meta.index,
+ jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[6].meta.name, "morn") == 0) {
+ printf("Day %2d temp_feels_like morn: %s\r\n", (int)jsp->stack[2].meta.index,
+ jsp->data.prim.buff);
+ }
+ }
+ } else if (jsp->stack_pos >= 8 && jsp->stack[5].type == LWJSON_STREAM_TYPE_ARRAY
+ && jsp->stack[6].type == LWJSON_STREAM_TYPE_OBJECT
+ && jsp->stack[7].type == LWJSON_STREAM_TYPE_KEY
+ && strcmp(jsp->stack[4].meta.name, "weather") == 0) {
+
+ if (strcmp(jsp->stack[7].meta.name, "id") == 0) {
+ printf("Day %2d weather %2d id: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[7].meta.name, "main") == 0) {
+ printf("Day %2d weather %2d main: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.str.buff);
+ } else if (strcmp(jsp->stack[7].meta.name, "description") == 0) {
+ printf("Day %2d weather %2d description: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.str.buff);
+ } else if (strcmp(jsp->stack[7].meta.name, "icon") == 0) {
+ printf("Day %2d weather %2d icon: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.str.buff);
+ }
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "dt") == 0) {
+ printf("Day %2d dt: %u\r\n", (int)jsp->stack[2].meta.index, (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "pressure") == 0) {
+ printf("Day %2d pressure: %u\r\n", (int)jsp->stack[2].meta.index, (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "humidity") == 0) {
+ printf("Day %2d humidity: %u\r\n", (int)jsp->stack[2].meta.index, (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "clouds") == 0) {
+ printf("Day %2d clouds: %u\r\n", (int)jsp->stack[2].meta.index, (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "snow") == 0) {
+ printf("Day %2d snow: %f\r\n", (int)jsp->stack[2].meta.index, strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "rain") == 0) {
+ printf("Day %2d rain: %f\r\n", (int)jsp->stack[2].meta.index, strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[jsp->stack_pos - 1].meta.name, "uvi") == 0) {
+ printf("Day %2d uvi: %f\r\n", (int)jsp->stack[2].meta.index, strtod(jsp->data.prim.buff, NULL));
+ }
+ } else if (strcmp(jsp->stack[1].meta.name, "hourly") == 0) {
+ if (jsp->stack_pos >= 8 && jsp->stack[5].type == LWJSON_STREAM_TYPE_ARRAY
+ && jsp->stack[6].type == LWJSON_STREAM_TYPE_OBJECT && jsp->stack[7].type == LWJSON_STREAM_TYPE_KEY
+ && strcmp(jsp->stack[4].meta.name, "weather") == 0) {
+
+ if (strcmp(jsp->stack[7].meta.name, "id") == 0) {
+ printf("Hour %2d weather %2d id: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.prim.buff);
+ } else if (strcmp(jsp->stack[7].meta.name, "main") == 0) {
+ printf("Hour %2d weather %2d main: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.str.buff);
+ } else if (strcmp(jsp->stack[7].meta.name, "description") == 0) {
+ printf("Hour %2d weather %2d description: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.str.buff);
+ } else if (strcmp(jsp->stack[7].meta.name, "icon") == 0) {
+ printf("Hour %2d weather %2d icon: %s\r\n", (int)jsp->stack[2].meta.index,
+ (int)jsp->stack[5].meta.index, jsp->data.str.buff);
+ }
+ } else if (strcmp(jsp->stack[4].meta.name, "dt") == 0) {
+ printf("Hour %2d forecast for dt: %u\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[4].meta.name, "temp") == 0) {
+ printf("Hour %2d forecast for temp: %f\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[4].meta.name, "feels_like") == 0) {
+ printf("Hour %2d forecast for feels_like: %f\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ strtod(jsp->data.prim.buff, NULL));
+ } else if (strcmp(jsp->stack[4].meta.name, "pressure") == 0) {
+ printf("Hour %2d forecast for pressure: %d\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ (int)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[4].meta.name, "humidity") == 0) {
+ printf("Hour %2d forecast for humidity: %d\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ (int)atoll(jsp->data.prim.buff));
+ }
+ } else if (strcmp(jsp->stack[1].meta.name, "minutely") == 0) {
+ if (strcmp(jsp->stack[4].meta.name, "dt") == 0) {
+ printf("Minute %2d forecast for dt: %u\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ (unsigned)atoll(jsp->data.prim.buff));
+ } else if (strcmp(jsp->stack[4].meta.name, "precipitation") == 0) {
+ printf("Minute %2d forecast for precipitation: %u\r\n", (int)jsp->stack[jsp->stack_pos - 3].meta.index,
+ (unsigned)atoll(jsp->data.prim.buff));
+ }
+ }
+ }
+}
diff --git a/docs/api-reference/lwjson_opt.rst b/docs/api-reference/lwjson_opt.rst
index 4bed334..754f4dc 100644
--- a/docs/api-reference/lwjson_opt.rst
+++ b/docs/api-reference/lwjson_opt.rst
@@ -9,4 +9,5 @@ When any of the settings shall be modified, it shall be done in dedicated applic
.. note::
Check :ref:`getting_started` for guidelines on how to create and use configuration file.
-.. doxygengroup:: LWJSON_OPT
\ No newline at end of file
+.. doxygengroup:: LWJSON_OPT
+ :inner:
\ No newline at end of file
diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst
new file mode 100644
index 0000000..51b89b4
--- /dev/null
+++ b/docs/changelog/index.rst
@@ -0,0 +1,6 @@
+.. _changelof:
+
+Changelog
+=========
+
+.. literalinclude:: ../../CHANGELOG.md
diff --git a/docs/conf.py b/docs/conf.py
index 6216731..d015a01 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -23,15 +23,25 @@
# -- Project information -----------------------------------------------------
project = 'LwJSON'
-copyright = '2020, Tilen MAJERLE'
+copyright = '2022, Tilen MAJERLE'
author = 'Tilen MAJERLE'
# Try to get branch at which this is running
# and try to determine which version to display in sphinx
-# Version is using git tag if on master or "latest-develop" if on develop branch
+# Version is using git tag if on master/main or "latest-develop" if on develop branch
version = ''
git_branch = ''
+def cmd_exec_print(t):
+ print("cmd > ", t, "\n", os.popen(t).read().strip(), "\n")
+
+# Print demo data here
+cmd_exec_print('git branch')
+cmd_exec_print('git describe')
+cmd_exec_print('git describe --tags')
+cmd_exec_print('git describe --tags --abbrev=0')
+cmd_exec_print('git describe --tags --abbrev=1')
+
# Get current branch
res = os.popen('git branch').read().strip()
for line in res.split("\n"):
@@ -41,17 +51,18 @@
# Decision for display version
git_branch = git_branch.replace('(HEAD detached at ', '').replace(')', '')
if git_branch.find('master') >= 0 or git_branch.find('main') >= 0:
- version = os.popen('git describe --tags --abbrev=0').read().strip()
- if version == '':
- version = 'v0.0.0'
-elif git_branch.find('develop') != -1 and not (git_branch.find('develop-') >= 0 or git_branch.find('develop/') >= 0):
+ #version = os.popen('git describe --tags --abbrev=0').read().strip()
+ version = 'latest-stable'
+elif git_branch.find('develop-') >= 0 or git_branch.find('develop/') >= 0:
+ version = 'branch-' + git_branch
+elif git_branch == 'develop' or git_branch == 'origin/develop':
version = 'latest-develop'
else:
- version = 'branch-' + git_branch
+ version = os.popen('git describe --tags --abbrev=0').read().strip()
# For debugging purpose only
print("GIT BRANCH: " + git_branch)
-print("GIT VERSION: " + version)
+print("PROJ VERSION: " + version)
# -- General configuration ---------------------------------------------------
@@ -115,20 +126,19 @@
html_css_files = [
'css/common.css',
'css/custom.css',
+ 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css',
]
html_js_files = [
- 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css'
+ ''
]
+# Master index file
master_doc = 'index'
-#
-# Breathe configuration
-#
-#
-#
+# --- Breathe configuration -----------------------------------------------------
breathe_projects = {
"lwjson": "_build/xml/"
}
breathe_default_project = "lwjson"
-breathe_default_members = ('members', 'undoc-members')
\ No newline at end of file
+breathe_default_members = ('members', 'undoc-members')
+breathe_show_enumvalue_initializer = True
\ No newline at end of file
diff --git a/docs/examples/stream.json b/docs/examples/stream.json
new file mode 100644
index 0000000..fe4f850
--- /dev/null
+++ b/docs/examples/stream.json
@@ -0,0 +1,13 @@
+{
+ "test": "abc",
+ "array": [
+ "123",
+ "def",
+ "ghi"
+ ],
+ "array_in_array": [
+ ["1", "2", "3"],
+ ["4", "5", "6"],
+ ["7", "8", "9"]
+ ]
+}
\ No newline at end of file
diff --git a/docs/get-started/index.rst b/docs/get-started/index.rst
index f38dc9e..11f14f6 100644
--- a/docs/get-started/index.rst
+++ b/docs/get-started/index.rst
@@ -13,10 +13,10 @@ Download library
Library is primarly hosted on `Github `_.
-You can get it with:
+You can get it by:
* Downloading latest release from `releases area `_ on Github
-* Cloning ``master`` branch for latest stable version
+* Cloning ``main`` branch for latest stable version
* Cloning ``develop`` branch for latest development
Download from releases
@@ -34,21 +34,21 @@ This is used when you do not have yet local copy on your machine.
* Make sure ``git`` is installed.
* Open console and navigate to path in the system to clone repository to. Use command ``cd your_path``
-* Clone repository with one of available ``3`` options
+* Clone repository with one of available options below
* Run ``git clone --recurse-submodules https://github.com/MaJerle/lwjson`` command to clone entire repository, including submodules
* Run ``git clone --recurse-submodules --branch develop https://github.com/MaJerle/lwjson`` to clone `development` branch, including submodules
- * Run ``git clone --recurse-submodules --branch master https://github.com/MaJerle/lwjson`` to clone `latest stable` branch, including submodules
+ * Run ``git clone --recurse-submodules --branch main https://github.com/MaJerle/lwjson`` to clone `latest stable` branch, including submodules
* Navigate to ``examples`` directory and run favourite example
Update cloned to latest version
"""""""""""""""""""""""""""""""
-* Open console and navigate to path in the system where your resources repository is. Use command ``cd your_path``
-* Run ``git pull origin master --recurse-submodules`` command to pull latest changes and to fetch latest changes from submodules on ``master`` branch
-* Run ``git pull origin develop --recurse-submodules`` command to pull latest changes and to fetch latest changes from submodules on ``develop`` branch
-* Run ``git submodule foreach git pull origin master`` to update & merge all submodules
+* Open console and navigate to path in the system where your repository is located. Use command ``cd your_path``
+* Run ``git pull origin main`` command to get latest changes on ``main`` branch
+* Run ``git pull origin develop`` command to get latest changes on ``develop`` branch
+* Run ``git submodule update --init --remote`` to update submodules to latest version
.. note::
This is preferred option to use when you want to evaluate library and run prepared examples.
diff --git a/docs/index.rst b/docs/index.rst
index 2d9cc78..8f14ebf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -22,6 +22,7 @@ Features
* No recursion during parse operation
* Re-entrant functions
* Zero-copy, no ``malloc`` or ``free`` functions used
+* Supports streaming parsing as secondary option
* Optional support for inline comments with `/* comment... */` syntax between any *blank* region of input string
* Advanced find algorithm for tokens
* Tests coverage is available
@@ -65,8 +66,30 @@ Table of contents
.. toctree::
:maxdepth: 2
+ :caption: Contents
self
get-started/index
user-manual/index
api-reference/index
+ changelog/index
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Other projects
+ :hidden:
+
+ LwBTN - Button manager
+ LwDTC - DateTimeCron
+ LwESP - ESP-AT library
+ LwEVT - Event manager
+ LwGPS - GPS NMEA parser
+ LwGSM - GSM-AT library
+ LwJSON - JSON parser
+ LwMEM - Memory manager
+ LwOW - OneWire with UART
+ LwPKT - Packet protocol
+ LwPRINTF - Printf
+ LwRB - Ring buffer
+ LwSHELL - Shell
+ LwUTIL - Utility functions
diff --git a/docs/requirements.txt b/docs/requirements.txt
index eb5e0fd..834b1bb 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -2,7 +2,7 @@ breathe>=4.9.1
colorama
docutils==0.16
sphinx>=3.5.1
-sphinx_rtd_theme
+sphinx_rtd_theme>=1.0.0
sphinx-tabs
sphinxcontrib-svg2pdfconverter
sphinx-sitemap
diff --git a/docs/static/dark-light/checked.svg b/docs/static/dark-light/checked.svg
new file mode 100644
index 0000000..a78af82
--- /dev/null
+++ b/docs/static/dark-light/checked.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/static/dark-light/common-dark-light.css b/docs/static/dark-light/common-dark-light.css
new file mode 100644
index 0000000..9a2dc1d
--- /dev/null
+++ b/docs/static/dark-light/common-dark-light.css
@@ -0,0 +1,143 @@
+/**
+ * @license
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+:root {
+ --heading-color: red;
+ --duration: 0.5s;
+ --timing: ease;
+}
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ transition:
+ color var(--duration) var(--timing),
+ background-color var(--duration) var(--timing);
+ font-family: sans-serif;
+ font-size: 12pt;
+ background-color: var(--background-color);
+ color: var(--text-color);
+ display: flex;
+ justify-content: center;
+}
+
+main {
+ margin: 1rem;
+ max-width: 30rem;
+ position: relative;
+}
+
+h1 {
+ color: var(--heading-color);
+ text-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color);
+ transition: text-shadow var(--duration) var(--timing);
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+ transition: filter var(--duration) var(--timing);
+}
+
+p {
+ line-height: 1.5;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ hyphens: auto;
+}
+
+fieldset {
+ border: solid 0.1rem;
+ box-shadow: 0.1rem 0.1rem 0.1rem var(--shadow-color);
+ transition: box-shadow var(--duration) var(--timing);
+}
+
+div {
+ padding: 0.5rem;
+}
+
+aside {
+ position: absolute;
+ right: 0;
+ padding: 0.5rem;
+}
+
+aside:nth-of-type(1) {
+ top: 0;
+}
+
+aside:nth-of-type(2) {
+ top: 3rem;
+}
+
+aside:nth-of-type(3) {
+ top: 7rem;
+}
+
+aside:nth-of-type(4) {
+ top: 12rem;
+}
+
+#content select,
+#content button,
+#content input[type="text"],
+#content input[type="search"] {
+ width: 15rem;
+}
+
+dark-mode-toggle {
+ --dark-mode-toggle-remember-icon-checked: url("checked.svg");
+ --dark-mode-toggle-remember-icon-unchecked: url("unchecked.svg");
+ --dark-mode-toggle-remember-font: 0.75rem "Helvetica";
+ --dark-mode-toggle-legend-font: bold 0.85rem "Helvetica";
+ --dark-mode-toggle-label-font: 0.85rem "Helvetica";
+ --dark-mode-toggle-color: var(--text-color);
+ --dark-mode-toggle-background-color: none;
+
+ margin-bottom: 1.5rem;
+}
+
+#dark-mode-toggle-1 {
+ --dark-mode-toggle-dark-icon: url("sun.png");
+ --dark-mode-toggle-light-icon: url("moon.png");
+}
+
+#dark-mode-toggle-2 {
+ --dark-mode-toggle-dark-icon: url("sun.svg");
+ --dark-mode-toggle-light-icon: url("moon.svg");
+ --dark-mode-toggle-icon-size: 2rem;
+ --dark-mode-toggle-icon-filter: invert(100%);
+}
+
+#dark-mode-toggle-3,
+#dark-mode-toggle-4 {
+ --dark-mode-toggle-dark-icon: url("moon.png");
+ --dark-mode-toggle-light-icon: url("sun.png");
+}
+
+#dark-mode-toggle-3 {
+ --dark-mode-toggle-remember-filter: invert(100%);
+}
+
+#dark-mode-toggle-4 {
+ --dark-mode-toggle-active-mode-background-color: var(--accent-color);
+ --dark-mode-toggle-remember-filter: invert(100%);
+}
diff --git a/docs/static/dark-light/dark-mode-toggle.mjs b/docs/static/dark-light/dark-mode-toggle.mjs
new file mode 100644
index 0000000..da22262
--- /dev/null
+++ b/docs/static/dark-light/dark-mode-toggle.mjs
@@ -0,0 +1,329 @@
+/**
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// @license © 2019 Google LLC. Licensed under the Apache License, Version 2.0.
+const doc = document;
+const store = localStorage;
+const PREFERS_COLOR_SCHEME = 'prefers-color-scheme';
+const MEDIA = 'media';
+const LIGHT = 'light';
+const DARK = 'dark';
+const MQ_DARK = `(${PREFERS_COLOR_SCHEME}:${DARK})`;
+const MQ_LIGHT = `(${PREFERS_COLOR_SCHEME}:${LIGHT})`;
+const LINK_REL_STYLESHEET = 'link[rel=stylesheet]';
+const REMEMBER = 'remember';
+const LEGEND = 'legend';
+const TOGGLE = 'toggle';
+const SWITCH = 'switch';
+const APPEARANCE = 'appearance';
+const PERMANENT = 'permanent';
+const MODE = 'mode';
+const COLOR_SCHEME_CHANGE = 'colorschemechange';
+const PERMANENT_COLOR_SCHEME = 'permanentcolorscheme';
+const ALL = 'all';
+const NOT_ALL = 'not all';
+const NAME = 'dark-mode-toggle';
+const DEFAULT_URL = 'https://googlechromelabs.github.io/dark-mode-toggle/demo/';
+
+// See https://html.spec.whatwg.org/multipage/common-dom-interfaces.html ↵
+// #reflecting-content-attributes-in-idl-attributes.
+const installStringReflection = (obj, attrName, propName = attrName) => {
+ Object.defineProperty(obj, propName, {
+ enumerable: true,
+ get() {
+ const value = this.getAttribute(attrName);
+ return value === null ? '' : value;
+ },
+ set(v) {
+ this.setAttribute(attrName, v);
+ },
+ });
+};
+
+const installBoolReflection = (obj, attrName, propName = attrName) => {
+ Object.defineProperty(obj, propName, {
+ enumerable: true,
+ get() {
+ return this.hasAttribute(attrName);
+ },
+ set(v) {
+ if (v) {
+ this.setAttribute(attrName, '');
+ } else {
+ this.removeAttribute(attrName);
+ }
+ },
+ });
+};
+
+const template = doc.createElement('template');
+// ⚠️ Note: this is a minified version of `src/template-contents.tpl`.
+// Compress the CSS with https://cssminifier.com/, then paste it here.
+// eslint-disable-next-line max-len
+template.innerHTML = ``;
+
+export class DarkModeToggle extends HTMLElement {
+ static get observedAttributes() {
+ return [MODE, APPEARANCE, PERMANENT, LEGEND, LIGHT, DARK, REMEMBER];
+ }
+
+ constructor() {
+ super();
+
+ installStringReflection(this, MODE);
+ installStringReflection(this, APPEARANCE);
+ installStringReflection(this, LEGEND);
+ installStringReflection(this, LIGHT);
+ installStringReflection(this, DARK);
+ installStringReflection(this, REMEMBER);
+
+ installBoolReflection(this, PERMANENT);
+
+ this._darkCSS = null;
+ this._lightCSS = null;
+
+ doc.addEventListener(COLOR_SCHEME_CHANGE, (event) => {
+ this.mode = event.detail.colorScheme;
+ this._updateRadios();
+ this._updateCheckbox();
+ });
+
+ doc.addEventListener(PERMANENT_COLOR_SCHEME, (event) => {
+ this.permanent = event.detail.permanent;
+ this._permanentCheckbox.checked = this.permanent;
+ });
+
+ this._initializeDOM();
+ }
+
+ _initializeDOM() {
+ const shadowRoot = this.attachShadow({mode: 'open'});
+ shadowRoot.appendChild(template.content.cloneNode(true));
+
+ // We need to support `media="(prefers-color-scheme: dark)"` (with space)
+ // and `media="(prefers-color-scheme:dark)"` (without space)
+ this._darkCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${DARK}"]`);
+ this._lightCSS = doc.querySelectorAll(`${LINK_REL_STYLESHEET}[${MEDIA}*=${PREFERS_COLOR_SCHEME}][${MEDIA}*="${LIGHT}"]`);
+
+ // Get DOM references.
+ this._lightRadio = shadowRoot.querySelector('[part=lightRadio]');
+ this._lightLabel = shadowRoot.querySelector('[part=lightLabel]');
+ this._darkRadio = shadowRoot.querySelector('[part=darkRadio]');
+ this._darkLabel = shadowRoot.querySelector('[part=darkLabel]');
+ this._darkCheckbox = shadowRoot.querySelector('[part=toggleCheckbox]');
+ this._checkboxLabel = shadowRoot.querySelector('[part=toggleLabel]');
+ this._legendLabel = shadowRoot.querySelector('legend');
+ this._permanentAside = shadowRoot.querySelector('aside');
+ this._permanentCheckbox =
+ shadowRoot.querySelector('[part=permanentCheckbox]');
+ this._permanentLabel = shadowRoot.querySelector('[part=permanentLabel]');
+
+ // Does the browser support native `prefers-color-scheme`?
+ const hasNativePrefersColorScheme =
+ matchMedia(MQ_DARK).media !== NOT_ALL;
+ // Listen to `prefers-color-scheme` changes.
+ if (hasNativePrefersColorScheme) {
+ matchMedia(MQ_DARK).addListener(({matches}) => {
+ this.mode = matches ? DARK : LIGHT;
+ this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode});
+ });
+ }
+ // Set initial state, giving preference to a remembered value, then the
+ // native value (if supported), and eventually defaulting to a light
+ // experience.
+ const rememberedValue = store.getItem(NAME);
+ if (rememberedValue && [DARK, LIGHT].includes(rememberedValue)) {
+ this.mode = rememberedValue;
+ this._permanentCheckbox.checked = true;
+ this.permanent = true;
+ } else if (hasNativePrefersColorScheme) {
+ this.mode = matchMedia(MQ_LIGHT).matches ? LIGHT : DARK;
+ }
+ if (!this.mode) {
+ this.mode = LIGHT;
+ }
+ if (this.permanent && !rememberedValue) {
+ store.setItem(NAME, this.mode);
+ }
+
+ // Default to toggle appearance.
+ if (!this.appearance) {
+ this.appearance = TOGGLE;
+ }
+
+ // Update the appearance to either of toggle or switch.
+ this._updateAppearance();
+
+ // Update the radios
+ this._updateRadios();
+
+ // Make the checkbox reflect the state of the radios
+ this._updateCheckbox();
+
+ // Synchronize the behavior of the radio and the checkbox.
+ [this._lightRadio, this._darkRadio].forEach((input) => {
+ input.addEventListener('change', () => {
+ this.mode = this._lightRadio.checked ? LIGHT : DARK;
+ this._updateCheckbox();
+ this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode});
+ });
+ });
+ this._darkCheckbox.addEventListener('change', () => {
+ this.mode = this._darkCheckbox.checked ? DARK : LIGHT;
+ this._updateRadios();
+ this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode});
+ });
+
+ // Make remembering the last mode optional
+ this._permanentCheckbox.addEventListener('change', () => {
+ this.permanent = this._permanentCheckbox.checked;
+ this._dispatchEvent(PERMANENT_COLOR_SCHEME, {
+ permanent: this.permanent,
+ });
+ });
+
+ // Finally update the mode and let the world know what's going on
+ this._updateMode();
+ this._dispatchEvent(COLOR_SCHEME_CHANGE, {colorScheme: this.mode});
+ this._dispatchEvent(PERMANENT_COLOR_SCHEME, {
+ permanent: this.permanent,
+ });
+ }
+
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (name === MODE) {
+ if (![LIGHT, DARK].includes(newValue)) {
+ throw new RangeError(`Allowed values: "${LIGHT}" and "${DARK}".`);
+ }
+ // Only show the dialog programmatically on devices not capable of hover
+ // and only if there is a label
+ if (matchMedia('(hover:none)').matches && this.remember) {
+ this._showPermanentAside();
+ }
+ if (this.permanent) {
+ store.setItem(NAME, this.mode);
+ }
+ this._updateRadios();
+ this._updateCheckbox();
+ this._updateMode();
+ } else if (name === APPEARANCE) {
+ if (![TOGGLE, SWITCH].includes(newValue)) {
+ throw new RangeError(`Allowed values: "${TOGGLE}" and "${SWITCH}".`);
+ }
+ this._updateAppearance();
+ } else if (name === PERMANENT) {
+ if (this.permanent) {
+ store.setItem(NAME, this.mode);
+ } else {
+ store.removeItem(NAME);
+ }
+ this._permanentCheckbox.checked = this.permanent;
+ } else if (name === LEGEND) {
+ this._legendLabel.textContent = newValue;
+ } else if (name === REMEMBER) {
+ this._permanentLabel.textContent = newValue;
+ } else if (name === LIGHT) {
+ this._lightLabel.textContent = newValue;
+ if (this.mode === LIGHT) {
+ this._checkboxLabel.textContent = newValue;
+ }
+ } else if (name === DARK) {
+ this._darkLabel.textContent = newValue;
+ if (this.mode === DARK) {
+ this._checkboxLabel.textContent = newValue;
+ }
+ }
+ }
+
+ _dispatchEvent(type, value) {
+ this.dispatchEvent(new CustomEvent(type, {
+ bubbles: true,
+ composed: true,
+ detail: value,
+ }));
+ }
+
+ _updateAppearance() {
+ // Hide or show the light-related affordances dependent on the appearance,
+ // which can be "switch" or "toggle".
+ const appearAsToggle = this.appearance === TOGGLE;
+ this._lightRadio.hidden = appearAsToggle;
+ this._lightLabel.hidden = appearAsToggle;
+ this._darkRadio.hidden = appearAsToggle;
+ this._darkLabel.hidden = appearAsToggle;
+ this._darkCheckbox.hidden = !appearAsToggle;
+ this._checkboxLabel.hidden = !appearAsToggle;
+ }
+
+ _updateRadios() {
+ if (this.mode === LIGHT) {
+ this._lightRadio.checked = true;
+ } else {
+ this._darkRadio.checked = true;
+ }
+ }
+
+ _updateCheckbox() {
+ if (this.mode === LIGHT) {
+ this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`,
+ `var(--${NAME}-light-icon,url("${DEFAULT_URL}moon.png"))`);
+ this._checkboxLabel.textContent = this.light;
+ if (!this.light) {
+ this._checkboxLabel.ariaLabel = DARK;
+ }
+ this._darkCheckbox.checked = false;
+ } else {
+ this._checkboxLabel.style.setProperty(`--${NAME}-checkbox-icon`,
+ `var(--${NAME}-dark-icon,url("${DEFAULT_URL}sun.png"))`);
+ this._checkboxLabel.textContent = this.dark;
+ if (!this.dark) {
+ this._checkboxLabel.ariaLabel = LIGHT;
+ }
+ this._darkCheckbox.checked = true;
+ }
+ }
+
+ _updateMode() {
+ if (this.mode === LIGHT) {
+ this._lightCSS.forEach((link) => {
+ link.media = ALL;
+ link.disabled = false;
+ });
+ this._darkCSS.forEach((link) => {
+ link.media = NOT_ALL;
+ link.disabled = true;
+ });
+ } else {
+ this._darkCSS.forEach((link) => {
+ link.media = ALL;
+ link.disabled = false;
+ });
+ this._lightCSS.forEach((link) => {
+ link.media = NOT_ALL;
+ link.disabled = true;
+ });
+ }
+ }
+
+ _showPermanentAside() {
+ this._permanentAside.style.visibility = 'visible';
+ setTimeout(() => {
+ this._permanentAside.style.visibility = 'hidden';
+ }, 3000);
+ }
+}
+
+customElements.define(NAME, DarkModeToggle);
\ No newline at end of file
diff --git a/docs/static/dark-light/dark.css b/docs/static/dark-light/dark.css
new file mode 100644
index 0000000..6ed8cfb
--- /dev/null
+++ b/docs/static/dark-light/dark.css
@@ -0,0 +1,36 @@
+/**
+ * @license
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+:root {
+ color-scheme: dark; /* stylelint-disable-line property-no-unknown */
+
+ --background-color: rgb(15 15 15);
+ --text-color: rgb(240 240 240);
+ --shadow-color: rgb(240 240 240 / 50%);
+ --accent-color: rgb(0 0 240 / 50%);
+}
+
+img {
+ filter: grayscale(50%);
+}
+
+.icon {
+ filter: invert(100%);
+}
+
+a {
+ color: yellow;
+}
diff --git a/docs/static/dark-light/light.css b/docs/static/dark-light/light.css
new file mode 100644
index 0000000..f73cf7b
--- /dev/null
+++ b/docs/static/dark-light/light.css
@@ -0,0 +1,24 @@
+/**
+ * @license
+ * Copyright 2019 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+:root {
+ color-scheme: light; /* stylelint-disable-line property-no-unknown */
+
+ --background-color: rgb(240 240 240);
+ --text-color: rgb(15 15 15);
+ --shadow-color: rgb(15 15 15 / 50%);
+ --accent-color: rgb(240 0 0 / 50%);
+}
diff --git a/docs/static/dark-light/moon.png b/docs/static/dark-light/moon.png
new file mode 100644
index 0000000..0ad57d9
Binary files /dev/null and b/docs/static/dark-light/moon.png differ
diff --git a/docs/static/dark-light/moon.svg b/docs/static/dark-light/moon.svg
new file mode 100644
index 0000000..fad89a4
--- /dev/null
+++ b/docs/static/dark-light/moon.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/docs/static/dark-light/sun.png b/docs/static/dark-light/sun.png
new file mode 100644
index 0000000..40c9b36
Binary files /dev/null and b/docs/static/dark-light/sun.png differ
diff --git a/docs/static/dark-light/sun.svg b/docs/static/dark-light/sun.svg
new file mode 100644
index 0000000..0b18941
--- /dev/null
+++ b/docs/static/dark-light/sun.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/docs/static/dark-light/unchecked.svg b/docs/static/dark-light/unchecked.svg
new file mode 100644
index 0000000..6702330
--- /dev/null
+++ b/docs/static/dark-light/unchecked.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/user-manual/data-access.rst b/docs/user-manual/data-access.rst
index e3997a6..42dabe5 100644
--- a/docs/user-manual/data-access.rst
+++ b/docs/user-manual/data-access.rst
@@ -57,7 +57,7 @@ Let's consider following JSON as input:
"brand":"Range",
"year":2020,
"repainted":true
- }
+ }
]
}
diff --git a/docs/user-manual/how-it-works.rst b/docs/user-manual/how-it-works.rst
index caa7b45..3c5bf4a 100644
--- a/docs/user-manual/how-it-works.rst
+++ b/docs/user-manual/how-it-works.rst
@@ -3,11 +3,14 @@
How it works
============
-LwJSON fully complies with *RFC 4627* memo.
+LwJSON fully complies with *RFC 4627* memo and supports ``2`` types of parsing:
-LwJSON accepts input string formatted as JSON as per *RFC 4627*.
-Library parses each character and creates list of tokens that are
-understood by C language for easier further processing.
+* Parsing with full data available as single linear memory (primary option)
+* Stream parsing with partial available bytes at any given point of time - advanced state machine
+
+When full data are available, standard parsing is used with tokens,
+that contain references to start/stop indexes of the strings and other primitives and provide
+full device tree - sort of custom hash-map value.
When JSON is successfully parsed, there are several tokens used, one for each JSON data type.
Each token consists of:
@@ -19,11 +22,15 @@ Each token consists of:
As an example, JSON text ``{"mykey":"myvalue"}`` will be parsed into ``2`` tokens:
* First token is the opening bracket and has type *object* as it holds children tokens
-* Second token has name ``mykey``, its type is *string* and value is ``myvalue``
+* Second token has name ``mykey``, its type is *string* with value set as ``myvalue``
.. warning::
When JSON input string is parsed, create tokens use input string as a reference.
This means that until JSON parsed tokens are being used, original text must stay as-is.
+ Any modification of source JSON input may destroy references from the token tree and hence generate wrong output for the user
+
+.. tip::
+ See :ref:`stream` for implementation of streaming parser where full data do not need to be available at any given time.
.. toctree::
:maxdepth: 2
\ No newline at end of file
diff --git a/docs/user-manual/index.rst b/docs/user-manual/index.rst
index d9e14b1..d0e7eb4 100644
--- a/docs/user-manual/index.rst
+++ b/docs/user-manual/index.rst
@@ -8,4 +8,5 @@ User manual
how-it-works
token-design
- data-access
\ No newline at end of file
+ data-access
+ stream
\ No newline at end of file
diff --git a/docs/user-manual/stream.rst b/docs/user-manual/stream.rst
new file mode 100644
index 0000000..9789370
--- /dev/null
+++ b/docs/user-manual/stream.rst
@@ -0,0 +1,70 @@
+.. _stream:
+
+Stream parser
+=============
+
+Streaming parser implementation is alternative option versus standard tokenized one, in the sense that:
+
+* There is no need to have full JSON available at one time to have successful parsing
+* It can be utilized to parse very large JSON strings on very small systems with limited memory
+* It allows users to *take* from the stream only necessary parts and store them to local more system-friendly variable
+
+This type of parser does not utilize use of tokens, rather focuses on the callback function,
+where user is in charge to manually understand token structure and get useful data from it.
+
+Stream parser introduces *stack* mechanism instead - to keep the track of depthness during parsing the process.
+``3`` different element types are stored on *local stack*:
+
+* Start of object, with ``{`` character
+* Start of array, with ``[`` character
+* Key from the *object* entry
+
+.. note::
+ Stack is nested as long as JSON input stream is nested in the same way
+
+Consider this input string: ``{"k1":"v1","k2":[true, false]}``.
+During parsing procedure, at some point of time, these events will occur:
+
+#. Start of *object* detected - *object* pushed to stack
+
+ #. *key* element with name ``k1`` detected and pushed to stack
+
+ #. *string* ``v1`` parsed as *string-value*
+ #. *key* element with name ``k1`` popped from stack
+ #. *key* element with name ``k2`` detected and pushed to stack
+
+ #. Start of *array* detected - *array* pushed to stack
+
+ #. ``true`` primitive detected
+ #. ``false`` primitive detected
+ #. End of *array* detected - *array* popped from stack
+ #. *key* element with name ``k2`` popped from stack
+#. End of *object* detected - *object* popped from stack
+
+Each of these events is reported to user in the callback function.
+
+An example of the stream parsing:
+
+.. literalinclude:: ../../examples/example_stream.c
+ :language: c
+ :linenos:
+ :caption: Parse JSON data as a stream object
+
+Example
+*******
+
+For the purpose of example, the following JSON input...
+
+.. literalinclude:: ../examples/stream.json
+ :language: json
+ :linenos:
+ :caption: JSON input for streaming
+
+\... will output the log as:
+
+.. literalinclude:: ../examples/stream_log.txt
+ :linenos:
+ :caption: JSON development log for the various events
+
+.. toctree::
+ :maxdepth: 2
\ No newline at end of file
diff --git a/examples/example_stream.c b/examples/example_stream.c
new file mode 100644
index 0000000..66cda22
--- /dev/null
+++ b/examples/example_stream.c
@@ -0,0 +1,46 @@
+#include
+#include "lwjson/lwjson.h"
+
+/* Test string to parser */
+static const char* json_str = "{\"k1\":\"v1\",\"k2\":[true, false]}";
+
+/* LwJSON stream parser */
+static lwjson_stream_parser_t stream_parser;
+
+/**
+ * \brief Callback function for various events
+ * \param jsp: JSON stream parser object
+ * \param type: Event type
+ */
+void
+prv_example_callback_func(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type) {
+ /* Get a value corresponsing to "k1" key */
+ if (jsp->stack_pos >= 2 /* Number of stack entries must be high */
+ && jsp->stack[0].type == LWJSON_STREAM_TYPE_OBJECT /* First must be object */
+ && jsp->stack[1].type == LWJSON_STREAM_TYPE_KEY /* We need key to be before */
+ && strcmp(jsp->stack[1].meta.name, "k1") == 0) {
+ printf("Got key '%s' with value '%s'\r\n", jsp->stack[1].meta.name, jsp->data.str.buff);
+ }
+ (void)type;
+}
+
+/* Parse JSON */
+void
+example_stream_run(void) {
+ lwjsonr_t res;
+ printf("\r\n\r\nParsing stream\r\n");
+ lwjson_stream_init(&stream_parser, prv_example_callback_func);
+
+ /* Demonstrate as stream inputs */
+ for (const char* c = json_str; *c != '\0'; ++c) {
+ if ((res = lwjson_stream_parse(&stream_parser, *c)) == lwjsonOK) {
+ printf("OK\r\n");
+ } else if (res == lwjsonSTREAMDONE) {
+ printf("Done\r\n");
+ } else {
+ printf("Error\r\n");
+ break;
+ }
+ }
+ printf("Parsing completed\r\n");
+}
diff --git a/library.json b/library.json
index 7f47b8f..78145e7 100644
--- a/library.json
+++ b/library.json
@@ -1,31 +1,33 @@
{
- "name": "LwJSON",
- "version": "1.5.0",
- "description": "Lightweight JSON parser for embedded systems with support for inline comments",
- "keywords": "json, javascript, lightweight, parser, stm32, manager, library, comment, object, notation, object notation",
- "repository": {
- "type": "git",
- "url": "https://github.com/MaJerle/lwjson.git"
- },
- "authors": [
- {
- "name": "Tilen Majerle",
- "email": "tilen@majerle.eu",
- "url": "https://majerle.eu"
- }
- ],
- "license": "MIT",
- "homepage": "https://github.com/MaJerle/lwjson",
- "dependencies": {
-
- },
- "frameworks": "*",
- "platforms": "*",
- "export": {
- "exclude": [
- "docs",
- "**/.vs",
- "**/Debug"
- ]
- }
+ "name": "LwJSON",
+ "version": "1.6.0",
+ "description": "Lightweight JSON parser for embedded systems with support for inline comments",
+ "keywords": "json, javascript, lightweight, parser, stm32, manager, library, comment, object, notation, object notation",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/MaJerle/lwjson.git"
+ },
+ "authors": [
+ {
+ "name": "Tilen Majerle",
+ "email": "tilen@majerle.eu",
+ "url": "https://majerle.eu"
+ }
+ ],
+ "license": "MIT",
+ "homepage": "https://github.com/MaJerle/lwjson",
+ "dependencies": {},
+ "frameworks": "*",
+ "platforms": "*",
+ "export": {
+ "exclude": [
+ ".github",
+ "dev",
+ "docs",
+ "**/.vs",
+ "**/Debug",
+ "build",
+ "**/build"
+ ]
+ }
}
\ No newline at end of file
diff --git a/lwjson/CMakeLists.txt b/lwjson/CMakeLists.txt
new file mode 100644
index 0000000..c005663
--- /dev/null
+++ b/lwjson/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.22)
+
+#Debug message
+message("Entering ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
+
+# Setup generic source files
+set(lwjson_core_SRCS
+ ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson.c
+ ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson_stream.c
+ )
+
+# Debug sources
+set(lwjson_debug_SRCS
+ ${CMAKE_CURRENT_LIST_DIR}/src/lwjson/lwjson_debug.c
+ )
+
+# Setup include directories
+set(lwjson_include_DIRS
+ ${CMAKE_CURRENT_LIST_DIR}/src/include
+ )
+
+# Register core library to the system
+add_library(lwjson INTERFACE)
+target_sources(lwjson PUBLIC ${lwjson_core_SRCS})
+target_include_directories(lwjson INTERFACE ${lwjson_include_DIRS})
+
+# Register lwjson debug module
+add_library(lwjson_debug INTERFACE)
+target_sources(lwjson_debug PUBLIC ${lwjson_debug_SRCS})
+target_include_directories(lwjson_debug INTERFACE ${lwjson_include_DIRS})
+
+#Debug message
+message("Exiting ${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt")
\ No newline at end of file
diff --git a/lwjson/src/include/lwjson/lwjson.h b/lwjson/src/include/lwjson/lwjson.h
index 22cd303..1f7202f 100644
--- a/lwjson/src/include/lwjson/lwjson.h
+++ b/lwjson/src/include/lwjson/lwjson.h
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (c) 2020 Tilen MAJERLE
+ * Copyright (c) 2022 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,7 +29,7 @@
* This file is part of LwJSON - Lightweight JSON format parser.
*
* Author: Tilen MAJERLE
- * Version: v1.5.0
+ * Version: v1.6.0
*/
#ifndef LWJSON_HDR_H
#define LWJSON_HDR_H
@@ -53,20 +53,20 @@ extern "C" {
* \param[in] x: Object to get array size of
* \return Number of elements in array
*/
-#define LWJSON_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define LWJSON_ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0]))
/**
* \brief List of supported JSON types
*/
typedef enum {
- LWJSON_TYPE_STRING, /*!< String/Text format. Everything that has beginning and ending quote character */
- LWJSON_TYPE_NUM_INT, /*!< Number type for integer */
- LWJSON_TYPE_NUM_REAL, /*!< Number type for real number */
- LWJSON_TYPE_OBJECT, /*!< Object data type */
- LWJSON_TYPE_ARRAY, /*!< Array data type */
- LWJSON_TYPE_TRUE, /*!< True boolean value */
- LWJSON_TYPE_FALSE, /*!< False boolean value */
- LWJSON_TYPE_NULL, /*!< Null value */
+ LWJSON_TYPE_STRING, /*!< String/Text format. Everything that has beginning and ending quote character */
+ LWJSON_TYPE_NUM_INT, /*!< Number type for integer */
+ LWJSON_TYPE_NUM_REAL, /*!< Number type for real number */
+ LWJSON_TYPE_OBJECT, /*!< Object data type */
+ LWJSON_TYPE_ARRAY, /*!< Array data type */
+ LWJSON_TYPE_TRUE, /*!< True boolean value */
+ LWJSON_TYPE_FALSE, /*!< False boolean value */
+ LWJSON_TYPE_NULL, /*!< Null value */
} lwjson_type_t;
/**
@@ -83,89 +83,190 @@ typedef LWJSON_CFG_INT_TYPE lwjson_int_t;
* \brief JSON token
*/
typedef struct lwjson_token {
- struct lwjson_token* next; /*!< Next token on a list */
- lwjson_type_t type; /*!< Token type */
- const char* token_name; /*!< Token name (if exists) */
- size_t token_name_len; /*!< Length of token name (this is needed to support const input strings to parse) */
+ struct lwjson_token* next; /*!< Next token on a list */
+ lwjson_type_t type; /*!< Token type */
+ const char* token_name; /*!< Token name (if exists) */
+ size_t token_name_len; /*!< Length of token name (this is needed to support const input strings to parse) */
+
union {
struct {
- const char* token_value; /*!< Pointer to the beginning of the string */
- size_t token_value_len; /*!< Length of token value (this is needed to support const input strings to parse) */
- } str; /*!< String data */
- lwjson_real_t num_real; /*!< Real number format */
- lwjson_int_t num_int; /*!< Int number format */
- struct lwjson_token* first_child; /*!< First children object for object or array type */
- } u; /*!< Union with different data types */
+ const char* token_value; /*!< Pointer to the beginning of the string */
+ size_t
+ token_value_len; /*!< Length of token value (this is needed to support const input strings to parse) */
+ } str; /*!< String data */
+
+ lwjson_real_t num_real; /*!< Real number format */
+ lwjson_int_t num_int; /*!< Int number format */
+ struct lwjson_token* first_child; /*!< First children object for object or array type */
+ } u; /*!< Union with different data types */
} lwjson_token_t;
/**
* \brief JSON result enumeration
*/
typedef enum {
- lwjsonOK = 0x00, /*!< Function returns successfully */
- lwjsonERR, /*!< Generic error message */
- lwjsonERRJSON, /*!< Error JSON format */
- lwjsonERRMEM, /*!< Memory error */
- lwjsonERRPAR, /*!< Parameter error */
-} lwjsonr_t;
+ lwjsonOK = 0x00, /*!< Function returns successfully */
+ lwjsonERR, /*!< Generic error message */
+ lwjsonERRJSON, /*!< Error JSON format */
+ lwjsonERRMEM, /*!< Memory error */
+ lwjsonERRPAR, /*!< Parameter error */
+
+ lwjsonSTREAMWAITFIRSTCHAR, /*!< Streaming parser did not yet receive first valid character
+ indicating start of JSON sequence */
+ lwjsonSTREAMDONE, /*!< Streaming parser is done,
+ closing character matched the stream opening one */
+ lwjsonSTREAMINPROG, /*!< Stream parsing is still in progress */
+}
+
+lwjsonr_t;
/**
* \brief LwJSON instance
*/
typedef struct {
- lwjson_token_t* tokens; /*!< Pointer to array of tokens */
- size_t tokens_len; /*!< Size of all tokens */
- size_t next_free_token_pos; /*!< Position of next free token instance */
- lwjson_token_t first_token; /*!< First token on a list */
+ lwjson_token_t* tokens; /*!< Pointer to array of tokens */
+ size_t tokens_len; /*!< Size of all tokens */
+ size_t next_free_token_pos; /*!< Position of next free token instance */
+ lwjson_token_t first_token; /*!< First token on a list */
+
struct {
- uint8_t parsed : 1; /*!< Flag indicating JSON parsing has finished successfully */
- } flags; /*!< List of flags */
+ uint8_t parsed : 1; /*!< Flag indicating JSON parsing has finished successfully */
+ } flags; /*!< List of flags */
} lwjson_t;
-lwjsonr_t lwjson_init(lwjson_t* lw, lwjson_token_t* tokens, size_t tokens_len);
-lwjsonr_t lwjson_parse_ex(lwjson_t* lw, const void* json_data, size_t len);
-lwjsonr_t lwjson_parse(lwjson_t* lw, const char* json_str);
-const lwjson_token_t* lwjson_find(lwjson_t* lw, const char* path);
-const lwjson_token_t* lwjson_find_ex(lwjson_t* lw, const lwjson_token_t* token, const char* path);
-lwjsonr_t lwjson_free(lwjson_t* lw);
+lwjsonr_t lwjson_init(lwjson_t* lw, lwjson_token_t* tokens, size_t tokens_len);
+lwjsonr_t lwjson_parse_ex(lwjson_t* lw, const void* json_data, size_t len);
+lwjsonr_t lwjson_parse(lwjson_t* lw, const char* json_str);
+const lwjson_token_t* lwjson_find(lwjson_t* lw, const char* path);
+const lwjson_token_t* lwjson_find_ex(lwjson_t* lw, const lwjson_token_t* token, const char* path);
+lwjsonr_t lwjson_free(lwjson_t* lw);
+
+void lwjson_print_token(const lwjson_token_t* token);
+void lwjson_print_json(const lwjson_t* lw);
+
+/**
+ * \brief Object type for streaming parser
+ */
+typedef enum {
+ LWJSON_STREAM_TYPE_NONE, /*!< No entry - not used */
+ LWJSON_STREAM_TYPE_OBJECT, /*!< Object indication */
+ LWJSON_STREAM_TYPE_OBJECT_END, /*!< Object end indication */
+ LWJSON_STREAM_TYPE_ARRAY, /*!< Array indication */
+ LWJSON_STREAM_TYPE_ARRAY_END, /*!< Array end indication */
+ LWJSON_STREAM_TYPE_KEY, /*!< Key string */
+ LWJSON_STREAM_TYPE_STRING, /*!< Strin type */
+ LWJSON_STREAM_TYPE_TRUE, /*!< True primitive */
+ LWJSON_STREAM_TYPE_FALSE, /*!< False primitive */
+ LWJSON_STREAM_TYPE_NULL, /*!< Null primitive */
+ LWJSON_STREAM_TYPE_NUMBER, /*!< Generic number */
+} lwjson_stream_type_t;
+
+/**
+ * \brief Stream parsing stack object
+ */
+typedef struct {
+ lwjson_stream_type_t type; /*!< Streaming type - current value */
+
+ union {
+ char name[LWJSON_CFG_STREAM_KEY_MAX_LEN
+ + 1]; /*!< Last known key name, used only for \ref LWJSON_STREAM_TYPE_KEY type */
+ uint16_t index; /*!< Current index when type is an array */
+ } meta; /*!< Meta information */
+} lwjson_stream_stack_t;
+
+typedef enum {
+ LWJSON_STREAM_STATE_WAITINGFIRSTCHAR = 0x00, /*!< State to wait for very first opening character */
+ LWJSON_STREAM_STATE_PARSING, /*!< In parsing of the first char state - detecting next character state */
+ LWJSON_STREAM_STATE_PARSING_STRING, /*!< Parse string primitive */
+ LWJSON_STREAM_STATE_PARSING_PRIMITIVE, /*!< Parse any primitive that is non-string, either "true", "false", "null" or a number */
+} lwjson_stream_state_t;
+
+/* Forward declaration */
+struct lwjson_stream_parser;
+
+/**
+ * \brief Callback function for various events
+ *
+ */
+typedef void (*lwjson_stream_parser_callback_fn)(struct lwjson_stream_parser* jsp, lwjson_stream_type_t type);
+
+/**
+ * \brief LwJSON streaming structure
+ */
+typedef struct lwjson_stream_parser {
+ lwjson_stream_stack_t
+ stack[LWJSON_CFG_STREAM_STACK_SIZE]; /*!< Stack used for parsing. TODO: Add conditional compilation flag */
+ size_t stack_pos; /*!< Current stack position */
+
+ lwjson_stream_state_t parse_state; /*!< Parser state */
+
+ lwjson_stream_parser_callback_fn evt_fn; /*!< Event function for user */
+
+ /* State */
+ union {
+ struct {
+ char buff[LWJSON_CFG_STREAM_STRING_MAX_LEN
+ + 1]; /*!< Buffer to write temporary data. TODO: Size to be variable with define */
+ size_t buff_pos; /*!< Buffer position for next write (length of bytes in buffer) */
+ size_t buff_total_pos; /*!< Total buffer position used up to now (in several data chunks) */
+ uint8_t is_last; /*!< Status indicates if this is the last part of the string */
+ } str; /*!< String structure. It is only used for keys and string objects.
+ Use primitive part for all other options */
+
+ struct {
+ char buff[LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN + 1]; /*!< Temporary write buffer */
+ size_t buff_pos; /*!< Buffer position for next write */
+ } prim; /*!< Primitive object. Used for all types, except key or string */
+
+ /* Todo: Add other types */
+ } data; /*!< Data union used to parse various */
+
+ char prev_c; /*!< History of characters */
+} lwjson_stream_parser_t;
-void lwjson_print_token(const lwjson_token_t* token);
-void lwjson_print_json(const lwjson_t* lw);
+lwjsonr_t lwjson_stream_init(lwjson_stream_parser_t* jsp, lwjson_stream_parser_callback_fn evt_fn);
+lwjsonr_t lwjson_stream_reset(lwjson_stream_parser_t* jsp);
+lwjsonr_t lwjson_stream_parse(lwjson_stream_parser_t* jsp, char c);
/**
* \brief Get number of tokens used to parse JSON
* \param[in] lw: Pointer to LwJSON instance
* \return Number of tokens used to parse JSON
*/
-#define lwjson_get_tokens_used(lw) (((lw) != NULL) ? ((lw)->next_free_token_pos + 1) : 0)
+#define lwjson_get_tokens_used(lw) (((lw) != NULL) ? ((lw)->next_free_token_pos + 1) : 0)
/**
* \brief Get very first token of LwJSON instance
* \param[in] lw: Pointer to LwJSON instance
* \return Pointer to first token
*/
-#define lwjson_get_first_token(lw) (((lw) != NULL) ? (&(lw)->first_token) : NULL)
+#define lwjson_get_first_token(lw) (((lw) != NULL) ? (&(lw)->first_token) : NULL)
/**
* \brief Get token value for \ref LWJSON_TYPE_NUM_INT type
* \param[in] token: token with integer type
* \return Int number if type is integer, `0` otherwise
*/
-#define lwjson_get_val_int(token) ((lwjson_int_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_INT) ? (token)->u.num_int : 0))
+#define lwjson_get_val_int(token) \
+ ((lwjson_int_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_INT) ? (token)->u.num_int : 0))
/**
* \brief Get token value for \ref LWJSON_TYPE_NUM_REAL type
* \param[in] token: token with real type
* \return Real numbeer if type is real, `0` otherwise
*/
-#define lwjson_get_val_real(token) ((lwjson_real_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_REAL) ? (token)->u.num_real : 0))
+#define lwjson_get_val_real(token) \
+ ((lwjson_real_t)(((token) != NULL && (token)->type == LWJSON_TYPE_NUM_REAL) ? (token)->u.num_real : 0))
/**
* \brief Get first child token for \ref LWJSON_TYPE_OBJECT or \ref LWJSON_TYPE_ARRAY types
* \param[in] token: token with integer type
* \return Pointer to first child or `NULL` if parent token is not object or array
*/
-#define lwjson_get_first_child(token) (const void *)(((token) != NULL && ((token)->type == LWJSON_TYPE_OBJECT || (token)->type == LWJSON_TYPE_ARRAY)) ? (token)->u.first_child : NULL)
+#define lwjson_get_first_child(token) \
+ (const void*)(((token) != NULL && ((token)->type == LWJSON_TYPE_OBJECT || (token)->type == LWJSON_TYPE_ARRAY)) \
+ ? (token)->u.first_child \
+ : NULL)
/**
* \brief Get string value from JSON token
@@ -190,12 +291,13 @@ lwjson_get_val_string(const lwjson_token_t* token, size_t* str_len) {
* \param[in] token: token with string type
* \return Length of string in units of bytes
*/
-#define lwjson_get_val_string_length(token) ((size_t)(((token) != NULL && (token)->type == LWJSON_TYPE_STRING) ? (token)->u.str.token_value_len : 0))
+#define lwjson_get_val_string_length(token) \
+ ((size_t)(((token) != NULL && (token)->type == LWJSON_TYPE_STRING) ? (token)->u.str.token_value_len : 0))
/**
* \brief Compare string token with user input string for a case-sensitive match
* \param[in] token: Token with string type
- * \param[out] str: String to compare
+ * \param[in] str: NULL-terminated string to compare
* \return `1` if equal, `0` otherwise
*/
static inline uint8_t
@@ -209,7 +311,8 @@ lwjson_string_compare(const lwjson_token_t* token, const char* str) {
/**
* \brief Compare string token with user input string for a case-sensitive match
* \param[in] token: Token with string type
- * \param[out] str: String to compare
+ * \param[in] str: NULL-terminated string to compare
+ * \param[in] len: Length of the string in bytes
* \return `1` if equal, `0` otherwise
*/
static inline uint8_t
diff --git a/lwjson/src/include/lwjson/lwjson_opt.h b/lwjson/src/include/lwjson/lwjson_opt.h
index f36288f..23c23ab 100644
--- a/lwjson/src/include/lwjson/lwjson_opt.h
+++ b/lwjson/src/include/lwjson/lwjson_opt.h
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (c) 2020 Tilen MAJERLE
+ * Copyright (c) 2022 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,7 +29,7 @@
* This file is part of LwJSON - Lightweight JSON format parser.
*
* Author: Tilen MAJERLE
- * Version: v1.5.0
+ * Version: v1.6.0
*/
#ifndef LWJSON_HDR_OPT_H
#define LWJSON_HDR_OPT_H
@@ -59,7 +59,7 @@ extern "C" {
* This is used for numbers in \ref LWJSON_TYPE_NUM_REAL token data type.
*/
#ifndef LWJSON_CFG_REAL_TYPE
-#define LWJSON_CFG_REAL_TYPE float
+#define LWJSON_CFG_REAL_TYPE float
#endif
/**
@@ -69,7 +69,7 @@ extern "C" {
* This is used for numbers in \ref LWJSON_TYPE_NUM_INT token data type.
*/
#ifndef LWJSON_CFG_INT_TYPE
-#define LWJSON_CFG_INT_TYPE long long
+#define LWJSON_CFG_INT_TYPE long long
#endif
/**
@@ -78,9 +78,52 @@ extern "C" {
* Default set to `0` to be JSON compliant
*/
#ifndef LWJSON_CFG_COMMENTS
-#define LWJSON_CFG_COMMENTS 0
+#define LWJSON_CFG_COMMENTS 0
#endif
+/**
+ * \defgroup LWJSON_OPT_STREAM JSON stream
+ * \brief JSON streaming confiuration
+ * \{
+ */
+
+/**
+ * \brief Max length of token key (object key name) to be available for stack storage
+ *
+ */
+#ifndef LWJSON_CFG_STREAM_KEY_MAX_LEN
+#define LWJSON_CFG_STREAM_KEY_MAX_LEN 32
+#endif
+
+/**
+ * \brief Max stack size (depth) in units of \ref lwjson_stream_stack_t structure
+ *
+ */
+#ifndef LWJSON_CFG_STREAM_STACK_SIZE
+#define LWJSON_CFG_STREAM_STACK_SIZE 16
+#endif
+
+/**
+ * \brief Max size of string for single parsing in units of bytes
+ *
+ */
+#ifndef LWJSON_CFG_STREAM_STRING_MAX_LEN
+#define LWJSON_CFG_STREAM_STRING_MAX_LEN 256
+#endif
+
+/**
+ * \brief Max number of bytes used to parse primitive.
+ *
+ * Primitives are all numbers and logical values (null, true, false)
+ */
+#ifndef LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN
+#define LWJSON_CFG_STREAM_PRIMITIVE_MAX_LEN 32
+#endif
+
+/**
+ * \}
+ */
+
/**
* \}
*/
diff --git a/lwjson/src/include/lwjson/lwjson_opts_template.h b/lwjson/src/include/lwjson/lwjson_opts_template.h
index e4c0006..50b181e 100644
--- a/lwjson/src/include/lwjson/lwjson_opts_template.h
+++ b/lwjson/src/include/lwjson/lwjson_opts_template.h
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (c) 2020 Tilen MAJERLE
+ * Copyright (c) 2022 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,7 +29,7 @@
* This file is part of LwJSON - Lightweight JSON format parser.
*
* Author: Tilen MAJERLE
- * Version: v1.5.0
+ * Version: v1.6.0
*/
#ifndef LWJSON_HDR_OPTS_H
#define LWJSON_HDR_OPTS_H
diff --git a/lwjson/src/lwjson/lwjson.c b/lwjson/src/lwjson/lwjson.c
index a1659c0..b8a1482 100644
--- a/lwjson/src/lwjson/lwjson.c
+++ b/lwjson/src/lwjson/lwjson.c
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (c) 2020 Tilen MAJERLE
+ * Copyright (c) 2022 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,7 +29,7 @@
* This file is part of LwJSON - Lightweight JSON format parser.
*
* Author: Tilen MAJERLE
- * Version: v1.5.0
+ * Version: v1.6.0
*/
#include
#include "lwjson/lwjson.h"
@@ -38,9 +38,9 @@
* \brief Internal string object
*/
typedef struct {
- const char* start; /*!< Original pointer to beginning of JSON object */
- size_t len; /*!< Total length of input json string */
- const char* p; /*!< Current char pointer */
+ const char* start; /*!< Original pointer to beginning of JSON object */
+ size_t len; /*!< Total length of input json string */
+ const char* p; /*!< Current char pointer */
} lwjson_int_str_t;
/**
@@ -68,7 +68,7 @@ prv_skip_blank(lwjson_int_str_t* pobj) {
if (*pobj->p == ' ' || *pobj->p == '\t' || *pobj->p == '\r' || *pobj->p == '\n' || *pobj->p == '\f') {
++pobj->p;
#if LWJSON_CFG_COMMENTS
- /* Check for comments and remove them */
+ /* Check for comments and remove them */
} else if (*pobj->p == '/') {
++pobj->p;
if (pobj->p != NULL && *pobj->p == '*') {
@@ -137,9 +137,8 @@ prv_parse_string(lwjson_int_str_t* pobj, const char** pout, size_t* poutlen) {
case 'u':
++pobj->p;
for (size_t i = 0; i < 4; ++i, ++len) {
- if (!((*pobj->p >= '0' && *pobj->p <= '9')
- || (*pobj->p >= 'a' && *pobj->p <= 'f')
- || (*pobj->p >= 'A' && *pobj->p <= 'F'))) {
+ if (!((*pobj->p >= '0' && *pobj->p <= '9') || (*pobj->p >= 'a' && *pobj->p <= 'f')
+ || (*pobj->p >= 'A' && *pobj->p <= 'F'))) {
return lwjsonERRJSON;
}
if (i < 3) {
@@ -211,9 +210,10 @@ prv_parse_number(lwjson_int_str_t* pobj, lwjson_type_t* tout, lwjson_real_t* fou
return lwjsonERRJSON;
}
is_minus = *pobj->p == '-' ? (++pobj->p, 1) : 0;
- if (*pobj->p == '\0' /* Invalid string */
- || *pobj->p < '0' || *pobj->p > '9' /* Character outside number range */
- || (*pobj->p == '0' && (pobj->p[1] < '0' && pobj->p[1] > '9'))) { /* Number starts with 0 but not followed by dot */
+ if (*pobj->p == '\0' /* Invalid string */
+ || *pobj->p < '0' || *pobj->p > '9' /* Character outside number range */
+ || (*pobj->p == '0'
+ && (pobj->p[1] < '0' && pobj->p[1] > '9'))) { /* Number starts with 0 but not followed by dot */
return lwjsonERRJSON;
}
@@ -221,7 +221,7 @@ prv_parse_number(lwjson_int_str_t* pobj, lwjson_type_t* tout, lwjson_real_t* fou
for (num = 0; *pobj->p >= '0' && *pobj->p <= '9'; ++pobj->p) {
num = num * 10 + (*pobj->p - '0');
}
- if (pobj->p != NULL && *pobj->p == '.') { /* Number has exponent */
+ if (pobj->p != NULL && *pobj->p == '.') { /* Number has exponent */
lwjson_real_t exp, dec_num;
type = LWJSON_TYPE_NUM_REAL; /* Format is real */
@@ -233,16 +233,16 @@ prv_parse_number(lwjson_int_str_t* pobj, lwjson_type_t* tout, lwjson_real_t* fou
for (exp = 1, dec_num = 0; *pobj->p >= '0' && *pobj->p <= '9'; ++pobj->p, exp *= 10) {
dec_num = dec_num * 10 + (*pobj->p - '0');
}
- num += dec_num / exp; /* Add decimal part to number */
+ num += dec_num / exp; /* Add decimal part to number */
}
- if (pobj->p != NULL && (*pobj->p == 'e' || *pobj->p == 'E')) { /* Engineering mode */
+ if (pobj->p != NULL && (*pobj->p == 'e' || *pobj->p == 'E')) { /* Engineering mode */
uint8_t is_minus_exp;
int exp_cnt;
- type = LWJSON_TYPE_NUM_REAL; /* Format is real */
- ++pobj->p; /* Ignore enginnering sing part */
- is_minus_exp = *pobj->p == '-' ? (++pobj->p, 1) : 0;/* Check if negative */
- if (*pobj->p == '+') { /* Optional '+' is possible too */
+ type = LWJSON_TYPE_NUM_REAL; /* Format is real */
+ ++pobj->p; /* Ignore enginnering sing part */
+ is_minus_exp = *pobj->p == '-' ? (++pobj->p, 1) : 0; /* Check if negative */
+ if (*pobj->p == '+') { /* Optional '+' is possible too */
++pobj->p;
}
if (*pobj->p < '0' || *pobj->p > '9') { /* Must be followed by number characters */
@@ -352,7 +352,7 @@ prv_find(const lwjson_token_t* parent, const char* path) {
/* Check if index requested */
if (segment_len > 1) {
- const lwjson_token_t *t;
+ const lwjson_token_t* t;
size_t index = 0;
/* Parse number */
@@ -377,7 +377,7 @@ prv_find(const lwjson_token_t* parent, const char* path) {
}
/* Scan all indexes and get first match */
- for (const lwjson_token_t* tmp_t, *t = parent->u.first_child; t != NULL; t = t->next) {
+ for (const lwjson_token_t *tmp_t, *t = parent->u.first_child; t != NULL; t = t->next) {
if ((tmp_t = prv_find(t, path)) != NULL) {
return tmp_t;
}
@@ -416,12 +416,10 @@ prv_check_valid_char_after_open_bracket(lwjson_int_str_t* pobj, lwjson_token_t*
if ((res = prv_skip_blank(pobj)) != lwjsonOK) {
return res;
}
- if (*pobj->p == '\0'
- || (t->type == LWJSON_TYPE_OBJECT
- && (*pobj->p != '"' && *pobj->p != '}'))
+ if (*pobj->p == '\0' || (t->type == LWJSON_TYPE_OBJECT && (*pobj->p != '"' && *pobj->p != '}'))
|| (t->type == LWJSON_TYPE_ARRAY
- && (*pobj->p != '"' && *pobj->p != ']' && *pobj->p != '[' && *pobj->p != '{' && *pobj->p != '-'
- && (*pobj->p < '0' || *pobj->p > '9') && *pobj->p != 't' && *pobj->p != 'n' && *pobj->p != 'f'))) {
+ && (*pobj->p != '"' && *pobj->p != ']' && *pobj->p != '[' && *pobj->p != '{' && *pobj->p != '-'
+ && (*pobj->p < '0' || *pobj->p > '9') && *pobj->p != 't' && *pobj->p != 'n' && *pobj->p != 'f'))) {
res = lwjsonERRJSON;
}
return res;
@@ -455,12 +453,8 @@ lwjson_init(lwjson_t* lw, lwjson_token_t* tokens, size_t tokens_len) {
lwjsonr_t
lwjson_parse_ex(lwjson_t* lw, const void* json_data, size_t json_len) {
lwjsonr_t res = lwjsonOK;
- lwjson_token_t* t, *to;
- lwjson_int_str_t pobj = {
- .start = json_data,
- .len = json_len,
- .p = json_data
- };
+ lwjson_token_t *t, *to;
+ lwjson_int_str_t pobj = {.start = json_data, .len = json_len, .p = json_data};
/* Check input parameters */
if (lw == NULL || json_data == NULL || json_len == 0) {
@@ -508,21 +502,20 @@ lwjson_parse_ex(lwjson_t* lw, const void* json_data, size_t json_len) {
if (*pobj.p == (to->type == LWJSON_TYPE_OBJECT ? '}' : ']')) {
lwjson_token_t* parent = to->next;
to->next = NULL;
- to = parent;
++pobj.p;
- /* End of string, check if properly terminated */
- if (to == NULL) {
+ /* End of string if to == NULL (no parent), check if properly terminated */
+ if ((to = parent) == NULL) {
prv_skip_blank(&pobj);
- res = (pobj.p == NULL || *pobj.p == '\0' || (pobj.p - pobj.start) == pobj.len) ? lwjsonOK : lwjsonERR;
+ res = (pobj.p == NULL || *pobj.p == '\0' || (size_t)(pobj.p - pobj.start) == pobj.len) ? lwjsonOK
+ : lwjsonERR;
goto ret;
}
continue;
}
/* Allocate new token */
- t = prv_alloc_token(lw);
- if (t == NULL) {
+ if ((t = prv_alloc_token(lw)) == NULL) {
res = lwjsonERRMEM;
goto ret;
}
@@ -556,7 +549,7 @@ lwjson_parse_ex(lwjson_t* lw, const void* json_data, size_t json_len) {
if ((res = prv_check_valid_char_after_open_bracket(&pobj, t)) != lwjsonOK) {
goto ret;
}
- t->next = to; /* Temporary saved as parent object */
+ t->next = to; /* Temporary saved as parent object */
to = t;
break;
case '"':
@@ -630,7 +623,7 @@ lwjson_parse_ex(lwjson_t* lw, const void* json_data, size_t json_len) {
if (pobj.p == NULL || *pobj.p == '\0' || (*pobj.p != ',' && *pobj.p != ']' && *pobj.p != '}')) {
res = lwjsonERRJSON;
goto ret;
- } else if (*pobj.p == ',') { /* Check to advance to next token immediatey */
+ } else if (*pobj.p == ',') { /* Check to advance to next token immediatey */
++pobj.p;
}
}
diff --git a/lwjson/src/lwjson/lwjson_debug.c b/lwjson/src/lwjson/lwjson_debug.c
index 7b9ddf5..1ac9ada 100644
--- a/lwjson/src/lwjson/lwjson_debug.c
+++ b/lwjson/src/lwjson/lwjson_debug.c
@@ -4,7 +4,7 @@
*/
/*
- * Copyright (c) 2020 Tilen MAJERLE
+ * Copyright (c) 2022 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
@@ -29,17 +29,17 @@
* This file is part of LwJSON - Lightweight JSON format parser.
*
* Author: Tilen MAJERLE
- * Version: v1.5.0
+ * Version: v1.6.0
*/
-#include
#include
+#include
#include "lwjson/lwjson.h"
/**
* \brief Token print instance
*/
typedef struct {
- size_t indent; /*!< Indent level for token print */
+ size_t indent; /*!< Indent level for token print */
} lwjson_token_print_t;
/**
@@ -49,7 +49,7 @@ typedef struct {
*/
static void
prv_print_token(lwjson_token_print_t* p, const lwjson_token_t* token) {
-#define print_indent() printf("%.*s", (int)((p->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");
+#define print_indent() printf("%.*s", (int)((p->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");
if (token == NULL) {
return;
@@ -118,7 +118,7 @@ prv_print_token(lwjson_token_print_t* p, const lwjson_token_t* token) {
*/
void
lwjson_print_token(const lwjson_token_t* token) {
- lwjson_token_print_t p = { 0 };
+ lwjson_token_print_t p = {0};
prv_print_token(&p, token);
}
@@ -129,6 +129,6 @@ lwjson_print_token(const lwjson_token_t* token) {
*/
void
lwjson_print_json(const lwjson_t* lw) {
- lwjson_token_print_t p = { 0 };
+ lwjson_token_print_t p = {0};
prv_print_token(&p, lwjson_get_first_token(lw));
-}
\ No newline at end of file
+}
diff --git a/lwjson/src/lwjson/lwjson_stream.c b/lwjson/src/lwjson/lwjson_stream.c
new file mode 100644
index 0000000..54f08c5
--- /dev/null
+++ b/lwjson/src/lwjson/lwjson_stream.c
@@ -0,0 +1,436 @@
+/**
+ * \file lwjson_stream.c
+ * \brief Lightweight JSON format parser
+ */
+
+/*
+ * Copyright (c) 2022 Tilen MAJERLE
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * This file is part of LwJSON - Lightweight JSON format parser.
+ *
+ * Author: Tilen MAJERLE
+ * Version: v1.6.0
+ */
+#include
+#include "lwjson/lwjson.h"
+
+#if defined(LWJSON_DEV)
+#include
+#define DEBUG_STRING_PREFIX_SPACES \
+ " "
+#define LWJSON_DEBUG(jsp, ...) \
+ do { \
+ if ((jsp) != NULL) { \
+ printf("%.*s", (int)(4 * (jsp)->stack_pos), DEBUG_STRING_PREFIX_SPACES); \
+ } \
+ printf(__VA_ARGS__); \
+ } while (0)
+
+/* Strings for debug */
+static const char* type_strings[] = {
+ [LWJSON_STREAM_TYPE_NONE] = "none",
+ [LWJSON_STREAM_TYPE_OBJECT] = "object",
+ [LWJSON_STREAM_TYPE_OBJECT_END] = "object_end",
+ [LWJSON_STREAM_TYPE_ARRAY] = "array",
+ [LWJSON_STREAM_TYPE_ARRAY_END] = "array_end",
+ [LWJSON_STREAM_TYPE_KEY] = "key",
+ [LWJSON_STREAM_TYPE_STRING] = "string",
+ [LWJSON_STREAM_TYPE_TRUE] = "true",
+ [LWJSON_STREAM_TYPE_FALSE] = "false",
+ [LWJSON_STREAM_TYPE_NULL] = "null",
+ [LWJSON_STREAM_TYPE_NUMBER] = "number",
+};
+#else
+#define LWJSON_DEBUG(jsp, ...)
+#endif /* defined(LWJSON_DEV) */
+
+/**
+ * \brief Sends an event to user for further processing
+ *
+ */
+#define SEND_EVT(jsp, type) \
+ if ((jsp) != NULL && (jsp)->evt_fn != NULL) { \
+ (jsp)->evt_fn((jsp), (type)); \
+ }
+
+/**
+ * \brief Check if character is a space character (with extended chars)
+ * \param[in] c: Character to check
+ * \return `1` if considered extended space, `0` otherwise
+ */
+#define prv_is_space_char_ext(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == '\n' || (c) == '\f')
+
+/**
+ * \brief Push "parent" state to the artificial stack
+ * \param jsp: JSON stream parser instance
+ * \param type: Stream type to be pushed on stack
+ * \return `1` on success, `0` otherwise
+ */
+static uint8_t
+prv_stack_push(lwjson_stream_parser_t* jsp, lwjson_stream_type_t type) {
+ if (jsp->stack_pos < LWJSON_ARRAYSIZE(jsp->stack)) {
+ jsp->stack[jsp->stack_pos].type = type;
+ jsp->stack[jsp->stack_pos].meta.index = 0;
+ LWJSON_DEBUG(jsp, "Pushed to stack: %s\r\n", type_strings[type]);
+ jsp->stack_pos++;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * \brief Pop value from stack (remove it) and return its value
+ * \param jsp: JSON stream parser instance
+ * \return Member of \ref lwjson_stream_type_t enumeration
+ */
+static lwjson_stream_type_t
+prv_stack_pop(lwjson_stream_parser_t* jsp) {
+ if (jsp->stack_pos > 0) {
+ lwjson_stream_type_t t = jsp->stack[--jsp->stack_pos].type;
+ jsp->stack[jsp->stack_pos].type = LWJSON_STREAM_TYPE_NONE;
+ LWJSON_DEBUG(jsp, "Popped from stack: %s\r\n", type_strings[t]);
+
+ /* Take care of array to indicate number of entries */
+ if (jsp->stack_pos > 0 && jsp->stack[jsp->stack_pos - 1].type == LWJSON_STREAM_TYPE_ARRAY) {
+ jsp->stack[jsp->stack_pos - 1].meta.index++;
+ }
+ return t;
+ }
+ return LWJSON_STREAM_TYPE_NONE;
+}
+
+/**
+ * \brief Get top type value currently on the stack
+ * \param jsp: JSON stream parser instance
+ * \return Member of \ref lwjson_stream_type_t enumeration
+ */
+static lwjson_stream_type_t
+prv_stack_get_top(lwjson_stream_parser_t* jsp) {
+ if (jsp->stack_pos > 0) {
+ return jsp->stack[jsp->stack_pos - 1].type;
+ }
+ return LWJSON_STREAM_TYPE_NONE;
+}
+
+/**
+ * \brief Initialize LwJSON stream object before parsing takes place
+ * \param[in,out] jsp: Stream JSON structure
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
+ */
+lwjsonr_t
+lwjson_stream_init(lwjson_stream_parser_t* jsp, lwjson_stream_parser_callback_fn evt_fn) {
+ memset(jsp, 0x00, sizeof(*jsp));
+ jsp->parse_state = LWJSON_STREAM_STATE_WAITINGFIRSTCHAR;
+ jsp->evt_fn = evt_fn;
+ return lwjsonOK;
+}
+
+/**
+ * \brief Reset LwJSON stream structure
+ *
+ * \param jsp: LwJSON stream parser
+ * \return \ref lwjsonOK on success, member of \ref lwjsonr_t otherwise
+ */
+lwjsonr_t
+lwjson_stream_reset(lwjson_stream_parser_t* jsp) {
+ jsp->parse_state = LWJSON_STREAM_STATE_WAITINGFIRSTCHAR;
+ jsp->stack_pos = 0;
+ return lwjsonOK;
+}
+
+/**
+ * \brief Parse JSON string in streaming mode
+ * \param[in,out] jsp: Stream JSON structure
+ * \param[in] c: Character to parse
+ * \return \ref lwjsonOK if parsing is in progress and no hard error detected
+ * \ref lwjsonSTREAMDONE when valid JSON was detected and stack level reached back `0` level
+ */
+lwjsonr_t
+lwjson_stream_parse(lwjson_stream_parser_t* jsp, char c) {
+ /* Get first character first */
+ if (jsp->parse_state == LWJSON_STREAM_STATE_WAITINGFIRSTCHAR && c != '{' && c != '[') {
+ return lwjsonSTREAMDONE;
+ }
+
+start_over:
+ /*
+ * Determine what to do from parsing state
+ */
+ switch (jsp->parse_state) {
+
+ /*
+ * Waiting for very first valid characters,
+ * that is used to indicate start of JSON stream
+ */
+ case LWJSON_STREAM_STATE_WAITINGFIRSTCHAR:
+ case LWJSON_STREAM_STATE_PARSING: {
+ /* Determine start of object or an array */
+ if (c == '{' || c == '[') {
+ /* Reset stack pointer if this character came from waiting for first character */
+ if (jsp->parse_state == LWJSON_STREAM_STATE_WAITINGFIRSTCHAR) {
+ jsp->stack_pos = 0;
+ }
+ if (!prv_stack_push(jsp, c == '{' ? LWJSON_STREAM_TYPE_OBJECT : LWJSON_STREAM_TYPE_ARRAY)) {
+ LWJSON_DEBUG(jsp, "Cannot push object/array to stack\r\n");
+ return lwjsonERRMEM;
+ }
+ jsp->parse_state = LWJSON_STREAM_STATE_PARSING;
+ SEND_EVT(jsp, c == '{' ? LWJSON_STREAM_TYPE_OBJECT : LWJSON_STREAM_TYPE_ARRAY);
+
+ /* Determine end of object or an array */
+ } else if (c == '}' || c == ']') {
+ lwjson_stream_type_t t = prv_stack_get_top(jsp);
+
+ /*
+ * If it is a key last entry on closing area,
+ * it is an error - an example: {"key":}
+ */
+ if (t == LWJSON_STREAM_TYPE_KEY) {
+ LWJSON_DEBUG(jsp, "ERROR - key should not be followed by ] without value for a key\r\n");
+ return lwjsonERRJSON;
+ }
+
+ /*
+ * Check if closing character matches stack value
+ * Avoid cases like: {"key":"value"] or ["v1", "v2", "v3"}
+ */
+ if ((c == '}' && t != LWJSON_STREAM_TYPE_OBJECT) || (c == ']' && t != LWJSON_STREAM_TYPE_ARRAY)) {
+ LWJSON_DEBUG(jsp, "ERROR - closing character '%c' does not match stack element \"%s\"\r\n", c,
+ type_strings[t]);
+ return lwjsonERRJSON;
+ }
+
+ /* Now remove the array or object from stack */
+ if (prv_stack_pop(jsp) == LWJSON_STREAM_TYPE_NONE) {
+ return lwjsonERRJSON;
+ }
+
+ /*
+ * Check if above is a key type
+ * and remove it too as we finished with processing of potential case.
+ *
+ * {"key":{"abc":1}} - remove "key" part
+ */
+ if (prv_stack_get_top(jsp) == LWJSON_STREAM_TYPE_KEY) {
+ prv_stack_pop(jsp);
+ }
+ SEND_EVT(jsp, c == '}' ? LWJSON_STREAM_TYPE_OBJECT_END : LWJSON_STREAM_TYPE_ARRAY_END);
+
+ /* If that is the end of JSON */
+ if (jsp->stack_pos == 0) {
+ return lwjsonSTREAMDONE;
+ }
+
+ /* Determine start of string - can be key or regular string (in array or after key) */
+ } else if (c == '"') {
+#if defined(LWJSON_DEV)
+ lwjson_stream_type_t t = prv_stack_get_top(jsp);
+ if (t == LWJSON_STREAM_TYPE_OBJECT) {
+ LWJSON_DEBUG(jsp, "Start of string parsing - expected key name in an object\r\n");
+ } else if (t == LWJSON_STREAM_TYPE_KEY) {
+ LWJSON_DEBUG(jsp,
+ "Start of string parsing - string value associated to previous key in an object\r\n");
+ } else if (t == LWJSON_STREAM_TYPE_ARRAY) {
+ LWJSON_DEBUG(jsp, "Start of string parsing - string entry in an array\r\n");
+ }
+#endif /* defined(LWJSON_DEV) */
+ jsp->parse_state = LWJSON_STREAM_STATE_PARSING_STRING;
+ memset(&jsp->data.str, 0x00, sizeof(jsp->data.str));
+
+ /* Check for end of key character */
+ } else if (c == ':') {
+ lwjson_stream_type_t t = prv_stack_get_top(jsp);
+
+ /*
+ * Color can only be followed by key on the stack
+ *
+ * It is clear JSON error if this is not the case
+ */
+ if (t != LWJSON_STREAM_TYPE_KEY) {
+ LWJSON_DEBUG(jsp, "Error - wrong ':' character\r\n");
+ return lwjsonERRJSON;
+ }
+ /* Check if this is start of number or "true", "false" or "null" */
+ } else if (c == '-' || (c >= '0' && c <= '9') || c == 't' || c == 'f' || c == 'n') {
+ LWJSON_DEBUG(jsp, "Start of primitive parsing parsing - %s, First char: %c\r\n",
+ (c == '-' || (c >= '0' && c <= '9')) ? "number" : "true,false,null", c);
+ jsp->parse_state = LWJSON_STREAM_STATE_PARSING_PRIMITIVE;
+ memset(&jsp->data.prim, 0x00, sizeof(jsp->data.prim));
+ jsp->data.prim.buff[jsp->data.prim.buff_pos++] = c;
+ }
+ break;
+ }
+
+ /*
+ * Parse any type of string in a sequence
+ *
+ * It is used for key or string in an object or an array
+ */
+ case LWJSON_STREAM_STATE_PARSING_STRING: {
+ lwjson_stream_type_t t = prv_stack_get_top(jsp);
+
+ /*
+ * Quote character may trigger end of string,
+ * or if backslasled before - it is part of string
+ *
+ * TODO: Handle backslash
+ */
+ if (c == '"' && jsp->prev_c != '\\') {
+#if defined(LWJSON_DEV)
+ if (t == LWJSON_STREAM_TYPE_OBJECT) {
+ LWJSON_DEBUG(jsp, "End of string parsing - object key name: \"%s\"\r\n", jsp->data.str.buff);
+ } else if (t == LWJSON_STREAM_TYPE_KEY) {
+ LWJSON_DEBUG(
+ jsp, "End of string parsing - string value associated to previous key in an object: \"%s\"\r\n",
+ jsp->data.str.buff);
+ } else if (t == LWJSON_STREAM_TYPE_ARRAY) {
+ LWJSON_DEBUG(jsp, "End of string parsing - an array string entry: \"%s\"\r\n", jsp->data.str.buff);
+ }
+#endif /* defined(LWJSON_DEV) */
+
+ /* Set is_last to 1 as this is the last part of this string token */
+ jsp->data.str.is_last = 1;
+
+ /*
+ * When top of stack is object - string is treated as a key
+ * When top of stack is a key - string is a value for a key - notify user and pop the value for key
+ * When top of stack is an array - string is one type - notify user and don't do anything
+ */
+ if (t == LWJSON_STREAM_TYPE_OBJECT) {
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_KEY);
+ if (prv_stack_push(jsp, LWJSON_STREAM_TYPE_KEY)) {
+ size_t len = jsp->data.str.buff_pos;
+ if (len > (sizeof(jsp->stack[0].meta.name) - 1)) {
+ len = sizeof(jsp->stack[0].meta.name) - 1;
+ }
+ memcpy(jsp->stack[jsp->stack_pos - 1].meta.name, jsp->data.str.buff, len);
+ jsp->stack[jsp->stack_pos - 1].meta.name[len] = '\0';
+ } else {
+ LWJSON_DEBUG(jsp, "Cannot push key to stack\r\n");
+ return lwjsonERRMEM;
+ }
+ } else if (t == LWJSON_STREAM_TYPE_KEY) {
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_STRING);
+ prv_stack_pop(jsp);
+ /* Next character to wait for is either space or comma or end of object */
+ } else if (t == LWJSON_STREAM_TYPE_ARRAY) {
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_STRING);
+ jsp->stack[jsp->stack_pos - 1].meta.index++;
+ }
+ jsp->parse_state = LWJSON_STREAM_STATE_PARSING;
+ } else {
+ /* TODO: Check other backslash elements */
+ jsp->data.str.buff[jsp->data.str.buff_pos++] = c;
+ jsp->data.str.buff_total_pos++;
+
+ /* Handle buffer "overflow" */
+ if (jsp->data.str.buff_pos >= (LWJSON_CFG_STREAM_STRING_MAX_LEN - 1)) {
+ jsp->data.str.buff[jsp->data.str.buff_pos] = '\0';
+
+ /*
+ * - For array or key types - following one is always string
+ * - For object type - character is key
+ */
+ SEND_EVT(jsp, (t == LWJSON_STREAM_TYPE_KEY || t == LWJSON_STREAM_TYPE_ARRAY)
+ ? LWJSON_STREAM_TYPE_STRING
+ : LWJSON_STREAM_TYPE_KEY);
+ jsp->data.str.buff_pos = 0;
+ }
+ }
+ break;
+ }
+
+ /*
+ * Parse any type of primitive that is not a string.
+ *
+ * true, false, null or any number primitive
+ */
+ case LWJSON_STREAM_STATE_PARSING_PRIMITIVE: {
+ /* Any character except space, comma, or end of array/object are valid */
+ if (!prv_is_space_char_ext(c) && c != ',' && c != ']' && c != '}') {
+ if (jsp->data.prim.buff_pos < sizeof(jsp->data.prim.buff) - 1) {
+ jsp->data.prim.buff[jsp->data.prim.buff_pos++] = c;
+ }
+ } else {
+ lwjson_stream_type_t t = prv_stack_get_top(jsp);
+
+#if defined(LWJSON_DEV)
+ if (t == LWJSON_STREAM_TYPE_OBJECT) {
+ /* TODO: Handle error - primitive cannot be just after object */
+ } else if (t == LWJSON_STREAM_TYPE_KEY) {
+ LWJSON_DEBUG(
+ jsp,
+ "End of primitive parsing - string value associated to previous key in an object: \"%s\"\r\n",
+ jsp->data.prim.buff);
+ } else if (t == LWJSON_STREAM_TYPE_ARRAY) {
+ LWJSON_DEBUG(jsp, "End of primitive parsing - an array string entry: \"%s\"\r\n",
+ jsp->data.prim.buff);
+ }
+#endif /* defined(LWJSON_DEV) */
+
+ /*
+ * This is the end of primitive parsing
+ *
+ * It is assumed that buffer for primitive can handle at least
+ * true, false, null or all number characters (that being real or int number)
+ */
+ if (jsp->data.prim.buff_pos == 4 && strncmp(jsp->data.prim.buff, "true", 4) == 0) {
+ LWJSON_DEBUG(jsp, "Primitive parsed as %s\r\n", "true");
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_TRUE);
+ } else if (jsp->data.prim.buff_pos == 4 && strncmp(jsp->data.prim.buff, "null", 4) == 0) {
+ LWJSON_DEBUG(jsp, "Primitive parsed as %s\r\n", "null");
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_NULL);
+ } else if (jsp->data.prim.buff_pos == 5 && strncmp(jsp->data.prim.buff, "false", 5) == 0) {
+ LWJSON_DEBUG(jsp, "Primitive parsed as %s\r\n", "false");
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_FALSE);
+ } else if (jsp->data.prim.buff[0] == '-'
+ || (jsp->data.prim.buff[0] >= '0' && jsp->data.prim.buff[0] <= '9')) {
+ LWJSON_DEBUG(jsp, "Primitive parsed - number\r\n");
+ SEND_EVT(jsp, LWJSON_STREAM_TYPE_NUMBER);
+ } else {
+ LWJSON_DEBUG(jsp, "Invalid primitive type. Got: %s\r\n", jsp->data.prim.buff);
+ }
+ if (t == LWJSON_STREAM_TYPE_KEY) {
+ prv_stack_pop(jsp);
+ } else if (t == LWJSON_STREAM_TYPE_ARRAY) {
+ jsp->stack[jsp->stack_pos - 1].meta.index++;
+ }
+
+ /*
+ * Received character is not part of the primitive and must be processed again
+ *
+ * Set state to default state and start from beginning
+ */
+ jsp->parse_state = LWJSON_STREAM_STATE_PARSING;
+ goto start_over;
+ }
+ break;
+ }
+
+ /* TODO: Add other case statements */
+ default:
+ break;
+ }
+ jsp->prev_c = c; /* Save current c as previous for next round */
+ return lwjsonSTREAMINPROG;
+}
diff --git a/test/json/custom_stream.json b/test/json/custom_stream.json
new file mode 100644
index 0000000..fe4f850
--- /dev/null
+++ b/test/json/custom_stream.json
@@ -0,0 +1,13 @@
+{
+ "test": "abc",
+ "array": [
+ "123",
+ "def",
+ "ghi"
+ ],
+ "array_in_array": [
+ ["1", "2", "3"],
+ ["4", "5", "6"],
+ ["7", "8", "9"]
+ ]
+}
\ No newline at end of file
diff --git a/test/json/weather_onecall.json b/test/json/weather_onecall.json
index 6a76f0a..11402a5 100644
--- a/test/json/weather_onecall.json
+++ b/test/json/weather_onecall.json
@@ -1 +1,1837 @@
-{"lat":45.57,"lon":15.19,"timezone":"Europe/Ljubljana","timezone_offset":3600,"current":{"dt":1607029923,"sunrise":1606976516,"sunset":1607008607,"temp":273.15,"feels_like":270.73,"pressure":1008,"humidity":97,"dew_point":272.78,"uvi":0,"clouds":100,"visibility":7000,"wind_speed":0.54,"wind_deg":279,"weather":[{"id":701,"main":"Mist","description":"mist","icon":"50n"}]},"minutely":[{"dt":1607029980,"precipitation":0},{"dt":1607030040,"precipitation":0},{"dt":1607030100,"precipitation":0},{"dt":1607030160,"precipitation":0},{"dt":1607030220,"precipitation":0},{"dt":1607030280,"precipitation":0},{"dt":1607030340,"precipitation":0},{"dt":1607030400,"precipitation":0},{"dt":1607030460,"precipitation":0},{"dt":1607030520,"precipitation":0},{"dt":1607030580,"precipitation":0},{"dt":1607030640,"precipitation":0},{"dt":1607030700,"precipitation":0},{"dt":1607030760,"precipitation":0},{"dt":1607030820,"precipitation":0},{"dt":1607030880,"precipitation":0},{"dt":1607030940,"precipitation":0},{"dt":1607031000,"precipitation":0},{"dt":1607031060,"precipitation":0},{"dt":1607031120,"precipitation":0},{"dt":1607031180,"precipitation":0},{"dt":1607031240,"precipitation":0},{"dt":1607031300,"precipitation":0},{"dt":1607031360,"precipitation":0},{"dt":1607031420,"precipitation":0},{"dt":1607031480,"precipitation":0},{"dt":1607031540,"precipitation":0},{"dt":1607031600,"precipitation":0},{"dt":1607031660,"precipitation":0},{"dt":1607031720,"precipitation":0},{"dt":1607031780,"precipitation":0},{"dt":1607031840,"precipitation":0},{"dt":1607031900,"precipitation":0},{"dt":1607031960,"precipitation":0},{"dt":1607032020,"precipitation":0},{"dt":1607032080,"precipitation":0},{"dt":1607032140,"precipitation":0},{"dt":1607032200,"precipitation":0},{"dt":1607032260,"precipitation":0},{"dt":1607032320,"precipitation":0},{"dt":1607032380,"precipitation":0},{"dt":1607032440,"precipitation":0},{"dt":1607032500,"precipitation":0},{"dt":1607032560,"precipitation":0},{"dt":1607032620,"precipitation":0},{"dt":1607032680,"precipitation":0},{"dt":1607032740,"precipitation":0},{"dt":1607032800,"precipitation":0},{"dt":1607032860,"precipitation":0},{"dt":1607032920,"precipitation":0},{"dt":1607032980,"precipitation":0},{"dt":1607033040,"precipitation":0},{"dt":1607033100,"precipitation":0},{"dt":1607033160,"precipitation":0},{"dt":1607033220,"precipitation":0},{"dt":1607033280,"precipitation":0},{"dt":1607033340,"precipitation":0},{"dt":1607033400,"precipitation":0},{"dt":1607033460,"precipitation":0},{"dt":1607033520,"precipitation":0},{"dt":1607033580,"precipitation":0}],"hourly":[{"dt":1607029200,"temp":273.15,"feels_like":270.73,"pressure":1008,"humidity":97,"dew_point":272.78,"uvi":0,"clouds":100,"visibility":10000,"wind_speed":0.54,"wind_deg":279,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.29},{"dt":1607032800,"temp":272.59,"feels_like":269.76,"pressure":1007,"humidity":97,"dew_point":272.22,"uvi":0,"clouds":100,"visibility":10000,"wind_speed":1.01,"wind_deg":281,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.28},{"dt":1607036400,"temp":271.72,"feels_like":269.22,"pressure":1006,"humidity":96,"dew_point":271.23,"uvi":0,"clouds":100,"visibility":10000,"wind_speed":0.35,"wind_deg":160,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.25},{"dt":1607040000,"temp":271.02,"feels_like":268.22,"pressure":1005,"humidity":96,"dew_point":270.53,"uvi":0,"clouds":100,"visibility":10000,"wind_speed":0.65,"wind_deg":253,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.28},{"dt":1607043600,"temp":269.93,"feels_like":266.79,"pressure":1005,"humidity":96,"dew_point":269.45,"uvi":0,"clouds":56,"visibility":10000,"wind_speed":0.95,"wind_deg":275,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"pop":0.03},{"dt":1607047200,"temp":270.23,"feels_like":267.14,"pressure":1005,"humidity":96,"dew_point":268.24,"uvi":0,"clouds":77,"visibility":10000,"wind_speed":0.93,"wind_deg":282,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"pop":0},{"dt":1607050800,"temp":269.63,"feels_like":266.68,"pressure":1004,"humidity":95,"dew_point":267.52,"uvi":0,"clouds":63,"visibility":10000,"wind_speed":0.61,"wind_deg":273,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"pop":0},{"dt":1607054400,"temp":269.46,"feels_like":266.56,"pressure":1004,"humidity":95,"dew_point":267.3,"uvi":0,"clouds":48,"visibility":10000,"wind_speed":0.51,"wind_deg":249,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"pop":0},{"dt":1607058000,"temp":269.72,"feels_like":266.6,"pressure":1005,"humidity":95,"dew_point":267.49,"uvi":0,"clouds":38,"visibility":10000,"wind_speed":0.86,"wind_deg":283,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"pop":0},{"dt":1607061600,"temp":269.77,"feels_like":266.81,"pressure":1005,"humidity":95,"dew_point":267.52,"uvi":0,"clouds":34,"visibility":10000,"wind_speed":0.64,"wind_deg":275,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"pop":0},{"dt":1607065200,"temp":270.2,"feels_like":267.36,"pressure":1005,"humidity":95,"dew_point":267.95,"uvi":0,"clouds":0,"visibility":10000,"wind_speed":0.55,"wind_deg":270,"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01d"}],"pop":0},{"dt":1607068800,"temp":272.92,"feels_like":270.58,"pressure":1005,"humidity":96,"dew_point":270.77,"uvi":0.26,"clouds":49,"visibility":10000,"wind_speed":0.35,"wind_deg":167,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"pop":0},{"dt":1607072400,"temp":274.41,"feels_like":272.19,"pressure":1005,"humidity":90,"dew_point":272.76,"uvi":0.6,"clouds":65,"visibility":10000,"wind_speed":0.29,"wind_deg":144,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0},{"dt":1607076000,"temp":275.47,"feels_like":273.46,"pressure":1005,"humidity":91,"dew_point":274.22,"uvi":0.85,"clouds":70,"visibility":10000,"wind_speed":0.25,"wind_deg":276,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0},{"dt":1607079600,"temp":276.53,"feels_like":274.59,"pressure":1005,"humidity":92,"dew_point":275.48,"uvi":0.99,"clouds":76,"visibility":1176,"wind_speed":0.43,"wind_deg":266,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0},{"dt":1607083200,"temp":277.13,"feels_like":275.22,"pressure":1005,"humidity":92,"dew_point":276.08,"uvi":0.85,"clouds":76,"visibility":144,"wind_speed":0.54,"wind_deg":207,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"pop":0},{"dt":1607086800,"temp":277.25,"feels_like":275.29,"pressure":1005,"humidity":92,"dew_point":276.21,"uvi":0.55,"clouds":88,"visibility":49,"wind_speed":0.64,"wind_deg":165,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0},{"dt":1607090400,"temp":276.81,"feels_like":274.39,"pressure":1004,"humidity":92,"dew_point":275.77,"uvi":0.24,"clouds":94,"visibility":46,"wind_speed":1.18,"wind_deg":144,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0},{"dt":1607094000,"temp":276.37,"feels_like":273.83,"pressure":1004,"humidity":92,"dew_point":275.34,"uvi":0,"clouds":96,"visibility":46,"wind_speed":1.25,"wind_deg":140,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0},{"dt":1607097600,"temp":276.11,"feels_like":273.67,"pressure":1004,"humidity":92,"dew_point":275.05,"uvi":0,"clouds":97,"visibility":53,"wind_speed":1.04,"wind_deg":135,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607101200,"temp":276.12,"feels_like":273.64,"pressure":1004,"humidity":92,"dew_point":275.05,"uvi":0,"clouds":98,"visibility":65,"wind_speed":1.11,"wind_deg":122,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607104800,"temp":275.96,"feels_like":273.42,"pressure":1004,"humidity":92,"dew_point":274.88,"uvi":0,"clouds":98,"visibility":78,"wind_speed":1.15,"wind_deg":126,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607108400,"temp":275.85,"feels_like":273.46,"pressure":1005,"humidity":92,"dew_point":274.74,"uvi":0,"clouds":100,"visibility":528,"wind_speed":0.92,"wind_deg":122,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607112000,"temp":275.69,"feels_like":273.4,"pressure":1005,"humidity":92,"dew_point":274.57,"uvi":0,"clouds":100,"visibility":532,"wind_speed":0.73,"wind_deg":124,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607115600,"temp":275.71,"feels_like":273.5,"pressure":1005,"humidity":92,"dew_point":274.6,"uvi":0,"clouds":100,"visibility":572,"wind_speed":0.62,"wind_deg":117,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607119200,"temp":275.85,"feels_like":273.69,"pressure":1005,"humidity":92,"dew_point":274.74,"uvi":0,"clouds":100,"visibility":167,"wind_speed":0.59,"wind_deg":102,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607122800,"temp":276.05,"feels_like":273.81,"pressure":1005,"humidity":92,"dew_point":274.97,"uvi":0,"clouds":100,"visibility":136,"wind_speed":0.75,"wind_deg":97,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607126400,"temp":276.29,"feels_like":274.02,"pressure":1005,"humidity":92,"dew_point":275.21,"uvi":0,"clouds":100,"visibility":107,"wind_speed":0.84,"wind_deg":110,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0},{"dt":1607130000,"temp":276.69,"feels_like":274.45,"pressure":1005,"humidity":92,"dew_point":275.62,"uvi":0,"clouds":100,"visibility":103,"wind_speed":0.9,"wind_deg":113,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.02},{"dt":1607133600,"temp":276.89,"feels_like":274.53,"pressure":1005,"humidity":92,"dew_point":275.82,"uvi":0,"clouds":100,"visibility":78,"wind_speed":1.12,"wind_deg":114,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.02},{"dt":1607137200,"temp":276.93,"feels_like":274.51,"pressure":1004,"humidity":92,"dew_point":275.85,"uvi":0,"clouds":100,"visibility":61,"wind_speed":1.21,"wind_deg":122,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.02},{"dt":1607140800,"temp":277.06,"feels_like":274.62,"pressure":1004,"humidity":92,"dew_point":275.98,"uvi":0,"clouds":100,"visibility":62,"wind_speed":1.28,"wind_deg":122,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.02},{"dt":1607144400,"temp":277.19,"feels_like":274.7,"pressure":1004,"humidity":92,"dew_point":276.11,"uvi":0,"clouds":100,"visibility":66,"wind_speed":1.38,"wind_deg":114,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.02},{"dt":1607148000,"temp":277.12,"feels_like":274.57,"pressure":1004,"humidity":92,"dew_point":276.04,"uvi":0,"clouds":100,"visibility":58,"wind_speed":1.45,"wind_deg":105,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.02},{"dt":1607151600,"temp":277.12,"feels_like":274.92,"pressure":1005,"humidity":92,"dew_point":276.06,"uvi":0,"clouds":100,"visibility":58,"wind_speed":0.95,"wind_deg":98,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.03},{"dt":1607155200,"temp":277.54,"feels_like":275.27,"pressure":1005,"humidity":92,"dew_point":276.5,"uvi":0.17,"clouds":100,"visibility":53,"wind_speed":1.15,"wind_deg":101,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.04},{"dt":1607158800,"temp":278.04,"feels_like":275.8,"pressure":1005,"humidity":92,"dew_point":277.01,"uvi":0.4,"clouds":100,"visibility":49,"wind_speed":1.23,"wind_deg":95,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.03},{"dt":1607162400,"temp":278.63,"feels_like":276.64,"pressure":1005,"humidity":93,"dew_point":277.6,"uvi":0.44,"clouds":100,"visibility":51,"wind_speed":1.08,"wind_deg":92,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.02},{"dt":1607166000,"temp":279.08,"feels_like":277.01,"pressure":1004,"humidity":93,"dew_point":278.04,"uvi":0.52,"clouds":100,"visibility":49,"wind_speed":1.32,"wind_deg":88,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.02},{"dt":1607169600,"temp":279.19,"feels_like":277.03,"pressure":1004,"humidity":93,"dew_point":278.15,"uvi":0.44,"clouds":100,"visibility":44,"wind_speed":1.48,"wind_deg":87,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.03},{"dt":1607173200,"temp":278.94,"feels_like":276.77,"pressure":1004,"humidity":93,"dew_point":277.9,"uvi":0.35,"clouds":100,"visibility":47,"wind_speed":1.42,"wind_deg":89,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.13},{"dt":1607176800,"temp":278.63,"feels_like":276.51,"pressure":1004,"humidity":93,"dew_point":277.59,"uvi":0.15,"clouds":100,"visibility":55,"wind_speed":1.27,"wind_deg":94,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.09},{"dt":1607180400,"temp":278.36,"feels_like":276.14,"pressure":1004,"humidity":92,"dew_point":277.3,"uvi":0,"clouds":100,"visibility":70,"wind_speed":1.29,"wind_deg":90,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"pop":0.09},{"dt":1607184000,"temp":278.33,"feels_like":276.08,"pressure":1004,"humidity":92,"dew_point":277.27,"uvi":0,"clouds":100,"visibility":86,"wind_speed":1.32,"wind_deg":92,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.09},{"dt":1607187600,"temp":278.34,"feels_like":276.06,"pressure":1004,"humidity":92,"dew_point":277.28,"uvi":0,"clouds":100,"visibility":97,"wind_speed":1.37,"wind_deg":94,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.09},{"dt":1607191200,"temp":278.31,"feels_like":276.04,"pressure":1004,"humidity":92,"dew_point":277.25,"uvi":0,"clouds":100,"visibility":136,"wind_speed":1.35,"wind_deg":94,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.05},{"dt":1607194800,"temp":278.1,"feels_like":275.9,"pressure":1005,"humidity":92,"dew_point":277.05,"uvi":0,"clouds":95,"visibility":135,"wind_speed":1.2,"wind_deg":98,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.04},{"dt":1607198400,"temp":278.17,"feels_like":276.08,"pressure":1005,"humidity":92,"dew_point":277.1,"uvi":0,"clouds":97,"visibility":748,"wind_speed":1.06,"wind_deg":103,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"pop":0.04}],"daily":[{"dt":1606989600,"sunrise":1606976516,"sunset":1607008607,"temp":{"day":272.75,"min":271.15,"max":273.41,"night":272.59,"eve":272.39,"morn":271.2},"feels_like":{"day":270.38,"night":269.76,"eve":269.92,"morn":268.42},"pressure":1011,"humidity":97,"dew_point":271.03,"wind_speed":0.39,"wind_deg":351,"weather":[{"id":601,"main":"Snow","description":"snow","icon":"13d"}],"clouds":100,"pop":1,"snow":18.51,"uvi":0.59},{"dt":1607076000,"sunrise":1607062981,"sunset":1607094989,"temp":{"day":275.47,"min":269.46,"max":277.25,"night":275.85,"eve":276.11,"morn":269.46},"feels_like":{"day":273.46,"night":273.69,"eve":273.67,"morn":266.56},"pressure":1005,"humidity":91,"dew_point":274.22,"wind_speed":0.25,"wind_deg":276,"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"clouds":70,"pop":0.28,"uvi":0.99},{"dt":1607162400,"sunrise":1607149445,"sunset":1607181374,"temp":{"day":278.63,"min":276.05,"max":279.19,"night":278.2,"eve":278.33,"morn":277.06},"feels_like":{"day":276.64,"night":276.01,"eve":276.08,"morn":274.62},"pressure":1005,"humidity":93,"dew_point":277.6,"wind_speed":1.08,"wind_deg":92,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"clouds":100,"pop":0.13,"uvi":0.52},{"dt":1607248800,"sunrise":1607235908,"sunset":1607267762,"temp":{"day":278.52,"min":277.86,"max":279.07,"night":278.45,"eve":278.66,"morn":278.43},"feels_like":{"day":276.6,"night":276.4,"eve":276.55,"morn":276.41},"pressure":1000,"humidity":92,"dew_point":277.47,"wind_speed":0.91,"wind_deg":50,"weather":[{"id":501,"main":"Rain","description":"moderate rain","icon":"10d"}],"clouds":100,"pop":1,"rain":15.96,"uvi":0.27},{"dt":1607335200,"sunrise":1607322368,"sunset":1607354152,"temp":{"day":279.21,"min":276.57,"max":281.48,"night":278.63,"eve":278.95,"morn":277.6},"feels_like":{"day":277.66,"night":277.23,"eve":276.9,"morn":275.15},"pressure":1004,"humidity":88,"dew_point":277.39,"wind_speed":0.39,"wind_deg":186,"weather":[{"id":501,"main":"Rain","description":"moderate rain","icon":"10d"}],"clouds":10,"pop":1,"rain":17.35,"uvi":1.1},{"dt":1607421600,"sunrise":1607408827,"sunset":1607440545,"temp":{"day":278.84,"min":277.29,"max":279.64,"night":277.69,"eve":278.39,"morn":277.84},"feels_like":{"day":277.16,"night":275.96,"eve":276.59,"morn":276.11},"pressure":1006,"humidity":91,"dew_point":277.57,"wind_speed":0.61,"wind_deg":340,"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":100,"pop":0.95,"rain":0.83,"uvi":0.65},{"dt":1607508000,"sunrise":1607495284,"sunset":1607526940,"temp":{"day":278.22,"min":276.81,"max":278.22,"night":277.26,"eve":277.74,"morn":277.4},"feels_like":{"day":276.1,"night":274.29,"eve":275.7,"morn":275.2},"pressure":1007,"humidity":87,"dew_point":276.3,"wind_speed":0.91,"wind_deg":11,"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"clouds":87,"pop":0.27,"uvi":1},{"dt":1607594400,"sunrise":1607581739,"sunset":1607613338,"temp":{"day":276.3,"min":276.3,"max":276.99,"night":276.66,"eve":276.89,"morn":276.32},"feels_like":{"day":273.51,"night":274.55,"eve":274.76,"morn":272.89},"pressure":1008,"humidity":90,"dew_point":274.94,"wind_speed":1.52,"wind_deg":315,"weather":[{"id":616,"main":"Snow","description":"rain and snow","icon":"13d"}],"clouds":100,"pop":0.96,"rain":6.33,"snow":2.4,"uvi":1}],"alerts":[{"sender_name":"DHMZ Državni hidrometeorološki zavod","event":"Red snow ice warning","start":1606950000,"end":1607036399,"description":"Freezing rain. minimum temperature \u003c 0 °C"},{"sender_name":"DHMZ Državni hidrometeorološki zavod","event":"Yellow snow ice warning","start":1607036400,"end":1607068800,"description":"Black ice. minimum temperature \u003c 0 °C"}]}
\ No newline at end of file
+{
+ "lat": 45.5709,
+ "lon": 15.193,
+ "timezone": "Europe/Ljubljana",
+ "base64_str": "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gRHVpcyBxdWlzIGRpYW0gbm9uIG5pc2wgZGljdHVtIGZlcm1lbnR1bS4gTWFlY2VuYXMgdnVscHV0YXRlIGVyYXQgc2VkIHNvbGxpY2l0dWRpbiBpbXBlcmRpZXQuIFN1c3BlbmRpc3NlIGV1IG1hdXJpcyBhdWd1ZS4gUGVsbGVudGVzcXVlIHZhcml1cyBkYXBpYnVzIGR1aSwgbm9uIHZlaGljdWxhIGxpYmVybyB2YXJpdXMgZXUuIFBlbGxlbnRlc3F1ZSBjb25ndWUgbmliaCBwdXJ1cywgaW4gYWxpcXVldCBsaWd1bGEgdml2ZXJyYSBhYy4gSW50ZWdlciBzZWQgbmlzbCBldSBlcm9zIHNhZ2l0dGlzIGNvbmRpbWVudHVtIHZlbCBzaXQgYW1ldCBsZW8uIE51bGxhbSBzZWQgZmluaWJ1cyBuaWJoLiBDbGFzcyBhcHRlbnQgdGFjaXRpIHNvY2lvc3F1IGFkIGxpdG9yYSB0b3JxdWVudCBwZXIgY29udWJpYSBub3N0cmEsIHBlciBpbmNlcHRvcyBoaW1lbmFlb3MuIEFsaXF1YW0gdmVsIHNvZGFsZXMganVzdG8sIGV0IHBvc3VlcmUgbWFnbmEuIEV0aWFtIGVsZW1lbnR1bSBmZWxpcyB1dCBsZWN0dXMgdGluY2lkdW50IGdyYXZpZGEuIE51bGxhbSBmYXVjaWJ1cyBhbGlxdWV0IHRlbGx1cyB2aXRhZSBzb2RhbGVzLiBBbGlxdWFtIHNjZWxlcmlzcXVlIGZlcm1lbnR1bSB2dWxwdXRhdGUuIEN1cmFiaXR1ciBzaXQgYW1ldCBlcmF0IGhlbmRyZXJpdCwgdWx0cmljZXMgbWV0dXMgc2l0IGFtZXQsIG1hbGVzdWFkYSBuaXNsLiBDdXJhYml0dXIgZXUgdG9ydG9yIG1hbGVzdWFkYSBmZWxpcyB2aXZlcnJhIHB1bHZpbmFyLiBOdWxsYSBlbGVtZW50dW0gdmVsIHVybmEgaWQgcGhhcmV0cmEuCgpBbGlxdWFtIHVsdHJpY2VzIHNpdCBhbWV0IGxhY3VzIHNlZCBiaWJlbmR1bS4gSW4gY29udmFsbGlzIGVyYXQgdmVsIGRpYW0gbGFjaW5pYSB1bHRyaWNpZXMuIFNlZCBhdCBjb25ndWUgbmlzaS4gRnVzY2UgdmFyaXVzIGxhY2luaWEgbGVvLCB2aXRhZSB0ZW1wdXMgbGVvIG1heGltdXMgZXQuIE51bGxhIG5lYyBkYXBpYnVzIGVyb3MuIEV0aWFtIGF0IGZlcm1lbnR1bSByaXN1cywgYWMgcHJldGl1bSBmZWxpcy4gRnVzY2UgcXVpcyBqdXN0byBhbGlxdWFtLCBmaW5pYnVzIG1ldHVzIGlkLCBzb2RhbGVzIG1hdXJpcy4gQ3JhcyB2ZXN0aWJ1bHVtIHB1cnVzIHF1YW0sIGlkIHZlc3RpYnVsdW0gbnVuYyBjb252YWxsaXMgZXQuIE1hZWNlbmFzIGp1c3RvIGxvcmVtLCB2YXJpdXMgYSBsYW9yZWV0IGVnZXQsIGZpbmlidXMgZXUgbGlndWxhLiBQcmFlc2VudCBmYWNpbGlzaXMsIG1pIGJsYW5kaXQgbW9sZXN0aWUgYXVjdG9yLCB0ZWxsdXMgc2VtIHVsdHJpY2llcyBuZXF1ZSwgdml0YWUgdm9sdXRwYXQgZW5pbSBsZW8gZXQgYXVndWUuIFBoYXNlbGx1cyB2ZXN0aWJ1bHVtIGNvbnNlY3RldHVyIGNvbW1vZG8uCgpEb25lYyBmZXJtZW50dW0gb2RpbyBldSBwdXJ1cyBpbnRlcmR1bSB0cmlzdGlxdWUuIER1aXMgaGVuZHJlcml0IG5lcXVlIGRvbG9yLCB1dCBjb25zZXF1YXQgc2VtIHBvcnRhIHF1aXMuIENyYXMgZGljdHVtIGNvbmd1ZSBpbnRlcmR1bS4gVXQgbmVjIGR1aSBpbiBhdWd1ZSBsYW9yZWV0IHRpbmNpZHVudCBzaXQgYW1ldCBhIGxhY3VzLiBWaXZhbXVzIGZyaW5naWxsYSBvcmNpIGF0IHZpdmVycmEgdGluY2lkdW50LiBEdWlzIGF1Y3RvciwgZmVsaXMgdm9sdXRwYXQgdGVtcHVzIGNvbW1vZG8sIGxlbyBwdXJ1cyBjb25ndWUgbGVjdHVzLCBldCBtb2xsaXMgZW5pbSBhbnRlIHF1aXMgdXJuYS4gVmVzdGlidWx1bSBxdWlzIG1hZ25hIGEgdmVsaXQgZGlnbmlzc2ltIGV1aXNtb2QgbGFjaW5pYSBpbiBlbmltLiBJbiBtYXR0aXMgc2VkIGRpYW0gbmVjIGF1Y3Rvci4gRnVzY2Ugc2l0IGFtZXQgZXN0IHR1cnBpcy4gSW4gZXUgdG9ydG9yIGVyb3MuIENyYXMgbG9ib3J0aXMgZGFwaWJ1cyBzZW0gbmVjIGxhY2luaWEuIFN1c3BlbmRpc3NlIG1hZ25hIGFyY3UsIHJ1dHJ1bSB1dCBkaWN0dW0gc2VkLCBzY2VsZXJpc3F1ZSBpbiBlbGl0LiBEdWlzIHZ1bHB1dGF0ZSBjdXJzdXMgZWZmaWNpdHVyLiBQZWxsZW50ZXNxdWUgYmxhbmRpdCBmZXJtZW50dW0gZXN0LiBDcmFzIHZhcml1cyBpcHN1bSB1cm5hLCBhIGhlbmRyZXJpdCBzYXBpZW4gcnV0cnVtIGF0LiBOYW0gZ3JhdmlkYSBxdWlzIG1pIGV0IGF1Y3Rvci4KCkRvbmVjIHBvcnRhIG1hdXJpcyBzaXQgYW1ldCB2aXZlcnJhIHVsdHJpY2VzLiBEb25lYyBsdWN0dXMgcHJldGl1bSBvcmNpIGV0IG9ybmFyZS4gTW9yYmkgYWMgbGFjdXMgYWxpcXVhbSwgY3Vyc3VzIHB1cnVzIHZpdGFlLCBncmF2aWRhIGRvbG9yLiBGdXNjZSBzZWQgdmVsaXQgbW9sbGlzLCBzZW1wZXIgbGlndWxhIGlkLCBtYXR0aXMgc2VtLiBWZXN0aWJ1bHVtIHVsdHJpY2llcyBzZW0gbm9uIG9kaW8gaGVuZHJlcml0IGRhcGlidXMuIFBoYXNlbGx1cyB2b2x1dHBhdCBhdWd1ZSB2ZWwgbmliaCB2YXJpdXMsIGluIGRpZ25pc3NpbSBtZXR1cyBzZW1wZXIuIFN1c3BlbmRpc3NlIGxvYm9ydGlzIGxlY3R1cyBldSBxdWFtIGNvbmd1ZSwgcXVpcyB2dWxwdXRhdGUgYXJjdSBiaWJlbmR1bS4gU3VzcGVuZGlzc2UgdGVtcHVzIG1hZ25hIHZlbmVuYXRpcyBwdXJ1cyB2ZXN0aWJ1bHVtIGlhY3VsaXMuIE51bGxhbSBpbnRlcmR1bSB0cmlzdGlxdWUgbmVxdWUgZWdldCBlZmZpY2l0dXIuIER1aXMgZmluaWJ1cywgdmVsaXQgdWxsYW1jb3JwZXIgc2NlbGVyaXNxdWUgaGVuZHJlcml0LCBuaXNpIG51bmMgbGFvcmVldCBkb2xvciwgdGVtcHVzIHJ1dHJ1bSBhbnRlIGxlbyBhIGxlY3R1cy4gRG9uZWMgdml0YWUgcG9ydGEgbG9yZW0sIGVnZXQgc3VzY2lwaXQgbGVjdHVzLiBNYXVyaXMgYWMgbGVvIG1pLiBNYXVyaXMgZXVpc21vZCBzZW0gZXUgbGlndWxhIGNvbnNlY3RldHVyIGNvbmRpbWVudHVtLgoKTW9yYmkgdWxsYW1jb3JwZXIgdm9sdXRwYXQgbWFzc2Egbm9uIGFsaXF1ZXQuIE1hdXJpcyB2aXRhZSBmcmluZ2lsbGEgbWF1cmlzLiBTZWQgbmliaCBtYWduYSwgdmVuZW5hdGlzIHF1aXMgbWF4aW11cyBhdCwgbWF0dGlzIG5lYyBudW5jLiBVdCByaG9uY3VzIGFudGUgYXQgZGFwaWJ1cyBmYWNpbGlzaXMuIEN1cmFiaXR1ciBhY2N1bXNhbiBpZCBhdWd1ZSBuZWMgbGFjaW5pYS4gUGhhc2VsbHVzIGNvbnNlcXVhdCBoZW5kcmVyaXQgbWFzc2EuIEFsaXF1YW0gbHVjdHVzIGVmZmljaXR1ciBjb25zZWN0ZXR1ci4gSW50ZWdlciBmZXJtZW50dW0gc2FwaWVuIGZldWdpYXQgZmVsaXMgdmVoaWN1bGEsIG5vbiBlbGVtZW50dW0gbnVsbGEgZ3JhdmlkYS4gSW4gdmVsIHVsdHJpY2llcyBleC4gUHJhZXNlbnQgcGhhcmV0cmEgYSB0dXJwaXMgdml0YWUgbGFvcmVldC4gRXRpYW0gYWNjdW1zYW4gc2VtcGVyIHR1cnBpcyBlZ2V0IHBvcnR0aXRvci4gUGhhc2VsbHVzIGFjIGZlbGlzIGV1IHR1cnBpcyBtYXhpbXVzIG1vbGxpcy4gRG9uZWMgY3Vyc3VzIGRpYW0gbmVjIGp1c3RvIGZhY2lsaXNpcywgc2VkIG1vbGxpcyBudWxsYSBoZW5kcmVyaXQuIE5hbSBydXRydW0sIG1hc3NhIG5vbiBibGFuZGl0IHZvbHV0cGF0LCBsYWN1cyB0ZWxsdXMgYWxpcXVhbSB0b3J0b3IsIHV0IGZyaW5naWxsYSBuaWJoIHNhcGllbiB2aXRhZSBzYXBpZW4uIENsYXNzIGFwdGVudCB0YWNpdGkgc29jaW9zcXUgYWQgbGl0b3JhIHRvcnF1ZW50IHBlciBjb251YmlhIG5vc3RyYSwgcGVyIGluY2VwdG9zIGhpbWVuYWVvcy4=",
+ "timezone_offset": 7200,
+ "current": {
+ "dt": 1650206004,
+ "sunrise": 1650168621,
+ "sunset": 1650217613,
+ "temp": 10.79,
+ "feels_like": 9,
+ "pressure": 1019,
+ "humidity": 41,
+ "dew_point": -1.7,
+ "uvi": 1.37,
+ "clouds": 90,
+ "visibility": 10000,
+ "wind_speed": 5.89,
+ "wind_deg": 48,
+ "wind_gust": 10.64,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ]
+ },
+ "minutely": [
+ {
+ "dt": 1650206040,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206100,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206160,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206220,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206280,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206340,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206400,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206460,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206520,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206580,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206640,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206700,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206760,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206820,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206880,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650206940,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207000,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207060,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207120,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207180,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207240,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207300,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207360,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207420,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207480,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207540,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207600,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207660,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207720,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207780,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207840,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207900,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650207960,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208020,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208080,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208140,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208200,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208260,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208320,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208380,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208440,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208500,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208560,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208620,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208680,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208740,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208800,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208860,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208920,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650208980,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209040,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209100,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209160,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209220,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209280,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209340,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209400,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209460,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209520,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209580,
+ "precipitation": 0
+ },
+ {
+ "dt": 1650209640,
+ "precipitation": 0
+ }
+ ],
+ "hourly": [
+ {
+ "dt": 1650204000,
+ "temp": 11.14,
+ "feels_like": 9.35,
+ "pressure": 1019,
+ "humidity": 40,
+ "dew_point": -1.72,
+ "uvi": 2.51,
+ "clouds": 91,
+ "visibility": 10000,
+ "wind_speed": 6.47,
+ "wind_deg": 48,
+ "wind_gust": 10.48,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650207600,
+ "temp": 10.79,
+ "feels_like": 9,
+ "pressure": 1019,
+ "humidity": 41,
+ "dew_point": -1.7,
+ "uvi": 1.37,
+ "clouds": 90,
+ "visibility": 10000,
+ "wind_speed": 5.89,
+ "wind_deg": 48,
+ "wind_gust": 10.64,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650211200,
+ "temp": 10.91,
+ "feels_like": 9.15,
+ "pressure": 1019,
+ "humidity": 42,
+ "dew_point": -1.31,
+ "uvi": 0.57,
+ "clouds": 88,
+ "visibility": 10000,
+ "wind_speed": 5.55,
+ "wind_deg": 49,
+ "wind_gust": 11.15,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650214800,
+ "temp": 10.37,
+ "feels_like": 8.64,
+ "pressure": 1019,
+ "humidity": 45,
+ "dew_point": -0.92,
+ "uvi": 0.14,
+ "clouds": 81,
+ "visibility": 10000,
+ "wind_speed": 5.09,
+ "wind_deg": 53,
+ "wind_gust": 11.56,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650218400,
+ "temp": 9.02,
+ "feels_like": 6.91,
+ "pressure": 1020,
+ "humidity": 50,
+ "dew_point": -0.74,
+ "uvi": 0,
+ "clouds": 71,
+ "visibility": 10000,
+ "wind_speed": 3.77,
+ "wind_deg": 53,
+ "wind_gust": 10.47,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650222000,
+ "temp": 7.03,
+ "feels_like": 5.38,
+ "pressure": 1021,
+ "humidity": 61,
+ "dew_point": 0.03,
+ "uvi": 0,
+ "clouds": 24,
+ "visibility": 10000,
+ "wind_speed": 2.4,
+ "wind_deg": 48,
+ "wind_gust": 7.74,
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650225600,
+ "temp": 4.9,
+ "feels_like": 3.46,
+ "pressure": 1021,
+ "humidity": 71,
+ "dew_point": -1,
+ "uvi": 0,
+ "clouds": 7,
+ "visibility": 10000,
+ "wind_speed": 1.81,
+ "wind_deg": 26,
+ "wind_gust": 3.92,
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650229200,
+ "temp": 4.22,
+ "feels_like": 2.75,
+ "pressure": 1021,
+ "humidity": 73,
+ "dew_point": -1.07,
+ "uvi": 0,
+ "clouds": 7,
+ "visibility": 10000,
+ "wind_speed": 1.74,
+ "wind_deg": 6,
+ "wind_gust": 3.2,
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650232800,
+ "temp": 3.97,
+ "feels_like": 2.43,
+ "pressure": 1021,
+ "humidity": 74,
+ "dew_point": -1.22,
+ "uvi": 0,
+ "clouds": 8,
+ "visibility": 10000,
+ "wind_speed": 1.77,
+ "wind_deg": 357,
+ "wind_gust": 3.31,
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650236400,
+ "temp": 3.7,
+ "feels_like": 2.1,
+ "pressure": 1021,
+ "humidity": 74,
+ "dew_point": -1.51,
+ "uvi": 0,
+ "clouds": 19,
+ "visibility": 10000,
+ "wind_speed": 1.78,
+ "wind_deg": 343,
+ "wind_gust": 2.41,
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650240000,
+ "temp": 3.66,
+ "feels_like": 2.18,
+ "pressure": 1020,
+ "humidity": 72,
+ "dew_point": -1.79,
+ "uvi": 0,
+ "clouds": 27,
+ "visibility": 10000,
+ "wind_speed": 1.68,
+ "wind_deg": 335,
+ "wind_gust": 2.59,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650243600,
+ "temp": 3.21,
+ "feels_like": 1.91,
+ "pressure": 1020,
+ "humidity": 73,
+ "dew_point": -2.2,
+ "uvi": 0,
+ "clouds": 65,
+ "visibility": 10000,
+ "wind_speed": 1.5,
+ "wind_deg": 353,
+ "wind_gust": 2.05,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650247200,
+ "temp": 2.4,
+ "feels_like": 1.07,
+ "pressure": 1019,
+ "humidity": 74,
+ "dew_point": -2.63,
+ "uvi": 0,
+ "clouds": 41,
+ "visibility": 10000,
+ "wind_speed": 1.44,
+ "wind_deg": 347,
+ "wind_gust": 1.63,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650250800,
+ "temp": 2.07,
+ "feels_like": 0.62,
+ "pressure": 1019,
+ "humidity": 75,
+ "dew_point": -2.86,
+ "uvi": 0,
+ "clouds": 39,
+ "visibility": 10000,
+ "wind_speed": 1.49,
+ "wind_deg": 336,
+ "wind_gust": 1.61,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650254400,
+ "temp": 1.85,
+ "feels_like": 0.51,
+ "pressure": 1019,
+ "humidity": 76,
+ "dew_point": -2.98,
+ "uvi": 0,
+ "clouds": 47,
+ "visibility": 10000,
+ "wind_speed": 1.4,
+ "wind_deg": 331,
+ "wind_gust": 1.47,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650258000,
+ "temp": 3.03,
+ "feels_like": 3.03,
+ "pressure": 1019,
+ "humidity": 71,
+ "dew_point": -2.6,
+ "uvi": 0.15,
+ "clouds": 56,
+ "visibility": 10000,
+ "wind_speed": 1.26,
+ "wind_deg": 329,
+ "wind_gust": 1.83,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650261600,
+ "temp": 5.55,
+ "feels_like": 4.67,
+ "pressure": 1019,
+ "humidity": 61,
+ "dew_point": -2.31,
+ "uvi": 0.57,
+ "clouds": 59,
+ "visibility": 10000,
+ "wind_speed": 1.43,
+ "wind_deg": 18,
+ "wind_gust": 4.99,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650265200,
+ "temp": 7.58,
+ "feels_like": 5.8,
+ "pressure": 1018,
+ "humidity": 53,
+ "dew_point": -2.37,
+ "uvi": 1.4,
+ "clouds": 50,
+ "visibility": 10000,
+ "wind_speed": 2.71,
+ "wind_deg": 44,
+ "wind_gust": 7.12,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650268800,
+ "temp": 9.16,
+ "feels_like": 7.06,
+ "pressure": 1018,
+ "humidity": 47,
+ "dew_point": -2.36,
+ "uvi": 2.56,
+ "clouds": 56,
+ "visibility": 10000,
+ "wind_speed": 3.81,
+ "wind_deg": 53,
+ "wind_gust": 8.78,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650272400,
+ "temp": 10.37,
+ "feels_like": 8.59,
+ "pressure": 1018,
+ "humidity": 43,
+ "dew_point": -2.61,
+ "uvi": 3.79,
+ "clouds": 43,
+ "visibility": 10000,
+ "wind_speed": 4.56,
+ "wind_deg": 54,
+ "wind_gust": 8.19,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650276000,
+ "temp": 11.35,
+ "feels_like": 9.56,
+ "pressure": 1017,
+ "humidity": 39,
+ "dew_point": -2.89,
+ "uvi": 4.66,
+ "clouds": 37,
+ "visibility": 10000,
+ "wind_speed": 4.84,
+ "wind_deg": 50,
+ "wind_gust": 7.91,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650279600,
+ "temp": 12.28,
+ "feels_like": 10.5,
+ "pressure": 1016,
+ "humidity": 36,
+ "dew_point": -3.17,
+ "uvi": 5.01,
+ "clouds": 48,
+ "visibility": 10000,
+ "wind_speed": 4.75,
+ "wind_deg": 49,
+ "wind_gust": 7.84,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650283200,
+ "temp": 13.02,
+ "feels_like": 11.24,
+ "pressure": 1015,
+ "humidity": 33,
+ "dew_point": -3.52,
+ "uvi": 4.66,
+ "clouds": 56,
+ "visibility": 10000,
+ "wind_speed": 4.55,
+ "wind_deg": 55,
+ "wind_gust": 7.83,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650286800,
+ "temp": 13.19,
+ "feels_like": 11.43,
+ "pressure": 1015,
+ "humidity": 33,
+ "dew_point": -3.41,
+ "uvi": 3.51,
+ "clouds": 97,
+ "visibility": 10000,
+ "wind_speed": 4.61,
+ "wind_deg": 64,
+ "wind_gust": 7.28,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650290400,
+ "temp": 12.65,
+ "feels_like": 10.88,
+ "pressure": 1014,
+ "humidity": 35,
+ "dew_point": -3.24,
+ "uvi": 2.37,
+ "clouds": 98,
+ "visibility": 10000,
+ "wind_speed": 3.8,
+ "wind_deg": 65,
+ "wind_gust": 5.81,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650294000,
+ "temp": 12.21,
+ "feels_like": 10.48,
+ "pressure": 1014,
+ "humidity": 38,
+ "dew_point": -2.55,
+ "uvi": 1.29,
+ "clouds": 99,
+ "visibility": 10000,
+ "wind_speed": 3.8,
+ "wind_deg": 68,
+ "wind_gust": 5.17,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650297600,
+ "temp": 11.44,
+ "feels_like": 9.81,
+ "pressure": 1015,
+ "humidity": 45,
+ "dew_point": -0.95,
+ "uvi": 0.48,
+ "clouds": 99,
+ "visibility": 10000,
+ "wind_speed": 3.05,
+ "wind_deg": 65,
+ "wind_gust": 5.02,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650301200,
+ "temp": 9.82,
+ "feels_like": 8.51,
+ "pressure": 1015,
+ "humidity": 52,
+ "dew_point": -0.56,
+ "uvi": 0.12,
+ "clouds": 99,
+ "visibility": 10000,
+ "wind_speed": 2.65,
+ "wind_deg": 59,
+ "wind_gust": 6.31,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650304800,
+ "temp": 6.91,
+ "feels_like": 6.13,
+ "pressure": 1017,
+ "humidity": 62,
+ "dew_point": -0.81,
+ "uvi": 0,
+ "clouds": 99,
+ "visibility": 10000,
+ "wind_speed": 1.5,
+ "wind_deg": 66,
+ "wind_gust": 1.86,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650308400,
+ "temp": 6.01,
+ "feels_like": 6.01,
+ "pressure": 1017,
+ "humidity": 64,
+ "dew_point": -1.14,
+ "uvi": 0,
+ "clouds": 97,
+ "visibility": 10000,
+ "wind_speed": 0.09,
+ "wind_deg": 358,
+ "wind_gust": 0.58,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650312000,
+ "temp": 5.61,
+ "feels_like": 5.61,
+ "pressure": 1018,
+ "humidity": 66,
+ "dew_point": -1.28,
+ "uvi": 0,
+ "clouds": 95,
+ "visibility": 10000,
+ "wind_speed": 1.25,
+ "wind_deg": 277,
+ "wind_gust": 1.09,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650315600,
+ "temp": 5.17,
+ "feels_like": 5.17,
+ "pressure": 1018,
+ "humidity": 67,
+ "dew_point": -1.35,
+ "uvi": 0,
+ "clouds": 96,
+ "visibility": 10000,
+ "wind_speed": 1.24,
+ "wind_deg": 277,
+ "wind_gust": 1.14,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650319200,
+ "temp": 4.41,
+ "feels_like": 4.41,
+ "pressure": 1017,
+ "humidity": 70,
+ "dew_point": -1.55,
+ "uvi": 0,
+ "clouds": 94,
+ "visibility": 10000,
+ "wind_speed": 0.91,
+ "wind_deg": 288,
+ "wind_gust": 0.95,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650322800,
+ "temp": 4.03,
+ "feels_like": 4.03,
+ "pressure": 1017,
+ "humidity": 71,
+ "dew_point": -1.69,
+ "uvi": 0,
+ "clouds": 92,
+ "visibility": 10000,
+ "wind_speed": 1.03,
+ "wind_deg": 277,
+ "wind_gust": 1.01,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650326400,
+ "temp": 3.82,
+ "feels_like": 3.82,
+ "pressure": 1017,
+ "humidity": 71,
+ "dew_point": -1.86,
+ "uvi": 0,
+ "clouds": 91,
+ "visibility": 10000,
+ "wind_speed": 1.3,
+ "wind_deg": 270,
+ "wind_gust": 1.13,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650330000,
+ "temp": 3.39,
+ "feels_like": 2.33,
+ "pressure": 1016,
+ "humidity": 72,
+ "dew_point": -2.13,
+ "uvi": 0,
+ "clouds": 70,
+ "visibility": 10000,
+ "wind_speed": 1.35,
+ "wind_deg": 272,
+ "wind_gust": 1.21,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650333600,
+ "temp": 3.31,
+ "feels_like": 3.31,
+ "pressure": 1016,
+ "humidity": 71,
+ "dew_point": -2.34,
+ "uvi": 0,
+ "clouds": 80,
+ "visibility": 10000,
+ "wind_speed": 1.15,
+ "wind_deg": 280,
+ "wind_gust": 1.07,
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650337200,
+ "temp": 3.46,
+ "feels_like": 3.46,
+ "pressure": 1016,
+ "humidity": 70,
+ "dew_point": -2.4,
+ "uvi": 0,
+ "clouds": 87,
+ "visibility": 10000,
+ "wind_speed": 1,
+ "wind_deg": 281,
+ "wind_gust": 0.93,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650340800,
+ "temp": 3.45,
+ "feels_like": 3.45,
+ "pressure": 1016,
+ "humidity": 70,
+ "dew_point": -2.52,
+ "uvi": 0,
+ "clouds": 90,
+ "visibility": 10000,
+ "wind_speed": 0.91,
+ "wind_deg": 279,
+ "wind_gust": 0.86,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650344400,
+ "temp": 4.72,
+ "feels_like": 4.72,
+ "pressure": 1016,
+ "humidity": 66,
+ "dew_point": -1.94,
+ "uvi": 0.09,
+ "clouds": 92,
+ "visibility": 10000,
+ "wind_speed": 0.41,
+ "wind_deg": 270,
+ "wind_gust": 0.49,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650348000,
+ "temp": 6.49,
+ "feels_like": 6.49,
+ "pressure": 1016,
+ "humidity": 60,
+ "dew_point": -1.71,
+ "uvi": 0.36,
+ "clouds": 93,
+ "visibility": 10000,
+ "wind_speed": 0.47,
+ "wind_deg": 107,
+ "wind_gust": 0.59,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650351600,
+ "temp": 7.94,
+ "feels_like": 7.94,
+ "pressure": 1015,
+ "humidity": 55,
+ "dew_point": -1.42,
+ "uvi": 0.62,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.26,
+ "wind_deg": 108,
+ "wind_gust": 1.57,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650355200,
+ "temp": 8.92,
+ "feels_like": 8.42,
+ "pressure": 1015,
+ "humidity": 53,
+ "dew_point": -1.03,
+ "uvi": 1.13,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.5,
+ "wind_deg": 119,
+ "wind_gust": 1.81,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ },
+ {
+ "dt": 1650358800,
+ "temp": 9.52,
+ "feels_like": 9.11,
+ "pressure": 1015,
+ "humidity": 52,
+ "dew_point": -0.66,
+ "uvi": 1.67,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.49,
+ "wind_deg": 126,
+ "wind_gust": 1.82,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0.04
+ },
+ {
+ "dt": 1650362400,
+ "temp": 10.25,
+ "feels_like": 8.69,
+ "pressure": 1014,
+ "humidity": 52,
+ "dew_point": -0.14,
+ "uvi": 2.64,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.44,
+ "wind_deg": 128,
+ "wind_gust": 1.92,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0.04
+ },
+ {
+ "dt": 1650366000,
+ "temp": 10.95,
+ "feels_like": 9.43,
+ "pressure": 1013,
+ "humidity": 51,
+ "dew_point": 0.33,
+ "uvi": 2.83,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.47,
+ "wind_deg": 124,
+ "wind_gust": 2.11,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0.04
+ },
+ {
+ "dt": 1650369600,
+ "temp": 11.83,
+ "feels_like": 10.37,
+ "pressure": 1013,
+ "humidity": 50,
+ "dew_point": 0.91,
+ "uvi": 2.64,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.54,
+ "wind_deg": 132,
+ "wind_gust": 2.6,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0.04
+ },
+ {
+ "dt": 1650373200,
+ "temp": 12.56,
+ "feels_like": 11.18,
+ "pressure": 1012,
+ "humidity": 50,
+ "dew_point": 1.53,
+ "uvi": 1.77,
+ "clouds": 100,
+ "visibility": 10000,
+ "wind_speed": 1.63,
+ "wind_deg": 155,
+ "wind_gust": 2.87,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "pop": 0
+ }
+ ],
+ "daily": [
+ {
+ "dt": 1650189600,
+ "sunrise": 1650168621,
+ "sunset": 1650217613,
+ "moonrise": 1650221520,
+ "moonset": 1650170220,
+ "moon_phase": 0.52,
+ "temp": {
+ "day": 10.24,
+ "min": 4.14,
+ "max": 11.52,
+ "night": 4.22,
+ "eve": 10.91,
+ "morn": 4.14
+ },
+ "feels_like": {
+ "day": 8.39,
+ "night": 2.75,
+ "eve": 9.15,
+ "morn": 0.12
+ },
+ "pressure": 1023,
+ "humidity": 41,
+ "dew_point": -3.48,
+ "wind_speed": 7.1,
+ "wind_deg": 58,
+ "wind_gust": 14.94,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 33,
+ "pop": 0.11,
+ "uvi": 5.01
+ },
+ {
+ "dt": 1650276000,
+ "sunrise": 1650254916,
+ "sunset": 1650304091,
+ "moonrise": 1650312840,
+ "moonset": 1650258120,
+ "moon_phase": 0.56,
+ "temp": {
+ "day": 11.35,
+ "min": 1.85,
+ "max": 13.19,
+ "night": 5.17,
+ "eve": 11.44,
+ "morn": 1.85
+ },
+ "feels_like": {
+ "day": 9.56,
+ "night": 5.17,
+ "eve": 9.81,
+ "morn": 0.51
+ },
+ "pressure": 1017,
+ "humidity": 39,
+ "dew_point": -2.89,
+ "wind_speed": 4.84,
+ "wind_deg": 50,
+ "wind_gust": 8.78,
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 37,
+ "pop": 0,
+ "uvi": 5.01
+ },
+ {
+ "dt": 1650362400,
+ "sunrise": 1650341212,
+ "sunset": 1650390569,
+ "moonrise": 1650404160,
+ "moonset": 1650346440,
+ "moon_phase": 0.6,
+ "temp": {
+ "day": 10.25,
+ "min": 3.31,
+ "max": 12.56,
+ "night": 5.52,
+ "eve": 11.97,
+ "morn": 3.45
+ },
+ "feels_like": {
+ "day": 8.69,
+ "night": 5.52,
+ "eve": 10.68,
+ "morn": 3.45
+ },
+ "pressure": 1014,
+ "humidity": 52,
+ "dew_point": -0.14,
+ "wind_speed": 1.63,
+ "wind_deg": 155,
+ "wind_gust": 3.27,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 100,
+ "pop": 0.05,
+ "uvi": 2.83
+ },
+ {
+ "dt": 1650448800,
+ "sunrise": 1650427509,
+ "sunset": 1650477047,
+ "moonrise": 0,
+ "moonset": 1650435240,
+ "moon_phase": 0.64,
+ "temp": {
+ "day": 4.99,
+ "min": 2.79,
+ "max": 7.64,
+ "night": 2.79,
+ "eve": 7.64,
+ "morn": 6.49
+ },
+ "feels_like": {
+ "day": 4.99,
+ "night": 2.79,
+ "eve": 7.64,
+ "morn": 6.49
+ },
+ "pressure": 1018,
+ "humidity": 97,
+ "dew_point": 3.52,
+ "wind_speed": 1.3,
+ "wind_deg": 32,
+ "wind_gust": 3.75,
+ "weather": [
+ {
+ "id": 501,
+ "main": "Rain",
+ "description": "moderate rain",
+ "icon": "10d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 100,
+ "pop": 1,
+ "rain": 6.92,
+ "uvi": 3.29
+ },
+ {
+ "dt": 1650535200,
+ "sunrise": 1650513807,
+ "sunset": 1650563525,
+ "moonrise": 1650495120,
+ "moonset": 1650524760,
+ "moon_phase": 0.67,
+ "temp": {
+ "day": 10.26,
+ "min": 1.2,
+ "max": 14.14,
+ "night": 9.05,
+ "eve": 14.14,
+ "morn": 1.2
+ },
+ "feels_like": {
+ "day": 9.01,
+ "night": 9.05,
+ "eve": 13.18,
+ "morn": 1.2
+ },
+ "pressure": 1014,
+ "humidity": 64,
+ "dew_point": 2.65,
+ "wind_speed": 1.59,
+ "wind_deg": 136,
+ "wind_gust": 2.04,
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 100,
+ "pop": 0,
+ "uvi": 5.04
+ },
+ {
+ "dt": 1650621600,
+ "sunrise": 1650600106,
+ "sunset": 1650650003,
+ "moonrise": 1650585540,
+ "moonset": 1650614940,
+ "moon_phase": 0.71,
+ "temp": {
+ "day": 8.81,
+ "min": 8.55,
+ "max": 9.77,
+ "night": 8.6,
+ "eve": 9.77,
+ "morn": 8.55
+ },
+ "feels_like": {
+ "day": 8.81,
+ "night": 8.17,
+ "eve": 9.3,
+ "morn": 8.55
+ },
+ "pressure": 1005,
+ "humidity": 96,
+ "dew_point": 7.11,
+ "wind_speed": 1.58,
+ "wind_deg": 318,
+ "wind_gust": 3.65,
+ "weather": [
+ {
+ "id": 501,
+ "main": "Rain",
+ "description": "moderate rain",
+ "icon": "10d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 100,
+ "pop": 0.96,
+ "rain": 7.26,
+ "uvi": 6
+ },
+ {
+ "dt": 1650708000,
+ "sunrise": 1650686407,
+ "sunset": 1650736481,
+ "moonrise": 1650675060,
+ "moonset": 1650705780,
+ "moon_phase": 0.75,
+ "temp": {
+ "day": 16.34,
+ "min": 8.68,
+ "max": 18.34,
+ "night": 9.74,
+ "eve": 17.68,
+ "morn": 8.68
+ },
+ "feels_like": {
+ "day": 15.81,
+ "night": 9.23,
+ "eve": 17.04,
+ "morn": 8.68
+ },
+ "pressure": 1007,
+ "humidity": 68,
+ "dew_point": 9.46,
+ "wind_speed": 4.27,
+ "wind_deg": 244,
+ "wind_gust": 6.63,
+ "weather": [
+ {
+ "id": 500,
+ "main": "Rain",
+ "description": "light rain",
+ "icon": "10d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 52,
+ "pop": 0.59,
+ "rain": 0.97,
+ "uvi": 6
+ },
+ {
+ "dt": 1650794400,
+ "sunrise": 1650772708,
+ "sunset": 1650822959,
+ "moonrise": 1650763920,
+ "moonset": 1650796740,
+ "moon_phase": 0.78,
+ "temp": {
+ "day": 16.25,
+ "min": 10.15,
+ "max": 17.18,
+ "night": 11.38,
+ "eve": 15.32,
+ "morn": 10.15
+ },
+ "feels_like": {
+ "day": 15.76,
+ "night": 10.66,
+ "eve": 14.92,
+ "morn": 9.6
+ },
+ "pressure": 1008,
+ "humidity": 70,
+ "dew_point": 9.72,
+ "wind_speed": 4.86,
+ "wind_deg": 230,
+ "wind_gust": 12.59,
+ "weather": [
+ {
+ "id": 500,
+ "main": "Rain",
+ "description": "light rain",
+ "icon": "10d"
+ },
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": 87,
+ "pop": 0.56,
+ "rain": 0.52,
+ "uvi": 6
+ }
+ ],
+ "alerts": [
+ {
+ "sender_name": "Agencija Republike Slovenije za okolje (ARSO vreme)",
+ "event": "Moderate Wind Warning",
+ "start": 1650114000,
+ "end": 1650221940,
+ "description": "Maximum wind speed : from 50 to 70 km/h. Wind sways trees and may break smaller branches.",
+ "tags": [
+ "Wind"
+ ]
+ },
+ {
+ "sender_name": "Agencija Republike Slovenije za okolje (ARSO vreme)",
+ "event": "Moderate Low temperature Warning",
+ "start": 1650229200,
+ "end": 1650265140,
+ "description": "Low temperatures may affect the health of sensitive members of the population.",
+ "tags": [
+ "Extreme temperature value"
+ ]
+ }
+ ]
+}
\ No newline at end of file