Compare commits

..

23 Commits

Author SHA1 Message Date
53f858f55c Remove SlotData 2025-06-02 15:57:34 +02:00
6cdd59034d Clang-format 2025-05-31 18:35:36 +02:00
6ccac18fd4 Fix comments 2025-05-31 18:34:07 +02:00
3f6fff7d0c Fix JsonVariantTests 2025-05-31 16:29:46 +00:00
cc2fea45cd All tests pass 2025-05-31 16:47:03 +02:00
4159af66eb JsonDeserializerTests pass 2025-05-31 16:41:09 +02:00
05942b620a JsonVariantTests pass 2025-05-31 16:38:36 +02:00
23c73b061e JsonArrayTests pass 2025-05-31 16:37:08 +02:00
c2e756d942 ResourceManagerTests pass 2025-05-31 16:31:31 +02:00
94aacf873e Replace extension with eight-byte values 2025-05-31 16:20:08 +02:00
bd2dccda0e Store static strings in a dedicated pool
Because a slot id is smaller than a pointer, this change will ultimately allow reducing the slot size.
2025-05-21 11:09:44 +02:00
411424b74e CI: upgrade clang-tidy 2025-05-20 20:55:01 +02:00
5e5c287978 CI: upgrade Clang versions 2025-05-20 18:24:46 +02:00
377cf63075 CI: upgrade runner to ubuntu-22.04 2025-05-20 14:48:42 +02:00
3252013509 Set version to 7.4.1 2025-04-11 15:43:42 +02:00
deab127a2f Fix crash with tiny Flash strings (issue #2170) 2025-04-11 10:23:43 +02:00
96281de682 Set version to 7.4.0 2025-04-09 14:49:13 +02:00
f0e84e4933 Fix support for const char[]
Fixes #2166
2025-04-09 08:55:09 +02:00
91397f9f06 Optimize storage of tiny strings (up to 3 characters) 2025-04-09 08:55:08 +02:00
7f75985e47 Change StringBuffer::save() to take a VariantData* 2025-02-28 10:05:50 +01:00
05b68fc7cc Change StringBuilder::save() to take a VariantData* 2025-02-28 09:59:46 +01:00
b06cee8f4d Remove the overload of setString() for StringNode 2025-02-28 09:23:56 +01:00
e03d8ae885 Set version to 7.3.1 2025-02-27 19:35:14 +01:00
52 changed files with 579 additions and 292 deletions

View File

@ -93,44 +93,40 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- clang: "3.9"
runner: ubuntu-20.04
archive: bionic
- clang: "4.0"
runner: ubuntu-20.04
archive: bionic
- clang: "5.0"
runner: ubuntu-20.04
archive: bionic
- clang: "6.0"
runner: ubuntu-20.04
archive: bionic
- clang: "7" - clang: "7"
runner: ubuntu-20.04 runner: ubuntu-22.04
archive: focal
- clang: "8" - clang: "8"
cxxflags: -fsanitize=leak -fno-sanitize-recover=all cxxflags: -fsanitize=leak -fno-sanitize-recover=all
runner: ubuntu-20.04 runner: ubuntu-22.04
archive: focal
- clang: "9" - clang: "9"
cxxflags: -fsanitize=undefined -fno-sanitize-recover=all cxxflags: -fsanitize=undefined -fno-sanitize-recover=all
runner: ubuntu-20.04 runner: ubuntu-22.04
archive: focal
- clang: "10" - clang: "10"
cxxflags: -fsanitize=address -fno-sanitize-recover=all cxxflags: -fsanitize=address -fno-sanitize-recover=all
runner: ubuntu-20.04 runner: ubuntu-22.04
archive: focal
- clang: "11" - clang: "11"
runner: ubuntu-22.04 runner: ubuntu-22.04
- clang: "12" - clang: "12"
runner: ubuntu-22.04 runner: ubuntu-22.04
- clang: "13" - clang: "13"
runner: ubuntu-22.04 runner: ubuntu-22.04
- clang: "14" - clang: 14
runner: ubuntu-22.04 - clang: 15
- clang: "15" - clang: 16
runner: ubuntu-22.04 - clang: 17
runs-on: ${{ matrix.runner }} - clang: 18
- clang: 19
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
steps: steps:
- name: Add archive repositories - name: Add archive repositories
if: matrix.archive if: matrix.archive
run: | run: |
sudo gpg --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
sudo gpg --export 3B4FE6ACC0B21F32 | sudo tee /etc/apt/trusted.gpg.d/ubuntu-keyring.gpg > /dev/null
sudo add-apt-repository -yn 'deb http://archive.ubuntu.com/ubuntu/ ${{ matrix.archive }} main' sudo add-apt-repository -yn 'deb http://archive.ubuntu.com/ubuntu/ ${{ matrix.archive }} main'
sudo add-apt-repository -yn 'deb http://archive.ubuntu.com/ubuntu/ ${{ matrix.archive }} universe' sudo add-apt-repository -yn 'deb http://archive.ubuntu.com/ubuntu/ ${{ matrix.archive }} universe'
- name: Install Clang ${{ matrix.clang }} - name: Install Clang ${{ matrix.clang }}
@ -138,10 +134,9 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install -y clang-${{ matrix.clang }} sudo apt-get install -y clang-${{ matrix.clang }}
- name: Install libc++ ${{ matrix.clang }} - name: Install libc++ ${{ matrix.clang }}
if: matrix.clang >= 11
run: sudo apt-get install -y libc++-${{ matrix.clang }}-dev libc++abi-${{ matrix.clang }}-dev run: sudo apt-get install -y libc++-${{ matrix.clang }}-dev libc++abi-${{ matrix.clang }}-dev
- name: Install libunwind ${{ matrix.clang }} - name: Install libunwind ${{ matrix.clang }}
if: matrix.clang == 12 # dependency is missing in Ubuntu 22.04 if: matrix.clang == 12 # dependency is missing in Ubuntu 22.04
run: sudo apt-get install -y libunwind-${{ matrix.clang }}-dev run: sudo apt-get install -y libunwind-${{ matrix.clang }}-dev
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -163,7 +158,7 @@ jobs:
conf_test: conf_test:
name: Test configuration on Linux name: Test configuration on Linux
needs: [gcc, clang] needs: [gcc, clang]
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Install - name: Install
run: | run: |
@ -257,7 +252,7 @@ jobs:
board: arduino:avr:uno board: arduino:avr:uno
- core: arduino:samd - core: arduino:samd
board: arduino:samd:mkr1000 board: arduino:samd:mkr1000
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -412,7 +407,7 @@ jobs:
arm: arm:
name: GCC for ARM processor name: GCC for ARM processor
needs: gcc needs: gcc
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Install - name: Install
run: | run: |
@ -431,7 +426,7 @@ jobs:
coverage: coverage:
needs: gcc needs: gcc
name: Coverage name: Coverage
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Install - name: Install
run: sudo apt-get install -y lcov ninja-build run: sudo apt-get install -y lcov ninja-build
@ -463,7 +458,7 @@ jobs:
valgrind: valgrind:
needs: gcc needs: gcc
name: Valgrind name: Valgrind
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Install - name: Install
run: | run: |
@ -485,24 +480,24 @@ jobs:
clang-tidy: clang-tidy:
needs: clang needs: clang
name: Clang-Tidy name: Clang-Tidy
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
steps: steps:
- name: Install - name: Install
run: sudo apt-get install -y clang-tidy cmake ninja-build run: sudo apt-get install -y clang-tidy libc++-dev libc++abi-dev
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Configure - name: Configure
run: cmake -G Ninja -DCMAKE_CXX_CLANG_TIDY="clang-tidy-10;--warnings-as-errors=*" -DCMAKE_BUILD_TYPE=Debug . run: cmake -G Ninja -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" -DCMAKE_BUILD_TYPE=Debug .
env: env:
CC: clang-10 CC: clang
CXX: clang++-10 CXX: clang++
- name: Check - name: Check
run: cmake --build . -- -k 0 run: cmake --build . -- -k 0
amalgamate: amalgamate:
needs: gcc needs: gcc
name: Amalgamate ArduinoJson.h name: Amalgamate ArduinoJson.h
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -578,7 +573,7 @@ jobs:
codeql: codeql:
name: CodeQL name: CodeQL
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
needs: gcc needs: gcc
permissions: permissions:
@ -587,20 +582,20 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v3
with: with:
languages: cpp languages: cpp
- name: Build - name: Build
run: | run: |
cmake -DCMAKE_BUILD_TYPE=Debug . cmake -DCMAKE_BUILD_TYPE=Debug .
cmake --build . cmake --build .
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v3
with: with:
category: "/language:cpp" category: "/language:cpp"

View File

@ -8,7 +8,7 @@ on:
jobs: jobs:
release: release:
name: Create release name: Create release
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Set variables - name: Set variables
id: init id: init

View File

@ -4,8 +4,6 @@ ArduinoJson: change log
HEAD HEAD
---- ----
* Fix conversion from static string to number
* Slightly reduce code size
* Optimize storage of static strings * Optimize storage of static strings
> ### BREAKING CHANGES > ### BREAKING CHANGES
@ -40,6 +38,23 @@ HEAD
> doc["key"] = str; // same as previous line for supported string classes (`String`, `std::string`, etc.) > doc["key"] = str; // same as previous line for supported string classes (`String`, `std::string`, etc.)
> ``` > ```
v7.4.1 (2025-04-11)
------
* Fix crash with tiny Flash strings (issue #2170)
v7.4.0 (2025-04-09)
------
* Optimize storage of tiny strings (up to 3 characters)
* Fix support for `const char[]` (issue #2166)
v7.3.1 (2025-02-27)
------
* Fix conversion from static string to number
* Slightly reduce code size
v7.3.0 (2024-12-29) v7.3.0 (2024-12-29)
------ ------

View File

@ -10,7 +10,7 @@ if(ESP_PLATFORM)
return() return()
endif() endif()
project(ArduinoJson VERSION 7.3.0) project(ArduinoJson VERSION 7.4.1)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
include(CTest) include(CTest)

View File

@ -80,7 +80,7 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
* Continuously tested on * Continuously tested on
* [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x) * [Visual Studio 2017, 2019, 2022](https://ci.appveyor.com/project/bblanchon/arduinojson/branch/7.x)
* [GCC 4.8, 5, 6, 7, 8, 9, 10, 11, 12](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22) * [GCC 4.8, 5, 6, 7, 8, 9, 10, 11, 12](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
* [Clang 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10, 11, 12, 13, 14, 15](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22) * [Clang 7 to 19](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
* [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson) * [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
* Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/) * Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
* Well documented * Well documented

View File

@ -1,4 +1,4 @@
version: 7.3.0.{build} version: 7.4.1.{build}
environment: environment:
matrix: matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022

View File

@ -12,7 +12,7 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 0, "ARDUINOJSON_USE_DOUBLE"); static_assert(ARDUINOJSON_USE_DOUBLE == 0, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 6, "slot size"); static_assert(sizeof(ArduinoJson::detail::VariantData) == 6, "slot size");
void setup() {} void setup() {}
void loop() {} void loop() {}

View File

@ -10,7 +10,7 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size"); static_assert(sizeof(VariantData) == 8, "slot size");
void setup() {} void setup() {}
void loop() {} void loop() {}

View File

@ -10,7 +10,6 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 16, static_assert(sizeof(ArduinoJson::detail::VariantData) == 16, "slot size");
"slot size");
int main() {} int main() {}

View File

@ -10,6 +10,6 @@ static_assert(ARDUINOJSON_LITTLE_ENDIAN == 1, "ARDUINOJSON_LITTLE_ENDIAN");
static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE"); static_assert(ARDUINOJSON_USE_DOUBLE == 1, "ARDUINOJSON_USE_DOUBLE");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size"); static_assert(sizeof(ArduinoJson::detail::VariantData) == 8, "slot size");
int main() {} int main() {}

View File

@ -46,7 +46,7 @@ TEST_CASE("BasicJsonDocument") {
deserializeJson(doc, "{\"hello\":\"world\"}"); deserializeJson(doc, "{\"hello\":\"world\"}");
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}"); REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}");
doc.clear(); doc.clear();
REQUIRE(allocatorLog == "ARAARDDD"); REQUIRE(allocatorLog == "AARARDDD");
} }
SECTION("copy") { SECTION("copy") {

View File

@ -269,10 +269,10 @@ inline size_t sizeofPoolList(size_t n = ARDUINOJSON_INITIAL_POOL_COUNT) {
return sizeof(MemoryPool<VariantData>) * n; return sizeof(MemoryPool<VariantData>) * n;
} }
template <typename T = ArduinoJson::detail::VariantData>
inline size_t sizeofPool( inline size_t sizeofPool(
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) { ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
using namespace ArduinoJson::detail; return ArduinoJson::detail::MemoryPool<T>::slotsToBytes(n);
return MemoryPool<VariantData>::slotsToBytes(n);
} }
inline size_t sizeofStaticStringPool( inline size_t sizeofStaticStringPool(

View File

@ -8,7 +8,7 @@
#include "Allocators.hpp" #include "Allocators.hpp"
#include "Literals.hpp" #include "Literals.hpp"
using ArduinoJson::detail::sizeofArray; using namespace ArduinoJson::detail;
TEST_CASE("JsonArray::add(T)") { TEST_CASE("JsonArray::add(T)") {
SpyingAllocator spy; SpyingAllocator spy;
@ -33,7 +33,8 @@ TEST_CASE("JsonArray::add(T)") {
REQUIRE(array[0].is<double>()); REQUIRE(array[0].is<double>());
REQUIRE_FALSE(array[0].is<bool>()); REQUIRE_FALSE(array[0].is<bool>());
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool<VariantData>()),
Allocate(sizeofPool<EightByteValue>()),
}); });
} }

View File

@ -7,7 +7,7 @@
#include "Allocators.hpp" #include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray; using namespace ArduinoJson::detail;
TEST_CASE("deserialize JSON array") { TEST_CASE("deserialize JSON array") {
SpyingAllocator spy; SpyingAllocator spy;
@ -92,8 +92,12 @@ TEST_CASE("deserialize JSON array") {
REQUIRE(arr[0].as<double>() == Approx(4.2123456)); REQUIRE(arr[0].as<double>() == Approx(4.2123456));
REQUIRE(arr[1] == -7E89); REQUIRE(arr[1] == -7E89);
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool<VariantData>()),
Reallocate(sizeofPool(), sizeofPool(4)), Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<VariantData>(),
sizeofPool<VariantData>(2)),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(2)),
}); });
} }

View File

@ -121,7 +121,7 @@ TEST_CASE("deserializeJson() returns NoMemory if string length overflows") {
} }
} }
TEST_CASE("deserializeJson() returns NoMemory if extension allocation fails") { TEST_CASE("deserializeJson() returns NoMemory if 8-bit slot allocation fails") {
JsonDocument doc(FailingAllocator::instance()); JsonDocument doc(FailingAllocator::instance());
SECTION("uint32_t should pass") { SECTION("uint32_t should pass") {

View File

@ -26,8 +26,8 @@ TEST_CASE("deserializeJson(char*)") {
REQUIRE(spy.log() == REQUIRE(spy.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofPool()), Allocate(sizeofPool()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("world")), Reallocate(sizeofStringBuffer(), sizeofString("world")),
Reallocate(sizeofPool(), sizeofObject(1)), Reallocate(sizeofPool(), sizeofObject(1)),

View File

@ -292,22 +292,23 @@ TEST_CASE("deserialize JSON object") {
} }
SECTION("Repeated key") { SECTION("Repeated key") {
DeserializationError err = deserializeJson(doc, "{a:{b:{c:1}},a:2}"); DeserializationError err =
deserializeJson(doc, "{alfa:{bravo:{charlie:1}},alfa:2}");
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{\"a\":2}"); REQUIRE(doc.as<std::string>() == "{\"alfa\":2}");
REQUIRE(spy.log() == REQUIRE(spy.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("a")),
Allocate(sizeofPool()), Allocate(sizeofPool()),
Reallocate(sizeofStringBuffer(), sizeofString("alfa")),
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("b")), Reallocate(sizeofStringBuffer(), sizeofString("bravo")),
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("c")), Reallocate(sizeofStringBuffer(), sizeofString("charlie")),
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Deallocate(sizeofString("b")), Deallocate(sizeofString("bravo")),
Deallocate(sizeofString("c")), Deallocate(sizeofString("charlie")),
Deallocate(sizeofStringBuffer()), Deallocate(sizeofStringBuffer()),
Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)), Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
}); });
@ -378,7 +379,7 @@ TEST_CASE("deserialize JSON object under memory constraints") {
} }
SECTION("pool allocation fails") { SECTION("pool allocation fails") {
timebomb.setCountdown(2); timebomb.setCountdown(1);
char input[] = "{\"a\":1}"; char input[] = "{\"a\":1}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
@ -389,11 +390,11 @@ TEST_CASE("deserialize JSON object under memory constraints") {
SECTION("string allocation fails") { SECTION("string allocation fails") {
timebomb.setCountdown(3); timebomb.setCountdown(3);
char input[] = "{\"a\":\"b\"}"; char input[] = "{\"alfa\":\"bravo\"}";
DeserializationError err = deserializeJson(doc, input); DeserializationError err = deserializeJson(doc, input);
REQUIRE(err == DeserializationError::NoMemory); REQUIRE(err == DeserializationError::NoMemory);
REQUIRE(doc.as<std::string>() == "{\"a\":null}"); REQUIRE(doc.as<std::string>() == "{\"alfa\":null}");
} }
} }

View File

@ -133,8 +133,8 @@ TEST_CASE("Allocation of the key fails") {
REQUIRE(spy.log() == REQUIRE(spy.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofPool()), Allocate(sizeofPool()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
AllocateFail(sizeofStringBuffer()), AllocateFail(sizeofStringBuffer()),
ReallocateFail(sizeofPool(), sizeofObject(1)), ReallocateFail(sizeofPool(), sizeofObject(1)),
}); });
@ -155,8 +155,8 @@ TEST_CASE("Allocation of the key fails") {
REQUIRE(spy.log() == REQUIRE(spy.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofStringBuffer()), Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofPool()), Allocate(sizeofPool()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
AllocateFail(sizeofStringBuffer()), AllocateFail(sizeofStringBuffer()),
ReallocateFail(sizeofPool(), sizeofObject(1)), ReallocateFail(sizeofPool(), sizeofObject(1)),
}); });

View File

@ -69,6 +69,8 @@ TEST_CASE("JsonDocument assignment") {
doc2 = std::move(doc1); doc2 = std::move(doc1);
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}"); REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
REQUIRE(doc1.as<std::string>() == "null"); REQUIRE(doc1.as<std::string>() == "null");
} }
REQUIRE(spyingAllocator.log() == AllocatorLog{ REQUIRE(spyingAllocator.log() == AllocatorLog{

View File

@ -44,6 +44,8 @@ TEST_CASE("JsonDocument constructor") {
JsonDocument doc2(std::move(doc1)); JsonDocument doc2(std::move(doc1));
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!"); REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
REQUIRE(doc1.as<std::string>() == "null"); REQUIRE(doc1.as<std::string>() == "null");
} }
REQUIRE(spyingAllocator.log() == AllocatorLog{ REQUIRE(spyingAllocator.log() == AllocatorLog{

View File

@ -89,6 +89,13 @@ TEST_CASE("JsonDocument::set()") {
}); });
} }
SECTION("Flash tiny string") { // issue #2170
doc.set(F("abc"));
REQUIRE(doc.as<const char*>() == "abc"_s);
REQUIRE(spy.log() == AllocatorLog{});
}
#ifdef HAS_VARIABLE_LENGTH_ARRAY #ifdef HAS_VARIABLE_LENGTH_ARRAY
SECTION("VLA") { SECTION("VLA") {
size_t i = 16; size_t i = 16;

View File

@ -179,15 +179,15 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
} }
SECTION("owned string in object") { SECTION("owned string in object") {
doc["key"_s] = "value"_s; doc["key1"_s] = "value"_s;
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"value\"}"); REQUIRE(doc.as<std::string>() == "{\"key1\":\"value\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofString("key")), Allocate(sizeofString("key1")),
Allocate(sizeofString("value")), Allocate(sizeofString("value")),
Reallocate(sizeofPool(), sizeofPool(2)), Reallocate(sizeofPool(), sizeofPool(2)),
}); });

View File

@ -88,13 +88,13 @@ TEST_CASE("JsonObject::set()") {
JsonDocument doc3(&timebomb); JsonDocument doc3(&timebomb);
JsonObject obj3 = doc3.to<JsonObject>(); JsonObject obj3 = doc3.to<JsonObject>();
obj1["a"_s] = 1; obj1["alpha"_s] = 1;
obj1["b"_s] = 2; obj1["beta"_s] = 2;
bool success = obj3.set(obj1); bool success = obj3.set(obj1);
REQUIRE(success == false); REQUIRE(success == false);
REQUIRE(doc3.as<std::string>() == "{\"a\":1}"); REQUIRE(doc3.as<std::string>() == "{\"alpha\":1}");
} }
SECTION("copy fails in the middle of an array") { SECTION("copy fails in the middle of an array") {

View File

@ -199,7 +199,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<JsonString>() == "hello"); REQUIRE(variant.as<JsonString>() == "hello");
} }
SECTION("set(std::string(\"4.2\"))") { SECTION("set(std::string(\"4.2\")) (tiny string optimization)") {
variant.set("4.2"_s); variant.set("4.2"_s);
REQUIRE(variant.as<bool>() == true); REQUIRE(variant.as<bool>() == true);
@ -211,6 +211,18 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<JsonString>().isStatic() == false); REQUIRE(variant.as<JsonString>().isStatic() == false);
} }
SECTION("set(std::string(\"123.45\"))") {
variant.set("123.45"_s);
REQUIRE(variant.as<bool>() == true);
REQUIRE(variant.as<long>() == 123L);
REQUIRE(variant.as<double>() == Approx(123.45));
REQUIRE(variant.as<const char*>() == "123.45"_s);
REQUIRE(variant.as<std::string>() == "123.45"_s);
REQUIRE(variant.as<JsonString>() == "123.45");
REQUIRE(variant.as<JsonString>().isStatic() == false);
}
SECTION("set(\"true\")") { SECTION("set(\"true\")") {
variant.set("true"); variant.set("true");

View File

@ -8,7 +8,7 @@
#include "Allocators.hpp" #include "Allocators.hpp"
#include "Literals.hpp" #include "Literals.hpp"
using ArduinoJson::detail::sizeofObject; using namespace ArduinoJson::detail;
enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 }; enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 };
@ -65,6 +65,18 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
}); });
} }
SECTION("char* (tiny string optimization)") {
char str[16];
strcpy(str, "abc");
bool result = variant.set(str);
strcpy(str, "def");
REQUIRE(result == true);
REQUIRE(variant == "abc"); // stores by copy
REQUIRE(spy.log() == AllocatorLog{});
}
SECTION("(char*)0") { SECTION("(char*)0") {
bool result = variant.set(static_cast<char*>(0)); bool result = variant.set(static_cast<char*>(0));
@ -185,11 +197,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true); REQUIRE(result == true);
REQUIRE(variant.is<double>() == true); REQUIRE(variant.is<double>() == true);
REQUIRE(variant.as<double>() == 1.2); REQUIRE(variant.as<double>() == 1.2);
REQUIRE(spy.log() == REQUIRE(spy.log() == AllocatorLog{
AllocatorLog{ Allocate(sizeofPool<EightByteValue>()),
Allocate(sizeofPool()), Reallocate(sizeofPool<EightByteValue>(),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot sizeofPool<EightByteValue>(1)),
}); });
} }
SECTION("int32_t") { SECTION("int32_t") {
@ -208,11 +220,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true); REQUIRE(result == true);
REQUIRE(variant.is<int64_t>() == true); REQUIRE(variant.is<int64_t>() == true);
REQUIRE(variant.as<int64_t>() == -2147483649LL); REQUIRE(variant.as<int64_t>() == -2147483649LL);
REQUIRE(spy.log() == REQUIRE(spy.log() == AllocatorLog{
AllocatorLog{ Allocate(sizeofPool<EightByteValue>()),
Allocate(sizeofPool()), Reallocate(sizeofPool<EightByteValue>(),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot sizeofPool<EightByteValue>(1)),
}); });
} }
SECTION("uint32_t") { SECTION("uint32_t") {
@ -231,11 +243,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true); REQUIRE(result == true);
REQUIRE(variant.is<uint64_t>() == true); REQUIRE(variant.is<uint64_t>() == true);
REQUIRE(variant.as<uint64_t>() == 4294967296); REQUIRE(variant.as<uint64_t>() == 4294967296);
REQUIRE(spy.log() == REQUIRE(spy.log() == AllocatorLog{
AllocatorLog{ Allocate(sizeofPool<EightByteValue>()),
Allocate(sizeofPool()), Reallocate(sizeofPool<EightByteValue>(),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot sizeofPool<EightByteValue>(1)),
}); });
} }
SECTION("JsonDocument") { SECTION("JsonDocument") {
@ -366,7 +378,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
} }
SECTION("float") { SECTION("float") {
v.set(1.2); v.set(1.2f);
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Deallocate(sizeofString("world")), Deallocate(sizeofString("world")),
}); });
@ -381,7 +393,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
} }
} }
TEST_CASE("JsonVariant::set() reuses extension slot") { TEST_CASE("JsonVariant::set() reuses 8-bit slot") {
SpyingAllocator spy; SpyingAllocator spy;
JsonDocument doc(&spy); JsonDocument doc(&spy);
JsonVariant variant = doc.to<JsonVariant>(); JsonVariant variant = doc.to<JsonVariant>();

View File

@ -7,6 +7,7 @@ add_executable(MiscTests
conflicts.cpp conflicts.cpp
issue1967.cpp issue1967.cpp
issue2129.cpp issue2129.cpp
issue2166.cpp
JsonString.cpp JsonString.cpp
NoArduinoHeader.cpp NoArduinoHeader.cpp
printable.cpp printable.cpp

View File

@ -128,6 +128,7 @@ TEST_CASE("IsString<T>") {
CHECK(IsString<const __FlashStringHelper*>::value == true); CHECK(IsString<const __FlashStringHelper*>::value == true);
CHECK(IsString<const char*>::value == true); CHECK(IsString<const char*>::value == true);
CHECK(IsString<const char[8]>::value == true); CHECK(IsString<const char[8]>::value == true);
CHECK(IsString<const char[]>::value == true);
CHECK(IsString<::String>::value == true); CHECK(IsString<::String>::value == true);
CHECK(IsString<::StringSumHelper>::value == true); CHECK(IsString<::StringSumHelper>::value == true);
CHECK(IsString<const EmptyStruct*>::value == false); CHECK(IsString<const EmptyStruct*>::value == false);

View File

@ -0,0 +1,22 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson.h>
#include <catch.hpp>
struct CCLASS {
static const char mszKey[];
};
TEST_CASE("Issue #2166") {
JsonDocument doc;
doc[CCLASS::mszKey] = 12;
REQUIRE(doc.as<std::string>() == "{\"test3\":12}");
JsonObject obj = doc.to<JsonObject>();
obj[CCLASS::mszKey] = 12;
REQUIRE(doc.as<std::string>() == "{\"test3\":12}");
}
const char CCLASS::mszKey[] = "test3";

View File

@ -5,8 +5,11 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp"
TEST_CASE("deserialize MsgPack array") { TEST_CASE("deserialize MsgPack array") {
JsonDocument doc; SpyingAllocator spy;
JsonDocument doc(&spy);
SECTION("fixarray") { SECTION("fixarray") {
SECTION("empty") { SECTION("empty") {
@ -30,6 +33,24 @@ TEST_CASE("deserialize MsgPack array") {
REQUIRE(array[0] == 1); REQUIRE(array[0] == 1);
REQUIRE(array[1] == 2); REQUIRE(array[1] == 2);
} }
SECTION("tiny strings") {
DeserializationError error =
deserializeMsgPack(doc, "\x92\xA3xxx\xA3yyy");
REQUIRE(error == DeserializationError::Ok);
REQUIRE(doc.is<JsonArray>());
REQUIRE(doc.size() == 2);
REQUIRE(doc[0] == "xxx");
REQUIRE(doc[1] == "yyy");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("xxx")),
// Buffer is reused for the next string
Deallocate(sizeofString("xxx")),
Reallocate(sizeofPool(), sizeofPool(2)),
});
}
} }
SECTION("array 16") { SECTION("array 16") {

View File

@ -348,13 +348,14 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
SECTION("{}") { SECTION("{}") {
checkError(0, "\x80", DeserializationError::Ok); checkError(0, "\x80", DeserializationError::Ok);
} }
SECTION("{H:1}") { SECTION("{Hello:1}") {
checkError(1, "\x81\xA1H\x01", DeserializationError::NoMemory); checkError(1, "\x81\xA5Hello\x01", DeserializationError::NoMemory);
checkError(2, "\x81\xA1H\x01", DeserializationError::Ok); checkError(2, "\x81\xA5Hello\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{Hello:1,World:2}") {
checkError(2, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory); checkError(2, "\x82\xA5Hello\x01\xA5World\x02",
checkError(3, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok); DeserializationError::NoMemory);
checkError(3, "\x82\xA5Hello\x01\xA5World\x02", DeserializationError::Ok);
} }
} }
@ -362,14 +363,16 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
SECTION("{}") { SECTION("{}") {
checkError(0, "\xDE\x00\x00", DeserializationError::Ok); checkError(0, "\xDE\x00\x00", DeserializationError::Ok);
} }
SECTION("{H:1}") { SECTION("{Hello:1}") {
checkError(1, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory); checkError(1, "\xDE\x00\x01\xA5Hello\x01",
checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
}
SECTION("{H:1,W:2}") {
checkError(2, "\xDE\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(3, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok); checkError(2, "\xDE\x00\x01\xA5Hello\x01", DeserializationError::Ok);
}
SECTION("{Hello:1,World:2}") {
checkError(2, "\xDE\x00\x02\xA5Hello\x01\xA5World\x02",
DeserializationError::NoMemory);
checkError(3, "\xDE\x00\x02\xA5Hello\x01\xA5World\x02",
DeserializationError::Ok);
} }
} }
@ -382,8 +385,8 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok); checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
} }
SECTION("{H:1,W:2}") { SECTION("{Hello:1,World:2}") {
checkError(2, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", checkError(2, "\xDF\x00\x00\x00\x02\xA5Hello\x01\xA5World\x02",
DeserializationError::NoMemory); DeserializationError::NoMemory);
checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02", checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
DeserializationError::Ok); DeserializationError::Ok);

View File

@ -9,6 +9,7 @@ add_executable(ResourceManagerTests
saveString.cpp saveString.cpp
shrinkToFit.cpp shrinkToFit.cpp
size.cpp size.cpp
StringBuffer.cpp
StringBuilder.cpp StringBuilder.cpp
swap.cpp swap.cpp
) )

View File

@ -0,0 +1,50 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
#include "Literals.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("StringBuffer") {
SpyingAllocator spy;
ResourceManager resources(&spy);
StringBuffer sb(&resources);
VariantData variant;
SECTION("Tiny string") {
auto ptr = sb.reserve(3);
strcpy(ptr, "hi!");
sb.save(&variant);
REQUIRE(variant.type() == VariantType::TinyString);
REQUIRE(variant.asString(&resources) == "hi!");
}
SECTION("Tiny string can't contain NUL") {
auto ptr = sb.reserve(3);
memcpy(ptr, "a\0b", 3);
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
auto str = variant.asString(&resources);
REQUIRE(str.size() == 3);
REQUIRE(str.c_str()[0] == 'a');
REQUIRE(str.c_str()[1] == 0);
REQUIRE(str.c_str()[2] == 'b');
}
SECTION("Tiny string can't have 4 characters") {
auto ptr = sb.reserve(4);
strcpy(ptr, "alfa");
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
REQUIRE(variant.asString(&resources) == "alfa");
}
}

View File

@ -2,11 +2,12 @@
// Copyright © 2014-2025, Benoit BLANCHON // Copyright © 2014-2025, Benoit BLANCHON
// MIT License // MIT License
#include <ArduinoJson/Memory/StringBuilder.hpp> #include <ArduinoJson.hpp>
#include <catch.hpp> #include <catch.hpp>
#include "Allocators.hpp" #include "Allocators.hpp"
using namespace ArduinoJson;
using namespace ArduinoJson::detail; using namespace ArduinoJson::detail;
TEST_CASE("StringBuilder") { TEST_CASE("StringBuilder") {
@ -16,17 +17,36 @@ TEST_CASE("StringBuilder") {
SECTION("Empty string") { SECTION("Empty string") {
StringBuilder str(&resources); StringBuilder str(&resources);
VariantData data;
str.startString(); str.startString();
str.save(); str.save(&data);
REQUIRE(resources.size() == sizeofString(""));
REQUIRE(resources.overflowed() == false); REQUIRE(resources.overflowed() == false);
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() == AllocatorLog{
AllocatorLog{ Allocate(sizeofStringBuffer()),
Allocate(sizeofStringBuffer()), });
Reallocate(sizeofStringBuffer(), sizeofString("")), REQUIRE(data.type() == VariantType::TinyString);
}); }
SECTION("Tiny string") {
StringBuilder str(&resources);
str.startString();
str.append("url");
REQUIRE(str.isValid() == true);
REQUIRE(str.str() == "url");
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStringBuffer()),
});
VariantData data;
str.save(&data);
REQUIRE(resources.overflowed() == false);
REQUIRE(data.type() == VariantType::TinyString);
REQUIRE(data.asString(&resources) == "url");
} }
SECTION("Short string fits in first allocation") { SECTION("Short string fits in first allocation") {
@ -96,48 +116,72 @@ TEST_CASE("StringBuilder") {
} }
} }
static StringNode* addStringToPool(ResourceManager& resources, const char* s) { static VariantData saveString(StringBuilder& builder, const char* s) {
StringBuilder str(&resources); VariantData data;
str.startString(); builder.startString();
str.append(s); builder.append(s);
return str.save(); builder.save(&data);
return data;
} }
TEST_CASE("StringBuilder::save() deduplicates strings") { TEST_CASE("StringBuilder::save() deduplicates strings") {
ResourceManager resources; SpyingAllocator spy;
ResourceManager resources(&spy);
StringBuilder builder(&resources);
SECTION("Basic") { SECTION("Basic") {
auto s1 = addStringToPool(resources, "hello"); auto s1 = saveString(builder, "hello");
auto s2 = addStringToPool(resources, "world"); auto s2 = saveString(builder, "world");
auto s3 = addStringToPool(resources, "hello"); auto s3 = saveString(builder, "hello");
REQUIRE(s1 == s3); REQUIRE(s1.asString(&resources) == "hello");
REQUIRE(s2 != s3); REQUIRE(s2.asString(&resources) == "world");
REQUIRE(s1->references == 2); REQUIRE(+s1.asString(&resources).c_str() ==
REQUIRE(s2->references == 1); +s3.asString(&resources).c_str()); // same address
REQUIRE(s3->references == 2);
REQUIRE(resources.size() == sizeofString("hello") + sizeofString("world")); REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("world")),
Allocate(sizeofStringBuffer()),
});
} }
SECTION("Requires terminator") { SECTION("Requires terminator") {
auto s1 = addStringToPool(resources, "hello world"); auto s1 = saveString(builder, "hello world");
auto s2 = addStringToPool(resources, "hello"); auto s2 = saveString(builder, "hello");
REQUIRE(s2 != s1); REQUIRE(s1.asString(&resources) == "hello world");
REQUIRE(s1->references == 1); REQUIRE(s2.asString(&resources) == "hello");
REQUIRE(s2->references == 1); REQUIRE(+s2.asString(&resources).c_str() !=
REQUIRE(resources.size() == +s1.asString(&resources).c_str()); // different address
sizeofString("hello world") + sizeofString("hello"));
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
});
} }
SECTION("Don't overrun") { SECTION("Don't overrun") {
auto s1 = addStringToPool(resources, "hello world"); auto s1 = saveString(builder, "hello world");
auto s2 = addStringToPool(resources, "wor"); auto s2 = saveString(builder, "worl");
REQUIRE(s2 != s1); REQUIRE(s1.asString(&resources) == "hello world");
REQUIRE(s1->references == 1); REQUIRE(s2.asString(&resources) == "worl");
REQUIRE(s2->references == 1); REQUIRE(s2.asString(&resources).c_str() !=
REQUIRE(resources.size() == s1.asString(&resources).c_str()); // different address
sizeofString("hello world") + sizeofString("wor"));
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("worl")),
});
} }
} }

View File

@ -1,7 +1,7 @@
version: "7.3.0" version: "7.4.1"
description: >- description: >-
A simple and efficient JSON library for embedded C++. A simple and efficient JSON library for embedded C++.
★ 6785 stars on GitHub! ★ 6898 stars on GitHub!
Supports serialization, deserialization, MessagePack, streams, filtering, and more. Supports serialization, deserialization, MessagePack, streams, filtering, and more.
Fully tested and documented. Fully tested and documented.
url: https://arduinojson.org/ url: https://arduinojson.org/

View File

@ -1,13 +1,13 @@
{ {
"name": "ArduinoJson", "name": "ArduinoJson",
"keywords": "json, rest, http, web", "keywords": "json, rest, http, web",
"description": "A simple and efficient JSON library for embedded C++. ⭐ 6785 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.", "description": "A simple and efficient JSON library for embedded C++. ⭐ 6898 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.",
"homepage": "https://arduinojson.org/?utm_source=meta&utm_medium=library.json", "homepage": "https://arduinojson.org/?utm_source=meta&utm_medium=library.json",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/bblanchon/ArduinoJson.git" "url": "https://github.com/bblanchon/ArduinoJson.git"
}, },
"version": "7.3.0", "version": "7.4.1",
"authors": { "authors": {
"name": "Benoit Blanchon", "name": "Benoit Blanchon",
"url": "https://blog.benoitblanchon.fr" "url": "https://blog.benoitblanchon.fr"

View File

@ -1,9 +1,9 @@
name=ArduinoJson name=ArduinoJson
version=7.3.0 version=7.4.1
author=Benoit Blanchon <blog.benoitblanchon.fr> author=Benoit Blanchon <blog.benoitblanchon.fr>
maintainer=Benoit Blanchon <blog.benoitblanchon.fr> maintainer=Benoit Blanchon <blog.benoitblanchon.fr>
sentence=A simple and efficient JSON library for embedded C++. sentence=A simple and efficient JSON library for embedded C++.
paragraph=⭐ 6785 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented. paragraph=⭐ 6898 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.
category=Data Processing category=Data Processing
url=https://arduinojson.org/?utm_source=meta&utm_medium=library.properties url=https://arduinojson.org/?utm_source=meta&utm_medium=library.properties
architectures=* architectures=*

View File

@ -73,7 +73,7 @@ inline bool ArrayData::addValue(const T& value, ResourceManager* resources) {
// Returns the size (in bytes) of an array with n elements. // Returns the size (in bytes) of an array with n elements.
constexpr size_t sizeofArray(size_t n) { constexpr size_t sizeofArray(size_t n) {
return n * ResourceManager::slotSize; return n * sizeof(VariantData);
} }
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -274,9 +274,9 @@
#endif #endif
#if ARDUINOJSON_USE_LONG_LONG || ARDUINOJSON_USE_DOUBLE #if ARDUINOJSON_USE_LONG_LONG || ARDUINOJSON_USE_DOUBLE
# define ARDUINOJSON_USE_EXTENSIONS 1 # define ARDUINOJSON_USE_8_BYTE_POOL 1
#else #else
# define ARDUINOJSON_USE_EXTENSIONS 0 # define ARDUINOJSON_USE_8_BYTE_POOL 0
#endif #endif
#if defined(nullptr) #if defined(nullptr)

View File

@ -275,13 +275,11 @@ class JsonDeserializer {
if (memberFilter.allow()) { if (memberFilter.allow()) {
auto member = object.getMember(adaptString(key), resources_); auto member = object.getMember(adaptString(key), resources_);
if (!member) { if (!member) {
// Save key in memory pool. auto keyVariant = object.addPair(&member, resources_);
auto savedKey = stringBuilder_.save(); if (!keyVariant)
// Allocate slot in object
member = object.addMember(savedKey, resources_);
if (!member)
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
stringBuilder_.save(keyVariant);
} else { } else {
member->clear(resources_); member->clear(resources_);
} }
@ -390,7 +388,7 @@ class JsonDeserializer {
if (err) if (err)
return err; return err;
variant.setOwnedString(stringBuilder_.save()); stringBuilder_.save(&variant);
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@ -18,16 +18,7 @@ class VariantData;
class VariantWithId; class VariantWithId;
class ResourceManager { class ResourceManager {
union SlotData {
VariantData variant;
#if ARDUINOJSON_USE_EXTENSIONS
VariantExtension extension;
#endif
};
public: public:
constexpr static size_t slotSize = sizeof(SlotData);
ResourceManager(Allocator* allocator = DefaultAllocator::instance()) ResourceManager(Allocator* allocator = DefaultAllocator::instance())
: allocator_(allocator), overflowed_(false) {} : allocator_(allocator), overflowed_(false) {}
@ -35,6 +26,9 @@ class ResourceManager {
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
variantPools_.clear(allocator_); variantPools_.clear(allocator_);
staticStringsPools_.clear(allocator_); staticStringsPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
} }
ResourceManager(const ResourceManager&) = delete; ResourceManager(const ResourceManager&) = delete;
@ -44,6 +38,9 @@ class ResourceManager {
swap(a.stringPool_, b.stringPool_); swap(a.stringPool_, b.stringPool_);
swap(a.variantPools_, b.variantPools_); swap(a.variantPools_, b.variantPools_);
swap(a.staticStringsPools_, b.staticStringsPools_); swap(a.staticStringsPools_, b.staticStringsPools_);
#if ARDUINOJSON_USE_8_BYTE_POOL
swap(a.eightBytePools_, b.eightBytePools_);
#endif
swap_(a.allocator_, b.allocator_); swap_(a.allocator_, b.allocator_);
swap_(a.overflowed_, b.overflowed_); swap_(a.overflowed_, b.overflowed_);
} }
@ -64,10 +61,10 @@ class ResourceManager {
void freeVariant(Slot<VariantData> slot); void freeVariant(Slot<VariantData> slot);
VariantData* getVariant(SlotId id) const; VariantData* getVariant(SlotId id) const;
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
Slot<VariantExtension> allocExtension(); Slot<EightByteValue> allocEightByte();
void freeExtension(SlotId slot); void freeEightByte(SlotId slot);
VariantExtension* getExtension(SlotId id) const; EightByteValue* getEightByte(SlotId id) const;
#endif #endif
template <typename TAdaptedString> template <typename TAdaptedString>
@ -136,19 +133,28 @@ class ResourceManager {
variantPools_.clear(allocator_); variantPools_.clear(allocator_);
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
staticStringsPools_.clear(allocator_); staticStringsPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
} }
void shrinkToFit() { void shrinkToFit() {
variantPools_.shrinkToFit(allocator_); variantPools_.shrinkToFit(allocator_);
staticStringsPools_.shrinkToFit(allocator_); staticStringsPools_.shrinkToFit(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.shrinkToFit(allocator_);
#endif
} }
private: private:
Allocator* allocator_; Allocator* allocator_;
bool overflowed_; bool overflowed_;
StringPool stringPool_; StringPool stringPool_;
MemoryPoolList<SlotData> variantPools_; MemoryPoolList<VariantData> variantPools_;
MemoryPoolList<const char*> staticStringsPools_; MemoryPoolList<const char*> staticStringsPools_;
#if ARDUINOJSON_USE_8_BYTE_POOL
MemoryPoolList<EightByteValue> eightBytePools_;
#endif
}; };
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -12,40 +12,41 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline Slot<VariantData> ResourceManager::allocVariant() { inline Slot<VariantData> ResourceManager::allocVariant() {
auto p = variantPools_.allocSlot(allocator_); auto slot = variantPools_.allocSlot(allocator_);
if (!p) { if (!slot) {
overflowed_ = true; overflowed_ = true;
return {}; return {};
} }
return {new (&p->variant) VariantData, p.id()}; new (slot.ptr()) VariantData();
return slot;
} }
inline void ResourceManager::freeVariant(Slot<VariantData> variant) { inline void ResourceManager::freeVariant(Slot<VariantData> slot) {
variant->clear(this); slot->clear(this);
variantPools_.freeSlot({alias_cast<SlotData*>(variant.ptr()), variant.id()}); variantPools_.freeSlot(slot);
} }
inline VariantData* ResourceManager::getVariant(SlotId id) const { inline VariantData* ResourceManager::getVariant(SlotId id) const {
return reinterpret_cast<VariantData*>(variantPools_.getSlot(id)); return reinterpret_cast<VariantData*>(variantPools_.getSlot(id));
} }
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
inline Slot<VariantExtension> ResourceManager::allocExtension() { inline Slot<EightByteValue> ResourceManager::allocEightByte() {
auto p = variantPools_.allocSlot(allocator_); auto slot = eightBytePools_.allocSlot(allocator_);
if (!p) { if (!slot) {
overflowed_ = true; overflowed_ = true;
return {}; return {};
} }
return {&p->extension, p.id()}; return slot;
} }
inline void ResourceManager::freeExtension(SlotId id) { inline void ResourceManager::freeEightByte(SlotId id) {
auto p = getExtension(id); auto p = getEightByte(id);
variantPools_.freeSlot({reinterpret_cast<SlotData*>(p), id}); eightBytePools_.freeSlot({p, id});
} }
inline VariantExtension* ResourceManager::getExtension(SlotId id) const { inline EightByteValue* ResourceManager::getEightByte(SlotId id) const {
return &variantPools_.getSlot(id)->extension; return eightBytePools_.getSlot(id);
} }
#endif #endif

View File

@ -32,7 +32,26 @@ class StringBuffer {
return node_->data; return node_->data;
} }
StringNode* save() { JsonString str() const {
ARDUINOJSON_ASSERT(node_ != nullptr);
return JsonString(node_->data, node_->length);
}
void save(VariantData* data) {
ARDUINOJSON_ASSERT(node_ != nullptr);
const char* s = node_->data;
if (isTinyString(s, size_))
data->setTinyString(adaptString(s, size_));
else
data->setOwnedString(commitStringNode());
}
void saveRaw(VariantData* data) {
data->setRawString(commitStringNode());
}
private:
StringNode* commitStringNode() {
ARDUINOJSON_ASSERT(node_ != nullptr); ARDUINOJSON_ASSERT(node_ != nullptr);
node_->data[size_] = 0; node_->data[size_] = 0;
auto node = resources_->getString(adaptString(node_->data, size_)); auto node = resources_->getString(adaptString(node_->data, size_));
@ -52,13 +71,6 @@ class StringBuffer {
return node; return node;
} }
JsonString str() const {
ARDUINOJSON_ASSERT(node_ != nullptr);
return JsonString(node_->data, node_->length);
}
private:
ResourceManager* resources_; ResourceManager* resources_;
StringNode* node_ = nullptr; StringNode* node_ = nullptr;
size_t size_ = 0; size_t size_ = 0;

View File

@ -25,10 +25,18 @@ class StringBuilder {
node_ = resources_->createString(initialCapacity); node_ = resources_->createString(initialCapacity);
} }
StringNode* save() { void save(VariantData* variant) {
ARDUINOJSON_ASSERT(variant != nullptr);
ARDUINOJSON_ASSERT(node_ != nullptr); ARDUINOJSON_ASSERT(node_ != nullptr);
node_->data[size_] = 0;
StringNode* node = resources_->getString(adaptString(node_->data, size_)); char* p = node_->data;
if (isTinyString(p, size_)) {
variant->setTinyString(adaptString(p, size_));
return;
}
p[size_] = 0;
StringNode* node = resources_->getString(adaptString(p, size_));
if (!node) { if (!node) {
node = resources_->resizeString(node_, size_); node = resources_->resizeString(node_, size_);
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
@ -37,7 +45,7 @@ class StringBuilder {
} else { } else {
node->references++; node->references++;
} }
return node; variant->setOwnedString(node);
} }
void append(const char* s) { void append(const char* s) {

View File

@ -305,7 +305,7 @@ class MsgPackDeserializer {
if (err) if (err)
return err; return err;
variant->setOwnedString(stringBuffer_.save()); stringBuffer_.save(variant);
return DeserializationError::Ok; return DeserializationError::Ok;
} }
@ -334,7 +334,7 @@ class MsgPackDeserializer {
if (err) if (err)
return err; return err;
variant->setRawString(stringBuffer_.save()); stringBuffer_.saveRaw(variant);
return DeserializationError::Ok; return DeserializationError::Ok;
} }
@ -403,19 +403,16 @@ class MsgPackDeserializer {
JsonString key = stringBuffer_.str(); JsonString key = stringBuffer_.str();
TFilter memberFilter = filter[key.c_str()]; TFilter memberFilter = filter[key.c_str()];
VariantData* member; VariantData* member = 0;
if (memberFilter.allow()) { if (memberFilter.allow()) {
ARDUINOJSON_ASSERT(object != 0); ARDUINOJSON_ASSERT(object != 0);
// Save key in memory pool. auto keyVariant = object->addPair(&member, resources_);
auto savedKey = stringBuffer_.save(); if (!keyVariant)
member = object->addMember(savedKey, resources_);
if (!member)
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
} else {
member = 0; stringBuffer_.save(keyVariant);
} }
err = parseVariant(member, memberFilter, nestingLimit.decrement()); err = parseVariant(member, memberFilter, nestingLimit.decrement());

View File

@ -10,9 +10,11 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class ObjectData : public CollectionData { class ObjectData : public CollectionData {
public: public:
template <typename TAdaptedString> // also works with StringNode* template <typename TAdaptedString>
VariantData* addMember(TAdaptedString key, ResourceManager* resources); VariantData* addMember(TAdaptedString key, ResourceManager* resources);
VariantData* addPair(VariantData** value, ResourceManager* resources);
template <typename TAdaptedString> template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources); VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources);

View File

@ -68,9 +68,25 @@ inline VariantData* ObjectData::addMember(TAdaptedString key,
return valueSlot.ptr(); return valueSlot.ptr();
} }
inline VariantData* ObjectData::addPair(VariantData** value,
ResourceManager* resources) {
auto keySlot = resources->allocVariant();
if (!keySlot)
return nullptr;
auto valueSlot = resources->allocVariant();
if (!valueSlot)
return nullptr;
*value = valueSlot.ptr();
CollectionData::appendPair(keySlot, valueSlot, resources);
return keySlot.ptr();
}
// Returns the size (in bytes) of an object with n members. // Returns the size (in bytes) of an object with n members.
constexpr size_t sizeofObject(size_t n) { constexpr size_t sizeofObject(size_t n) {
return 2 * n * ResourceManager::slotSize; return 2 * n * sizeof(VariantData);
} }
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -76,6 +76,16 @@ struct StringAdapter<TChar*, enable_if_t<IsChar<TChar>::value>> {
} }
}; };
template <typename TChar>
struct StringAdapter<TChar[], enable_if_t<IsChar<TChar>::value>> {
using AdaptedString = RamString;
static AdaptedString adapt(const TChar* p) {
auto str = reinterpret_cast<const char*>(p);
return AdaptedString(str, str ? ::strlen(str) : 0);
}
};
template <size_t N> template <size_t N>
struct StringAdapter<const char (&)[N]> { struct StringAdapter<const char (&)[N]> {
using AdaptedString = RamString; using AdaptedString = RamString;

View File

@ -230,9 +230,9 @@ class StringBuilderPrint : public Print {
copier_.startString(); copier_.startString();
} }
StringNode* save() { void save(VariantData* data) {
ARDUINOJSON_ASSERT(!overflowed()); ARDUINOJSON_ASSERT(!overflowed());
return copier_.save(); copier_.save(data);
} }
size_t write(uint8_t c) { size_t write(uint8_t c) {
@ -268,7 +268,7 @@ inline void convertToJson(const ::Printable& src, JsonVariant dst) {
src.printTo(print); src.printTo(print);
if (print.overflowed()) if (print.overflowed())
return; return;
data->setOwnedString(print.save()); print.save(data);
} }
#endif #endif

View File

@ -16,14 +16,13 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
enum class VariantTypeBits : uint8_t { enum class VariantTypeBits : uint8_t {
OwnedStringBit = 0x01, // 0000 0001 OwnedStringBit = 0x01, // 0000 0001
NumberBit = 0x08, // 0000 1000 NumberBit = 0x08, // 0000 1000
#if ARDUINOJSON_USE_EXTENSIONS EightByteBit = 0x10, // 0001 0000
ExtensionBit = 0x10, // 0001 0000
#endif
CollectionMask = 0x60, CollectionMask = 0x60,
}; };
enum class VariantType : uint8_t { enum class VariantType : uint8_t {
Null = 0, // 0000 0000 Null = 0, // 0000 0000
TinyString = 0x02, // 0000 0010
RawString = 0x03, // 0000 0011 RawString = 0x03, // 0000 0011
LinkedString = 0x04, // 0000 0100 LinkedString = 0x04, // 0000 0100
OwnedString = 0x05, // 0000 0101 OwnedString = 0x05, // 0000 0101
@ -46,6 +45,8 @@ inline bool operator&(VariantType type, VariantTypeBits bit) {
return (uint8_t(type) & uint8_t(bit)) != 0; return (uint8_t(type) & uint8_t(bit)) != 0;
} }
const size_t tinyStringMaxLength = 3;
union VariantContent { union VariantContent {
VariantContent() {} VariantContent() {}
@ -58,10 +59,11 @@ union VariantContent {
ObjectData asObject; ObjectData asObject;
CollectionData asCollection; CollectionData asCollection;
struct StringNode* asOwnedString; struct StringNode* asOwnedString;
char asTinyString[tinyStringMaxLength + 1];
}; };
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
union VariantExtension { union EightByteValue {
# if ARDUINOJSON_USE_LONG_LONG # if ARDUINOJSON_USE_LONG_LONG
uint64_t asUint64; uint64_t asUint64;
int64_t asInt64; int64_t asInt64;
@ -70,6 +72,9 @@ union VariantExtension {
double asDouble; double asDouble;
# endif # endif
}; };
static_assert(sizeof(EightByteValue) == 8,
"sizeof(EightByteValue) must be 8 bytes");
#endif #endif
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -17,6 +17,16 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
template <typename T> template <typename T>
T parseNumber(const char* s); T parseNumber(const char* s);
template <typename T>
static bool isTinyString(const T& s, size_t n) {
if (n > tinyStringMaxLength)
return false;
bool containsNul = false;
for (uint8_t i = 0; i < uint8_t(n); i++)
containsNul |= !s[i];
return !containsNul;
}
class VariantData { class VariantData {
VariantContent content_; // must be first to allow cast from array to variant VariantContent content_; // must be first to allow cast from array to variant
VariantType type_; VariantType type_;
@ -43,8 +53,8 @@ class VariantData {
template <typename TVisitor> template <typename TVisitor>
typename TVisitor::result_type accept( typename TVisitor::result_type accept(
TVisitor& visit, const ResourceManager* resources) const { TVisitor& visit, const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
auto extension = getExtension(resources); auto eightByteValue = getEightByte(resources);
#else #else
(void)resources; // silence warning (void)resources; // silence warning
#endif #endif
@ -54,7 +64,7 @@ class VariantData {
#if ARDUINOJSON_USE_DOUBLE #if ARDUINOJSON_USE_DOUBLE
case VariantType::Double: case VariantType::Double:
return visit.visit(extension->asDouble); return visit.visit(eightByteValue->asDouble);
#endif #endif
case VariantType::Array: case VariantType::Array:
@ -63,6 +73,9 @@ class VariantData {
case VariantType::Object: case VariantType::Object:
return visit.visit(content_.asObject); return visit.visit(content_.asObject);
case VariantType::TinyString:
return visit.visit(JsonString(content_.asTinyString));
case VariantType::LinkedString: case VariantType::LinkedString:
return visit.visit(JsonString(asLinkedString(resources), true)); return visit.visit(JsonString(asLinkedString(resources), true));
@ -82,10 +95,10 @@ class VariantData {
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
case VariantType::Int64: case VariantType::Int64:
return visit.visit(extension->asInt64); return visit.visit(eightByteValue->asInt64);
case VariantType::Uint64: case VariantType::Uint64:
return visit.visit(extension->asUint64); return visit.visit(eightByteValue->asUint64);
#endif #endif
case VariantType::Boolean: case VariantType::Boolean:
@ -132,8 +145,8 @@ class VariantData {
} }
bool asBoolean(const ResourceManager* resources) const { bool asBoolean(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
auto extension = getExtension(resources); auto eightByteValue = getEightByte(resources);
#else #else
(void)resources; // silence warning (void)resources; // silence warning
#endif #endif
@ -147,14 +160,14 @@ class VariantData {
return content_.asFloat != 0; return content_.asFloat != 0;
#if ARDUINOJSON_USE_DOUBLE #if ARDUINOJSON_USE_DOUBLE
case VariantType::Double: case VariantType::Double:
return extension->asDouble != 0; return eightByteValue->asDouble != 0;
#endif #endif
case VariantType::Null: case VariantType::Null:
return false; return false;
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64: case VariantType::Uint64:
case VariantType::Int64: case VariantType::Int64:
return extension->asUint64 != 0; return eightByteValue->asUint64 != 0;
#endif #endif
default: default:
return true; return true;
@ -180,8 +193,8 @@ class VariantData {
template <typename T> template <typename T>
T asFloat(const ResourceManager* resources) const { T asFloat(const ResourceManager* resources) const {
static_assert(is_floating_point<T>::value, "T must be a floating point"); static_assert(is_floating_point<T>::value, "T must be a floating point");
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
auto extension = getExtension(resources); auto eightByteValue = getEightByte(resources);
#else #else
(void)resources; // silence warning (void)resources; // silence warning
#endif #endif
@ -195,10 +208,13 @@ class VariantData {
return static_cast<T>(content_.asInt32); return static_cast<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64: case VariantType::Uint64:
return static_cast<T>(extension->asUint64); return static_cast<T>(eightByteValue->asUint64);
case VariantType::Int64: case VariantType::Int64:
return static_cast<T>(extension->asInt64); return static_cast<T>(eightByteValue->asInt64);
#endif #endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString: case VariantType::LinkedString:
str = asLinkedString(resources); str = asLinkedString(resources);
break; break;
@ -209,7 +225,7 @@ class VariantData {
return static_cast<T>(content_.asFloat); return static_cast<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE #if ARDUINOJSON_USE_DOUBLE
case VariantType::Double: case VariantType::Double:
return static_cast<T>(extension->asDouble); return static_cast<T>(eightByteValue->asDouble);
#endif #endif
default: default:
return 0.0; return 0.0;
@ -222,8 +238,8 @@ class VariantData {
template <typename T> template <typename T>
T asIntegral(const ResourceManager* resources) const { T asIntegral(const ResourceManager* resources) const {
static_assert(is_integral<T>::value, "T must be an integral type"); static_assert(is_integral<T>::value, "T must be an integral type");
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
auto extension = getExtension(resources); auto eightByteValue = getEightByte(resources);
#else #else
(void)resources; // silence warning (void)resources; // silence warning
#endif #endif
@ -237,10 +253,13 @@ class VariantData {
return convertNumber<T>(content_.asInt32); return convertNumber<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64: case VariantType::Uint64:
return convertNumber<T>(extension->asUint64); return convertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64: case VariantType::Int64:
return convertNumber<T>(extension->asInt64); return convertNumber<T>(eightByteValue->asInt64);
#endif #endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString: case VariantType::LinkedString:
str = asLinkedString(resources); str = asLinkedString(resources);
break; break;
@ -251,7 +270,7 @@ class VariantData {
return convertNumber<T>(content_.asFloat); return convertNumber<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE #if ARDUINOJSON_USE_DOUBLE
case VariantType::Double: case VariantType::Double:
return convertNumber<T>(extension->asDouble); return convertNumber<T>(eightByteValue->asDouble);
#endif #endif
default: default:
return 0; return 0;
@ -283,6 +302,8 @@ class VariantData {
JsonString asString(const ResourceManager* resources) const { JsonString asString(const ResourceManager* resources) const {
switch (type_) { switch (type_) {
case VariantType::TinyString:
return JsonString(content_.asTinyString);
case VariantType::LinkedString: case VariantType::LinkedString:
return JsonString(asLinkedString(resources), true); return JsonString(asLinkedString(resources), true);
case VariantType::OwnedString: case VariantType::OwnedString:
@ -293,8 +314,8 @@ class VariantData {
} }
} }
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
const VariantExtension* getExtension(const ResourceManager* resources) const; const EightByteValue* getEightByte(const ResourceManager* resources) const;
#endif #endif
VariantData* getElement(size_t index, VariantData* getElement(size_t index,
@ -357,7 +378,7 @@ class VariantData {
template <typename T> template <typename T>
bool isInteger(const ResourceManager* resources) const { bool isInteger(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
auto extension = getExtension(resources); auto eightByteValue = getEightByte(resources);
#else #else
(void)resources; // silence warning (void)resources; // silence warning
#endif #endif
@ -370,10 +391,10 @@ class VariantData {
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64: case VariantType::Uint64:
return canConvertNumber<T>(extension->asUint64); return canConvertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64: case VariantType::Int64:
return canConvertNumber<T>(extension->asInt64); return canConvertNumber<T>(eightByteValue->asInt64);
#endif #endif
default: default:
@ -397,7 +418,8 @@ class VariantData {
bool isString() const { bool isString() const {
return type_ == VariantType::LinkedString || return type_ == VariantType::LinkedString ||
type_ == VariantType::OwnedString; type_ == VariantType::OwnedString ||
type_ == VariantType::TinyString;
} }
size_t nesting(const ResourceManager* resources) const { size_t nesting(const ResourceManager* resources) const {
@ -490,11 +512,6 @@ class VariantData {
template <typename TAdaptedString> template <typename TAdaptedString>
bool setString(TAdaptedString value, ResourceManager* resources); bool setString(TAdaptedString value, ResourceManager* resources);
bool setString(StringNode* s, ResourceManager*) {
setOwnedString(s);
return true;
}
template <typename TAdaptedString> template <typename TAdaptedString>
static void setString(VariantData* var, TAdaptedString value, static void setString(VariantData* var, TAdaptedString value,
ResourceManager* resources) { ResourceManager* resources) {
@ -506,6 +523,23 @@ class VariantData {
bool setLinkedString(const char* s, ResourceManager* resources); bool setLinkedString(const char* s, ResourceManager* resources);
template <typename TAdaptedString>
void setTinyString(const TAdaptedString& s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s.size() <= tinyStringMaxLength);
type_ = VariantType::TinyString;
auto n = uint8_t(s.size());
for (uint8_t i = 0; i < n; i++) {
char c = s[i];
ARDUINOJSON_ASSERT(c != 0); // no NUL in tiny string
content_.asTinyString[i] = c;
}
content_.asTinyString[n] = 0;
}
void setOwnedString(StringNode* s) { void setOwnedString(StringNode* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s); ARDUINOJSON_ASSERT(s);

View File

@ -43,6 +43,11 @@ inline bool VariantData::setString(TAdaptedString value,
if (value.isStatic()) if (value.isStatic())
return setLinkedString(value.data(), resources); return setLinkedString(value.data(), resources);
if (isTinyString(value, value.size())) {
setTinyString(value);
return true;
}
auto dup = resources->saveString(value); auto dup = resources->saveString(value);
if (dup) { if (dup) {
setOwnedString(dup); setOwnedString(dup);
@ -56,9 +61,9 @@ inline void VariantData::clear(ResourceManager* resources) {
if (type_ & VariantTypeBits::OwnedStringBit) if (type_ & VariantTypeBits::OwnedStringBit)
resources->dereferenceString(content_.asOwnedString->data); resources->dereferenceString(content_.asOwnedString->data);
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
if (type_ & VariantTypeBits::ExtensionBit) if (type_ & VariantTypeBits::EightByteBit)
resources->freeExtension(content_.asSlotId); resources->freeEightByte(content_.asSlotId);
#endif #endif
auto collection = asCollection(); auto collection = asCollection();
@ -68,12 +73,12 @@ inline void VariantData::clear(ResourceManager* resources) {
type_ = VariantType::Null; type_ = VariantType::Null;
} }
#if ARDUINOJSON_USE_EXTENSIONS #if ARDUINOJSON_USE_8_BYTE_POOL
inline const VariantExtension* VariantData::getExtension( inline const EightByteValue* VariantData::getEightByte(
const ResourceManager* resources) const { const ResourceManager* resources) const {
return type_ & VariantTypeBits::ExtensionBit return type_ & VariantTypeBits::EightByteBit
? resources->getExtension(content_.asSlotId) ? resources->getEightByte(content_.asSlotId)
: nullptr; : 0;
} }
#endif #endif
@ -96,12 +101,12 @@ enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat(
type_ = VariantType::Float; type_ = VariantType::Float;
content_.asFloat = valueAsFloat; content_.asFloat = valueAsFloat;
} else { } else {
auto extension = resources->allocExtension(); auto slot = resources->allocEightByte();
if (!extension) if (!slot)
return false; return false;
type_ = VariantType::Double; type_ = VariantType::Double;
content_.asSlotId = extension.id(); content_.asSlotId = slot.id();
extension->asDouble = value; slot->asDouble = value;
} }
#else #else
type_ = VariantType::Float; type_ = VariantType::Float;
@ -122,12 +127,12 @@ enable_if_t<is_signed<T>::value, bool> VariantData::setInteger(
} }
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
else { else {
auto extension = resources->allocExtension(); auto slot = resources->allocEightByte();
if (!extension) if (!slot)
return false; return false;
type_ = VariantType::Int64; type_ = VariantType::Int64;
content_.asSlotId = extension.id(); content_.asSlotId = slot.id();
extension->asInt64 = value; slot->asInt64 = value;
} }
#endif #endif
return true; return true;
@ -145,12 +150,12 @@ enable_if_t<is_unsigned<T>::value, bool> VariantData::setInteger(
} }
#if ARDUINOJSON_USE_LONG_LONG #if ARDUINOJSON_USE_LONG_LONG
else { else {
auto extension = resources->allocExtension(); auto slot = resources->allocEightByte();
if (!extension) if (!slot)
return false; return false;
type_ = VariantType::Uint64; type_ = VariantType::Uint64;
content_.asSlotId = extension.id(); content_.asSlotId = slot.id();
extension->asUint64 = value; slot->asUint64 = value;
} }
#endif #endif
return true; return true;

View File

@ -4,8 +4,8 @@
#pragma once #pragma once
#define ARDUINOJSON_VERSION "7.3.0" #define ARDUINOJSON_VERSION "7.4.1"
#define ARDUINOJSON_VERSION_MAJOR 7 #define ARDUINOJSON_VERSION_MAJOR 7
#define ARDUINOJSON_VERSION_MINOR 3 #define ARDUINOJSON_VERSION_MINOR 4
#define ARDUINOJSON_VERSION_REVISION 0 #define ARDUINOJSON_VERSION_REVISION 1
#define ARDUINOJSON_VERSION_MACRO V730 #define ARDUINOJSON_VERSION_MACRO V741