Compare commits

..

7 Commits

Author SHA1 Message Date
Benoit Blanchon
f95dd7d204 Fix overflowed() 2025-02-27 13:42:05 +01:00
Benoit Blanchon
b529056a4a Fix tests on VS 2025-02-27 13:42:04 +01:00
Benoit Blanchon
ee144b89a2 Deduplicate static strings 2025-02-27 13:42:04 +01:00
Benoit Blanchon
08b2400592 Store static string in a dedicated pool 2025-02-27 13:42:04 +01:00
Benoit Blanchon
907bb43481 Use strlen() for literals too 2025-02-27 13:41:25 +01:00
Benoit Blanchon
7dfa163695 Update changelog 2025-02-27 13:41:25 +01:00
Benoit Blanchon
905176ea17 Remove JsonString third argument 2025-02-27 13:41:07 +01:00
118 changed files with 2633 additions and 2368 deletions

View File

@@ -93,40 +93,44 @@ jobs:
fail-fast: false
matrix:
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"
runner: ubuntu-22.04
archive: focal
runner: ubuntu-20.04
- clang: "8"
cxxflags: -fsanitize=leak -fno-sanitize-recover=all
runner: ubuntu-22.04
archive: focal
runner: ubuntu-20.04
- clang: "9"
cxxflags: -fsanitize=undefined -fno-sanitize-recover=all
runner: ubuntu-22.04
archive: focal
runner: ubuntu-20.04
- clang: "10"
cxxflags: -fsanitize=address -fno-sanitize-recover=all
runner: ubuntu-22.04
archive: focal
runner: ubuntu-20.04
- clang: "11"
runner: ubuntu-22.04
- clang: "12"
runner: ubuntu-22.04
- clang: "13"
runner: ubuntu-22.04
- clang: 14
- clang: 15
- clang: 16
- clang: 17
- clang: 18
- clang: 19
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
- clang: "14"
runner: ubuntu-22.04
- clang: "15"
runner: ubuntu-22.04
runs-on: ${{ matrix.runner }}
steps:
- name: Add archive repositories
if: matrix.archive
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 }} universe'
- name: Install Clang ${{ matrix.clang }}
@@ -134,9 +138,10 @@ jobs:
sudo apt-get update
sudo apt-get install -y clang-${{ 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
- 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
- name: Checkout
uses: actions/checkout@v4
@@ -158,7 +163,7 @@ jobs:
conf_test:
name: Test configuration on Linux
needs: [gcc, clang]
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Install
run: |
@@ -179,19 +184,19 @@ jobs:
conf_test_windows:
name: Test configuration on Windows
runs-on: windows-2022
runs-on: windows-2019
needs: [gcc, clang]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: 32-bit
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
cl /Isrc extras/conf_test/x86.cpp
shell: cmd
- name: 64-bit
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cl /Isrc extras/conf_test/x64.cpp
shell: cmd
@@ -252,7 +257,7 @@ jobs:
board: arduino:avr:uno
- core: arduino:samd
board: arduino:samd:mkr1000
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -398,9 +403,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install Particle CLI
run: |
bash <( curl -sL https://particle.io/install-cli )
echo "$HOME/bin" >> $GITHUB_PATH
run: sudo npm install -g particle-cli
- name: Login to Particle
run: particle login -t "${{ secrets.PARTICLE_TOKEN }}"
- name: Compile
@@ -409,7 +412,7 @@ jobs:
arm:
name: GCC for ARM processor
needs: gcc
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Install
run: |
@@ -428,7 +431,7 @@ jobs:
coverage:
needs: gcc
name: Coverage
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Install
run: sudo apt-get install -y lcov ninja-build
@@ -460,7 +463,7 @@ jobs:
valgrind:
needs: gcc
name: Valgrind
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Install
run: |
@@ -482,24 +485,24 @@ jobs:
clang-tidy:
needs: clang
name: Clang-Tidy
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- name: Install
run: sudo apt-get install -y clang-tidy libc++-dev libc++abi-dev
run: sudo apt-get install -y clang-tidy cmake ninja-build
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: cmake -G Ninja -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" -DCMAKE_BUILD_TYPE=Debug .
run: cmake -G Ninja -DCMAKE_CXX_CLANG_TIDY="clang-tidy-10;--warnings-as-errors=*" -DCMAKE_BUILD_TYPE=Debug .
env:
CC: clang
CXX: clang++
CC: clang-10
CXX: clang++-10
- name: Check
run: cmake --build . -- -k 0
amalgamate:
needs: gcc
name: Amalgamate ArduinoJson.h
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -575,7 +578,7 @@ jobs:
codeql:
name: CodeQL
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
needs: gcc
permissions:
@@ -584,20 +587,20 @@ jobs:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: cpp
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: cpp
- name: Build
run: |
cmake -DCMAKE_BUILD_TYPE=Debug .
cmake --build .
- name: Build
run: |
cmake -DCMAKE_BUILD_TYPE=Debug .
cmake --build .
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:cpp"
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:cpp"

View File

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

View File

@@ -4,58 +4,41 @@ ArduinoJson: change log
HEAD
----
* Don't store string literals by pointer anymore (issue #2189)
Version 7.3 introduced a new way to detect string literals, but it fails in some edge cases.
I could not find a way to fix it, so I chose to remove the optimization rather than keep it broken.
* Replace the "extension slots" mechanism with a memory pool dedicated to 8-byte values.
* Fix conversion from static string to number
* Slightly reduce code size
* Optimize storage of static strings
> ### BREAKING CHANGES
>
> #### `JsonString` constructor's boolean parameter
> Static string cannot contain NUL characters anymore (they could since 7.3.0).
> This is an extremely rare case, so you probably won't be affected.
>
> Since version 7.3, you could pass a boolean to `JsonString`'s constructor to force the string to be stored by pointer.
> This optimization has been removed, and you'll get a deprecation warning if you use it.
> To fix the issue, you must remove the boolean argument from the constructor, or better yet, remove `JsonString` altogether.
> For example, the following code produces different output in 7.3 and 7.4:
>
> ```diff
> char name[] = "ArduinoJson";
> - doc["name"] = JsonString(name, true);
> + doc["name"] = name;
> ```cpp
> JsonDocument doc;
> doc["a\0b"] = "c\0d";
> serializeJson(doc, Serial);
> // With Arduino 7.3 -> {"a\u0000b":"c\u0000d"}
> // With Arduino 7.4 -> {"a":"c"}
> ```
>
> #### NUL characters in string literals
>
> Since version 7.3, ArduinoJson has supported NUL characters (`\0`) in string literals.
> This feature has been removed as part of the storage policy change.
> `JsonString` contructor now only accepts two arguments, not three.
> If your code uses `JsonString` to store a string as a pointer, you must remove the size argument.
>
> If you do need to include NULs in your string, you must use a `JsonString` instead:
> For example, if you have something like this:
>
> ```diff
> - doc["strings"] = "hello\0world"
> + doc["strings"] = JsonString("hello\0world", 11)
> ```cpp
> doc["key"] = JsonString(str.c_str(), str.size(), true);
> ```
>
> You must replace with either:
>
> ```cpp
> doc["key"] = JsonString(str.c_str(), true); // store as pointer, cannot contain NUL characters
> doc["key"] = JsonString(str.c_str(), str.size()); // store by copy, NUL characters allowed
> doc["key"] = str; // same as previous line for supported string classes (`String`, `std::string`, etc.)
> ```
v7.4.2 (2025-06-20)
------
* Fix truncated strings on Arduino Due (issue #2181)
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)
------

View File

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

View File

@@ -80,7 +80,7 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
* Continuously tested on
* [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)
* [Clang 7 to 19](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)
* [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/)
* Well documented
@@ -139,6 +139,11 @@ See the [tutorial on arduinojson.org](https://arduinojson.org/v7/doc/serializati
ArduinoJson is thankful to its sponsors. Please give them a visit; they deserve it!
<p>
<a href="https://www.programmingelectronics.com/" rel="sponsored">
<img src="https://arduinojson.org/images/2021/10/programmingeleactronicsacademy.png" alt="Programming Electronics Academy" width="200">
</a>
</p>
<p>
<a href="https://github.com/1technophile" rel="sponsored">
<img alt="1technophile" src="https://avatars.githubusercontent.com/u/12672732?s=40&v=4">

View File

@@ -1,4 +1,4 @@
version: 7.4.2.{build}
version: 7.3.0.{build}
environment:
matrix:
- 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(sizeof(ArduinoJson::detail::VariantData) == 6, "slot size");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 6, "slot size");
void setup() {}
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(sizeof(ArduinoJson::detail::VariantData) == 8, "slot size");
static_assert(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size");
void setup() {}
void loop() {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -105,7 +105,7 @@ TEST_CASE("deserializeJson(MemberProxy)") {
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofString("value")),
Allocate(sizeofStaticStringPool()),
});
}
}

View File

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

View File

@@ -825,7 +825,9 @@ TEST_CASE("shrink filter") {
deserializeJson(doc, "{}", DeserializationOption::Filter(filter));
REQUIRE(spy.log() == AllocatorLog{
Reallocate(sizeofPool(), sizeofObject(1)),
});
REQUIRE(spy.log() ==
AllocatorLog{
Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ TEST_CASE("ElementProxy::add()") {
REQUIRE(doc.as<std::string>() == "[[\"world\"]]");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("world")),
Allocate(sizeofStaticStringPool()),
});
}

View File

@@ -25,7 +25,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
});
}
@@ -35,8 +35,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofString("world")),
Allocate(sizeofStaticStringPool()),
});
}
@@ -47,7 +46,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
@@ -59,9 +58,8 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
@@ -76,7 +74,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
@@ -405,7 +403,7 @@ TEST_CASE("MemberProxy under memory constraints") {
}
SECTION("value slot allocation fails") {
timebomb.setCountdown(1);
timebomb.setCountdown(2);
// fill the pool entirely, but leave one slot for the key
doc["foo"][ARDUINOJSON_POOL_CAPACITY - 4] = 1;
@@ -418,6 +416,7 @@ TEST_CASE("MemberProxy under memory constraints") {
REQUIRE(doc.overflowed() == true);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
AllocateFail(sizeofPool()),
});
}

View File

@@ -32,7 +32,7 @@ TEST_CASE("JsonDocument::add(T)") {
REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
});
}

View File

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

View File

@@ -8,6 +8,8 @@
#include "Allocators.hpp"
#include "Literals.hpp"
using ArduinoJson::detail::addPadding;
TEST_CASE("JsonDocument constructor") {
SpyingAllocator spyingAllocator;
@@ -42,8 +44,6 @@ TEST_CASE("JsonDocument constructor") {
JsonDocument doc2(std::move(doc1));
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
// NOLINTNEXTLINE(clang-analyzer-cplusplus.Move)
REQUIRE(doc1.as<std::string>() == "null");
}
REQUIRE(spyingAllocator.log() == AllocatorLog{
@@ -62,8 +62,7 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofString("world")),
Allocate(sizeofStaticStringPool()),
});
}
@@ -87,7 +86,7 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
});
}

View File

@@ -22,10 +22,10 @@ TEST_CASE("JsonDocument::remove()") {
SECTION("string literal") {
doc["a"] = 1;
doc["ab"_s] = 2;
doc["x"] = 2;
doc["b"] = 3;
doc.remove("ab");
doc.remove("x");
REQUIRE(doc.as<std::string>() == "{\"a\":1,\"b\":3}");
}

View File

@@ -38,7 +38,7 @@ TEST_CASE("JsonDocument::set()") {
REQUIRE(doc.as<const char*>() == "example"_s);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofString("example")),
Allocate(sizeofStaticStringPool()),
});
}
@@ -89,13 +89,6 @@ 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
SECTION("VLA") {
size_t i = 16;

View File

@@ -69,8 +69,21 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() == AllocatorLog{});
}
SECTION("string") {
doc.set("abcdefg");
SECTION("linked string") {
doc.set("hello");
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}
SECTION("owned string") {
doc.set("abcdefg"_s);
REQUIRE(doc.as<std::string>() == "abcdefg");
doc.shrinkToFit();
@@ -92,7 +105,22 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
});
}
SECTION("object key") {
SECTION("linked key") {
doc["key"] = 42;
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":42}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}
SECTION("owned key") {
doc["abcdefg"_s] = 42;
doc.shrinkToFit();
@@ -106,7 +134,22 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
});
}
SECTION("string in array") {
SECTION("linked string in array") {
doc.add("hello");
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofArray(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}
SECTION("owned string in array") {
doc.add("abcdefg"_s);
doc.shrinkToFit();
@@ -120,16 +163,32 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
});
}
SECTION("string in object") {
doc["key"] = "abcdefg"_s;
SECTION("linked string in object") {
doc["key"] = "hello";
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}");
REQUIRE(doc.as<std::string>() == "{\"key\":\"hello\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("abcdefg")),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(2)),
});
}
SECTION("owned string in object") {
doc["key"_s] = "value"_s;
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"value\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("key")),
Allocate(sizeofString("value")),
Reallocate(sizeofPool(), sizeofPool(2)),
});
}

View File

@@ -14,7 +14,7 @@ TEST_CASE("JsonDocument::operator[]") {
SECTION("object") {
doc["abc"_s] = "ABC";
doc["abcd"_s] = "ABCD";
doc["abc\0d"_s] = "ABCD";
SECTION("const char*") {
const char* key = "abc";
@@ -25,20 +25,18 @@ TEST_CASE("JsonDocument::operator[]") {
SECTION("string literal") {
REQUIRE(doc["abc"] == "ABC");
REQUIRE(cdoc["abc"] == "ABC");
REQUIRE(doc["abcd"] == "ABCD");
REQUIRE(cdoc["abcd"] == "ABCD");
}
SECTION("std::string") {
REQUIRE(doc["abc"_s] == "ABC");
REQUIRE(cdoc["abc"_s] == "ABC");
REQUIRE(doc["abcd"_s] == "ABCD");
REQUIRE(cdoc["abcd"_s] == "ABCD");
REQUIRE(doc["abc\0d"_s] == "ABCD");
REQUIRE(cdoc["abc\0d"_s] == "ABCD");
}
SECTION("JsonVariant") {
doc["key1"] = "abc";
doc["key2"] = "abcd"_s;
doc["key2"] = "abc\0d"_s;
doc["key3"] = "foo";
CHECK(doc[doc["key1"]] == "ABC");
@@ -114,7 +112,7 @@ TEST_CASE("JsonDocument::operator[] key storage") {
REQUIRE(doc.as<std::string>() == "{\"hello\":0}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofStaticStringPool()),
});
}

View File

@@ -16,12 +16,26 @@ TEST_CASE("JsonObject::set()") {
JsonObject obj1 = doc1.to<JsonObject>();
JsonObject obj2 = doc2.to<JsonObject>();
SECTION("copy key and string value") {
SECTION("doesn't copy static string in key or value") {
obj1["hello"] = "world";
spy.clearLog();
bool success = obj2.set(obj1);
REQUIRE(success == true);
REQUIRE(obj2["hello"] == "world"_s);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
SECTION("copy local string key and value") {
obj1["hello"_s] = "world"_s;
spy.clearLog();
bool success = obj2.set(obj1);
REQUIRE(success == true);
REQUIRE(obj2["hello"] == "world"_s);
REQUIRE(spy.log() == AllocatorLog{
@@ -74,13 +88,13 @@ TEST_CASE("JsonObject::set()") {
JsonDocument doc3(&timebomb);
JsonObject obj3 = doc3.to<JsonObject>();
obj1["alpha"_s] = 1;
obj1["beta"_s] = 2;
obj1["a"_s] = 1;
obj1["b"_s] = 2;
bool success = obj3.set(obj1);
REQUIRE(success == false);
REQUIRE(doc3.as<std::string>() == "{\"alpha\":1}");
REQUIRE(doc3.as<std::string>() == "{\"a\":1}");
}
SECTION("copy fails in the middle of an array") {

View File

@@ -101,8 +101,34 @@ TEST_CASE("JsonObject::operator[]") {
obj[key] = 42;
REQUIRE(42 == obj[key]);
}
SECTION("should duplicate key and value strings") {
SECTION("string literals") {
obj["hello"] = "world";
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
SECTION("should duplicate char* value") {
obj["hello"] = const_cast<char*>("world");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
SECTION("should duplicate char* key") {
obj[const_cast<char*>("hello")] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
});
}
SECTION("should duplicate char* key&value") {
obj[const_cast<char*>("hello")] = const_cast<char*>("world");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
@@ -110,6 +136,48 @@ TEST_CASE("JsonObject::operator[]") {
});
}
SECTION("should duplicate std::string value") {
obj["hello"] = "world"_s;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
SECTION("should duplicate std::string key") {
obj["hello"_s] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
});
}
SECTION("should duplicate std::string key&value") {
obj["hello"_s] = "world"_s;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofString("world")),
});
}
SECTION("should duplicate a non-static JsonString key") {
obj[JsonString("hello", false)] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
});
}
SECTION("should not duplicate a static JsonString key") {
obj[JsonString("hello", true)] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
SECTION("should ignore null key") {
// object must have a value to make a call to strcmp()
obj["dummy"] = 42;

View File

@@ -9,90 +9,124 @@
#include "Literals.hpp"
template <typename T>
std::string serialize(T value) {
void check(T value, const std::string& expected) {
JsonDocument doc;
doc.to<JsonVariant>().set(value);
std::string output;
serializeJson(doc, output);
return output;
char buffer[256] = "";
size_t returnValue = serializeJson(doc, buffer, sizeof(buffer));
REQUIRE(expected == buffer);
REQUIRE(expected.size() == returnValue);
}
TEST_CASE("serializeJson(JsonVariant)") {
SECTION("JsonVariant") {
CHECK(serialize(JsonVariant()) == "null");
SECTION("Undefined") {
check(JsonVariant(), "null");
}
SECTION("Null string") {
check(static_cast<char*>(0), "null");
}
SECTION("const char*") {
CHECK(serialize(static_cast<const char*>(0)) == "null");
CHECK(serialize("hello") == "\"hello\"");
check("hello", "\"hello\"");
}
SECTION("std::string") {
CHECK(serialize("hello"_s) == "\"hello\"");
CHECK(serialize("hello \"world\""_s) == "\"hello \\\"world\\\"\"");
CHECK(serialize("hello\\world"_s) == "\"hello\\\\world\"");
CHECK(serialize("fifty/fifty"_s) == "\"fifty/fifty\"");
CHECK(serialize("hello'world"_s) == "\"hello'world\"");
CHECK(serialize("hello\bworld"_s) == "\"hello\\bworld\"");
CHECK(serialize("hello\fworld"_s) == "\"hello\\fworld\"");
CHECK(serialize("hello\nworld"_s) == "\"hello\\nworld\"");
CHECK(serialize("hello\rworld"_s) == "\"hello\\rworld\"");
CHECK(serialize("hello\tworld"_s) == "\"hello\\tworld\"");
CHECK(serialize("hello\0world"_s) == "\"hello\\u0000world\"");
SECTION("string") {
check("hello"_s, "\"hello\"");
SECTION("Escape quotation mark") {
check("hello \"world\""_s, "\"hello \\\"world\\\"\"");
}
SECTION("Escape reverse solidus") {
check("hello\\world"_s, "\"hello\\\\world\"");
}
SECTION("Don't escape solidus") {
check("fifty/fifty"_s, "\"fifty/fifty\"");
}
SECTION("Don't escape single quote") {
check("hello'world"_s, "\"hello'world\"");
}
SECTION("Escape backspace") {
check("hello\bworld"_s, "\"hello\\bworld\"");
}
SECTION("Escape formfeed") {
check("hello\fworld"_s, "\"hello\\fworld\"");
}
SECTION("Escape linefeed") {
check("hello\nworld"_s, "\"hello\\nworld\"");
}
SECTION("Escape carriage return") {
check("hello\rworld"_s, "\"hello\\rworld\"");
}
SECTION("Escape tab") {
check("hello\tworld"_s, "\"hello\\tworld\"");
}
SECTION("NUL char") {
check("hello\0world"_s, "\"hello\\u0000world\"");
}
}
SECTION("SerializedValue<const char*>") {
CHECK(serialize(serialized("[1,2]")) == "[1,2]");
check(serialized("[1,2]"), "[1,2]");
}
SECTION("SerializedValue<std::string>") {
CHECK(serialize(serialized("[1,2]"_s)) == "[1,2]");
check(serialized("[1,2]"_s), "[1,2]");
}
SECTION("double") {
CHECK(serialize(0.0) == "0");
CHECK(serialize(-0.0) == "0");
CHECK(serialize(10.0) == "10");
CHECK(serialize(100.0) == "100");
CHECK(serialize(0.1) == "0.1");
CHECK(serialize(0.01) == "0.01");
CHECK(serialize(3.1415927) == "3.1415927");
CHECK(serialize(-3.1415927) == "-3.1415927");
CHECK(serialize(1.7976931348623157E+308) == "1.79769313e308");
CHECK(serialize(4.94065645841247e-324) == "4.94065646e-324");
SECTION("Double") {
check(3.1415927, "3.1415927");
}
SECTION("float") {
SECTION("Float") {
REQUIRE(sizeof(float) == 4);
CHECK(serialize(3.1415927f) == "3.141593");
CHECK(serialize(-3.1415927f) == "-3.141593");
CHECK(serialize(3.4E+38f) == "3.4e38");
CHECK(serialize(1.17549435e-38f) == "1.175494e-38");
check(3.1415927f, "3.141593");
}
SECTION("int") {
CHECK(serialize(0) == "0");
CHECK(serialize(42) == "42");
CHECK(serialize(-42) == "-42");
SECTION("Zero") {
check(0, "0");
}
SECTION("unsigned long") {
CHECK(serialize(4294967295UL) == "4294967295");
SECTION("Integer") {
check(42, "42");
}
SECTION("bool") {
CHECK(serialize(true) == "true");
CHECK(serialize(false) == "false");
SECTION("NegativeLong") {
check(-42, "-42");
}
SECTION("UnsignedLong") {
check(4294967295UL, "4294967295");
}
SECTION("True") {
check(true, "true");
}
SECTION("OneFalse") {
check(false, "false");
}
#if ARDUINOJSON_USE_LONG_LONG
SECTION("int64_t") {
CHECK(serialize(-9223372036854775807 - 1) == "-9223372036854775808");
CHECK(serialize(9223372036854775807) == "9223372036854775807");
SECTION("NegativeInt64") {
check(-9223372036854775807 - 1, "-9223372036854775808");
}
SECTION("uint64_t") {
CHECK(serialize(18446744073709551615U) == "18446744073709551615");
SECTION("PositiveInt64") {
check(9223372036854775807, "9223372036854775807");
}
SECTION("UInt64") {
check(18446744073709551615U, "18446744073709551615");
}
#endif
}

View File

@@ -185,6 +185,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<long>() == 42L);
REQUIRE(variant.as<double>() == 42);
REQUIRE(variant.as<JsonString>() == "42");
REQUIRE(variant.as<JsonString>().isStatic() == true);
}
SECTION("set(\"hello\")") {
@@ -198,7 +199,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<JsonString>() == "hello");
}
SECTION("set(std::string(\"4.2\")) (tiny string optimization)") {
SECTION("set(std::string(\"4.2\"))") {
variant.set("4.2"_s);
REQUIRE(variant.as<bool>() == true);
@@ -207,17 +208,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<const char*>() == "4.2"_s);
REQUIRE(variant.as<std::string>() == "4.2"_s);
REQUIRE(variant.as<JsonString>() == "4.2");
}
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\")") {

View File

@@ -38,14 +38,14 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
REQUIRE(var1.as<std::string>() == "{\"value\":[42]}");
}
SECTION("stores string literals by copy") {
SECTION("stores string literals by pointer") {
var1.set("hello!!");
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofString("hello!!")),
Allocate(sizeofStaticStringPool()),
});
}

View File

@@ -8,7 +8,7 @@
#include "Allocators.hpp"
#include "Literals.hpp"
using namespace ArduinoJson::detail;
using ArduinoJson::detail::sizeofObject;
enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 };
@@ -18,13 +18,14 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
JsonVariant variant = doc.to<JsonVariant>();
SECTION("string literal") {
bool result = variant.set("hello world");
bool result = variant.set("hello\0world");
REQUIRE(result == true);
REQUIRE(variant == "hello world"_s); // stores by copy
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofString(11)),
});
CHECK(variant ==
"hello"_s); // linked string cannot contain '\0' at the moment
CHECK(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
}
SECTION("const char*") {
@@ -64,18 +65,6 @@ 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") {
bool result = variant.set(static_cast<char*>(0));
@@ -141,7 +130,21 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
});
}
SECTION("JsonString") {
SECTION("static JsonString") {
char str[16];
strcpy(str, "hello");
bool result = variant.set(JsonString(str, true));
strcpy(str, "world");
REQUIRE(result == true);
REQUIRE(variant == "world"); // stores by pointer
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
}
SECTION("non-static JsonString") {
char str[16];
strcpy(str, "hello");
@@ -182,11 +185,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant.is<double>() == true);
REQUIRE(variant.as<double>() == 1.2);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
}
SECTION("int32_t") {
@@ -205,11 +208,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant.is<int64_t>() == true);
REQUIRE(variant.as<int64_t>() == -2147483649LL);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
}
SECTION("uint32_t") {
@@ -228,11 +231,11 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant.is<uint64_t>() == true);
REQUIRE(variant.as<uint64_t>() == 4294967296);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
}
SECTION("JsonDocument") {
@@ -254,6 +257,20 @@ TEST_CASE("JsonVariant::set() with not enough memory") {
JsonVariant v = doc.to<JsonVariant>();
SECTION("string literal") {
bool result = v.set("hello world");
REQUIRE(result == false);
REQUIRE(v.isNull());
}
SECTION("static JsonString") {
bool result = v.set(JsonString("hello world", true));
REQUIRE(result == false);
REQUIRE(v.isNull());
}
SECTION("std::string") {
bool result = v.set("hello world!!"_s);
@@ -349,7 +366,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
}
SECTION("float") {
v.set(1.2f);
v.set(1.2);
REQUIRE(spy.log() == AllocatorLog{
Deallocate(sizeofString("world")),
});
@@ -364,7 +381,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
}
}
TEST_CASE("JsonVariant::set() reuses 8-bit slot") {
TEST_CASE("JsonVariant::set() reuses extension slot") {
SpyingAllocator spy;
JsonDocument doc(&spy);
JsonVariant variant = doc.to<JsonVariant>();

View File

@@ -52,12 +52,12 @@ TEST_CASE("JsonVariantConst::operator[]") {
JsonObject object = doc.to<JsonObject>();
object["ab"_s] = "AB";
object["abc"_s] = "ABC";
object["abcd"_s] = "ABCD";
object["abc\0d"_s] = "ABCD";
SECTION("string literal") {
REQUIRE(var["ab"] == "AB"_s);
REQUIRE(var["abc"] == "ABC"_s);
REQUIRE(var["abcd"] == "ABCD"_s);
REQUIRE(var["abc\0d"] == "ABC"_s);
REQUIRE(var["def"].isNull());
REQUIRE(var[0].isNull());
}
@@ -73,7 +73,7 @@ TEST_CASE("JsonVariantConst::operator[]") {
SECTION("supports std::string") {
REQUIRE(var["ab"_s] == "AB"_s);
REQUIRE(var["abc"_s] == "ABC"_s);
REQUIRE(var["abcd"_s] == "ABCD"_s);
REQUIRE(var["abc\0d"_s] == "ABCD"_s);
REQUIRE(var["def"_s].isNull());
}
@@ -91,7 +91,7 @@ TEST_CASE("JsonVariantConst::operator[]") {
SECTION("supports JsonVariant") {
object["key1"] = "ab";
object["key2"] = "abc";
object["key3"] = "abcd"_s;
object["key3"] = "abc\0d"_s;
object["key4"] = "foo";
REQUIRE(var[var["key1"]] == "AB"_s);

View File

@@ -7,7 +7,6 @@ add_executable(MiscTests
conflicts.cpp
issue1967.cpp
issue2129.cpp
issue2166.cpp
JsonString.cpp
NoArduinoHeader.cpp
printable.cpp
@@ -29,23 +28,3 @@ set_tests_properties(Misc
PROPERTIES
LABELS "Catch"
)
add_executable(Issue2181
issue2181.cpp # Cannot be linked with other tests
)
set_target_properties(Issue2181 PROPERTIES UNITY_BUILD OFF)
add_test(Issue2181 Issue2181)
set_tests_properties(Issue2181
PROPERTIES
LABELS "Catch"
)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(Issue2181
PRIVATE
-Wno-keyword-macro # keyword is hidden by macro definition
)
endif()

View File

@@ -13,6 +13,7 @@ TEST_CASE("JsonString") {
CHECK(s.isNull() == true);
CHECK(s.c_str() == 0);
CHECK(s.isStatic() == true);
CHECK(s == JsonString());
CHECK(s != "");
}
@@ -95,6 +96,7 @@ TEST_CASE("JsonString") {
JsonString s("hello world", 5);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
CHECK(s == "hello");
CHECK(s != "hello world");
}

View File

@@ -22,6 +22,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == true);
}
SECTION("null const char*") {
@@ -37,6 +38,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
CHECK(s.data() == p);
}
@@ -44,6 +46,7 @@ TEST_CASE("adaptString()") {
auto s = adaptString(static_cast<const char*>(0), 10);
CHECK(s.isNull() == true);
CHECK(s.isStatic() == false);
}
SECTION("non-null const char* + size") {
@@ -51,6 +54,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
}
SECTION("null Flash string") {
@@ -58,6 +62,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == true);
CHECK(s.size() == 0);
CHECK(s.isStatic() == false);
}
SECTION("non-null Flash string") {
@@ -65,6 +70,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
}
SECTION("std::string") {
@@ -73,6 +79,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
}
SECTION("Arduino String") {
@@ -81,6 +88,7 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
}
SECTION("custom_string") {
@@ -89,14 +97,25 @@ TEST_CASE("adaptString()") {
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
}
SECTION("JsonString") {
JsonString orig("hello");
SECTION("JsonString linked") {
JsonString orig("hello", true);
auto s = adaptString(orig);
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == true);
}
SECTION("JsonString copied") {
JsonString orig("hello", false);
auto s = adaptString(orig);
CHECK(s.isNull() == false);
CHECK(s.size() == 5);
CHECK(s.isStatic() == false);
}
}
@@ -109,7 +128,6 @@ TEST_CASE("IsString<T>") {
CHECK(IsString<const __FlashStringHelper*>::value == true);
CHECK(IsString<const char*>::value == true);
CHECK(IsString<const char[8]>::value == true);
CHECK(IsString<const char[]>::value == true);
CHECK(IsString<::String>::value == true);
CHECK(IsString<::StringSumHelper>::value == true);
CHECK(IsString<const EmptyStruct*>::value == false);

View File

@@ -1,22 +0,0 @@
// 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

@@ -1,15 +0,0 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#define true 0x1
#define false 0x0
#include <ArduinoJson.h>
#include <catch.hpp>
TEST_CASE("Issue #2181") {
JsonDocument doc;
doc["hello"] = "world";
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}");
}

View File

@@ -2,8 +2,6 @@
#define ARDUINOJSON_ENABLE_ALIGNMENT 0
#include <ArduinoJson.h>
#include <ArduinoJson/Memory/Alignment.hpp>
#include <catch.hpp>
TEST_CASE("ARDUINOJSON_ENABLE_ALIGNMENT == 0") {

View File

@@ -1,8 +1,6 @@
#define ARDUINOJSON_ENABLE_ALIGNMENT 1
#include <ArduinoJson.h>
#include <ArduinoJson/Memory/Alignment.hpp>
#include <catch.hpp>
TEST_CASE("ARDUINOJSON_ENABLE_ALIGNMENT == 1") {

View File

@@ -89,71 +89,58 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 4") {
REQUIRE(err != DeserializationError::Ok);
}
SECTION("bin 32") {
auto str = std::string(65536, '?');
auto input = "\xc6\x00\x01\x00\x00"_s + str;
auto err = deserializeMsgPack(doc, input);
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<MsgPackBinary>());
auto binary = doc.as<MsgPackBinary>();
REQUIRE(binary.size() == 65536);
REQUIRE(binary.data() != nullptr);
REQUIRE(std::string(reinterpret_cast<const char*>(binary.data()),
binary.size()) == str);
}
SECTION("ext 32 deserialization") {
auto str = std::string(65536, '?');
auto input = "\xc9\x00\x01\x00\x00\x2a"_s + str;
auto err = deserializeMsgPack(doc, input);
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<MsgPackExtension>());
auto value = doc.as<MsgPackExtension>();
REQUIRE(value.type() == 42);
REQUIRE(value.size() == 65536);
REQUIRE(value.data() != nullptr);
REQUIRE(std::string(reinterpret_cast<const char*>(value.data()),
value.size()) == str);
}
}
SECTION("serializeMsgPack()") {
SECTION("bin 32 serialization") {
auto str = std::string(65536, '?');
doc.set(MsgPackBinary(str.data(), str.size()));
SECTION("bin 32 deserialization") {
auto str = std::string(65536, '?');
auto input = "\xc6\x00\x01\x00\x00"_s + str;
std::string output;
auto result = serializeMsgPack(doc, output);
auto err = deserializeMsgPack(doc, input);
REQUIRE(result == 5 + str.size());
REQUIRE(output == "\xc6\x00\x01\x00\x00"_s + str);
}
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<MsgPackBinary>());
auto binary = doc.as<MsgPackBinary>();
REQUIRE(binary.size() == 65536);
REQUIRE(binary.data() != nullptr);
REQUIRE(std::string(reinterpret_cast<const char*>(binary.data()),
binary.size()) == str);
}
SECTION("ext 32 serialization") {
auto str = std::string(65536, '?');
doc.set(MsgPackExtension(42, str.data(), str.size()));
SECTION("bin 32 serialization") {
auto str = std::string(65536, '?');
doc.set(MsgPackBinary(str.data(), str.size()));
std::string output;
auto result = serializeMsgPack(doc, output);
std::string output;
auto result = serializeMsgPack(doc, output);
REQUIRE(result == 6 + str.size());
REQUIRE(output == "\xc9\x00\x01\x00\x00\x2a"_s + str);
}
REQUIRE(result == 5 + str.size());
REQUIRE(output == "\xc6\x00\x01\x00\x00"_s + str);
}
SECTION("str 32 serialization") {
auto str = std::string(65536, '?');
doc.set(str);
SECTION("ext 32 deserialization") {
auto str = std::string(65536, '?');
auto input = "\xc9\x00\x01\x00\x00\x2a"_s + str;
std::string output;
auto result = serializeMsgPack(doc, output);
auto err = deserializeMsgPack(doc, input);
REQUIRE(result == 5 + str.size());
REQUIRE(output == "\xDB\x00\x01\x00\x00"_s + str);
}
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<MsgPackExtension>());
auto value = doc.as<MsgPackExtension>();
REQUIRE(value.type() == 42);
REQUIRE(value.size() == 65536);
REQUIRE(value.data() != nullptr);
REQUIRE(std::string(reinterpret_cast<const char*>(value.data()),
value.size()) == str);
}
SECTION("ext 32 serialization") {
auto str = std::string(65536, '?');
doc.set(MsgPackExtension(42, str.data(), str.size()));
std::string output;
auto result = serializeMsgPack(doc, output);
REQUIRE(result == 6 + str.size());
REQUIRE(output == "\xc9\x00\x01\x00\x00\x2a"_s + str);
}
}

View File

@@ -5,11 +5,8 @@
#include <ArduinoJson.h>
#include <catch.hpp>
#include "Allocators.hpp"
TEST_CASE("deserialize MsgPack array") {
SpyingAllocator spy;
JsonDocument doc(&spy);
JsonDocument doc;
SECTION("fixarray") {
SECTION("empty") {
@@ -33,24 +30,6 @@ TEST_CASE("deserialize MsgPack array") {
REQUIRE(array[0] == 1);
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") {

View File

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

View File

@@ -105,7 +105,7 @@ TEST_CASE("deserializeMsgPack(MemberProxy)") {
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofString("value")),
Allocate(sizeofStaticStringPool()),
});
}
}

View File

@@ -137,12 +137,11 @@ TEST_CASE("serialize MsgPack value") {
checkVariant(longest.c_str(), "\xDA\xFF\xFF"_s + longest);
}
#if ARDUINOJSON_STRING_LENGTH_SIZE > 2
SECTION("str 32") {
std::string shortest(65536, '?');
checkVariant(shortest.c_str(), "\xDB\x00\x01\x00\x00"_s + shortest);
checkVariant(JsonString(shortest.c_str(), true), // force store by pointer
"\xDB\x00\x01\x00\x00"_s + shortest);
}
#endif
SECTION("serialized(const char*)") {
checkVariant(serialized("\xDA\xFF\xFF"), "\xDA\xFF\xFF");

View File

@@ -4,6 +4,7 @@
add_executable(NumbersTests
convertNumber.cpp
decomposeFloat.cpp
parseDouble.cpp
parseFloat.cpp
parseInteger.cpp

View File

@@ -0,0 +1,42 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Numbers/FloatParts.hpp>
#include <catch.hpp>
using namespace ArduinoJson::detail;
TEST_CASE("decomposeFloat()") {
SECTION("1.7976931348623157E+308") {
auto parts = decomposeFloat(1.7976931348623157E+308, 9);
REQUIRE(parts.integral == 1);
REQUIRE(parts.decimal == 797693135);
REQUIRE(parts.decimalPlaces == 9);
REQUIRE(parts.exponent == 308);
}
SECTION("4.94065645841247e-324") {
auto parts = decomposeFloat(4.94065645841247e-324, 9);
REQUIRE(parts.integral == 4);
REQUIRE(parts.decimal == 940656458);
REQUIRE(parts.decimalPlaces == 9);
REQUIRE(parts.exponent == -324);
}
SECTION("3.4E+38") {
auto parts = decomposeFloat(3.4E+38f, 6);
REQUIRE(parts.integral == 3);
REQUIRE(parts.decimal == 4);
REQUIRE(parts.decimalPlaces == 1);
REQUIRE(parts.exponent == 38);
}
SECTION("1.17549435e38") {
auto parts = decomposeFloat(1.17549435e-38f, 6);
REQUIRE(parts.integral == 1);
REQUIRE(parts.decimal == 175494);
REQUIRE(parts.decimalPlaces == 6);
REQUIRE(parts.exponent == -38);
}
}

View File

@@ -5,10 +5,10 @@
add_executable(ResourceManagerTests
allocVariant.cpp
clear.cpp
saveStaticString.cpp
saveString.cpp
shrinkToFit.cpp
size.cpp
StringBuffer.cpp
StringBuilder.cpp
swap.cpp
)

View File

@@ -1,51 +0,0 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/StringBuffer.hpp>
#include <ArduinoJson/Variant/VariantImpl.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() == "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::LongString);
auto str = variant.asString();
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::LongString);
REQUIRE(variant.asString() == "alfa");
}
}

View File

@@ -3,12 +3,10 @@
// MIT License
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson;
using namespace ArduinoJson::detail;
TEST_CASE("StringBuilder") {
@@ -18,36 +16,17 @@ TEST_CASE("StringBuilder") {
SECTION("Empty string") {
StringBuilder str(&resources);
VariantData data;
str.startString();
str.save(&data);
str.save();
REQUIRE(resources.size() == sizeofString(""));
REQUIRE(resources.overflowed() == false);
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStringBuffer()),
});
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() == "url");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("")),
});
}
SECTION("Short string fits in first allocation") {
@@ -117,71 +96,48 @@ TEST_CASE("StringBuilder") {
}
}
static VariantData saveString(StringBuilder& builder, const char* s) {
VariantData data;
builder.startString();
builder.append(s);
builder.save(&data);
return data;
static StringNode* addStringToPool(ResourceManager& resources, const char* s) {
StringBuilder str(&resources);
str.startString();
str.append(s);
return str.save();
}
TEST_CASE("StringBuilder::save() deduplicates strings") {
SpyingAllocator spy;
ResourceManager resources(&spy);
StringBuilder builder(&resources);
ResourceManager resources;
SECTION("Basic") {
auto s1 = saveString(builder, "hello");
auto s2 = saveString(builder, "world");
auto s3 = saveString(builder, "hello");
auto s1 = addStringToPool(resources, "hello");
auto s2 = addStringToPool(resources, "world");
auto s3 = addStringToPool(resources, "hello");
REQUIRE(s1.asString() == "hello");
REQUIRE(s2.asString() == "world");
REQUIRE(+s1.asString().c_str() == +s3.asString().c_str()); // same address
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("world")),
Allocate(sizeofStringBuffer()),
});
REQUIRE(s1 == s3);
REQUIRE(s2 != s3);
REQUIRE(s1->references == 2);
REQUIRE(s2->references == 1);
REQUIRE(s3->references == 2);
REQUIRE(resources.size() == sizeofString("hello") + sizeofString("world"));
}
SECTION("Requires terminator") {
auto s1 = saveString(builder, "hello world");
auto s2 = saveString(builder, "hello");
auto s1 = addStringToPool(resources, "hello world");
auto s2 = addStringToPool(resources, "hello");
REQUIRE(s1.asString() == "hello world");
REQUIRE(s2.asString() == "hello");
REQUIRE(+s2.asString().c_str() !=
+s1.asString().c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
});
REQUIRE(s2 != s1);
REQUIRE(s1->references == 1);
REQUIRE(s2->references == 1);
REQUIRE(resources.size() ==
sizeofString("hello world") + sizeofString("hello"));
}
SECTION("Don't overrun") {
auto s1 = saveString(builder, "hello world");
auto s2 = saveString(builder, "worl");
auto s1 = addStringToPool(resources, "hello world");
auto s2 = addStringToPool(resources, "wor");
REQUIRE(s1.asString() == "hello world");
REQUIRE(s2.asString() == "worl");
REQUIRE(s2.asString().c_str() !=
s1.asString().c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
Allocate(sizeofStringBuffer()),
Reallocate(sizeofStringBuffer(), sizeofString("worl")),
});
REQUIRE(s2 != s1);
REQUIRE(s1->references == 1);
REQUIRE(s2->references == 1);
REQUIRE(resources.size() ==
sizeofString("hello world") + sizeofString("wor"));
}
}

View File

@@ -5,8 +5,6 @@
#include <ArduinoJson.hpp>
#include <catch.hpp>
#include <ArduinoJson/Memory/Alignment.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;

View File

@@ -3,8 +3,8 @@
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp>
using namespace ArduinoJson::detail;

View File

@@ -0,0 +1,47 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
using namespace ArduinoJson::detail;
TEST_CASE("ResourceManager::saveStaticString() deduplicates strings") {
SpyingAllocator spy;
ResourceManager resources(&spy);
auto str1 = "hello";
auto str2 = "world";
auto id1 = resources.saveStaticString(str1);
auto id2 = resources.saveStaticString(str2);
REQUIRE(id1 != id2);
auto id3 = resources.saveStaticString(str1);
REQUIRE(id1 == id3);
resources.shrinkToFit();
REQUIRE(spy.log() ==
AllocatorLog{
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(2)),
});
REQUIRE(resources.overflowed() == false);
}
TEST_CASE("ResourceManager::saveStaticString() when allocation fails") {
SpyingAllocator spy(FailingAllocator::instance());
ResourceManager resources(&spy);
auto slotId = resources.saveStaticString("hello");
REQUIRE(slotId == NULL_SLOT);
REQUIRE(resources.overflowed() == true);
REQUIRE(spy.log() == AllocatorLog{
AllocateFail(sizeofStaticStringPool()),
});
}

View File

@@ -3,7 +3,7 @@
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"

View File

@@ -3,7 +3,7 @@
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"

View File

@@ -4,7 +4,7 @@
#include <ArduinoJson/Memory/Alignment.hpp>
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"

View File

@@ -14,112 +14,106 @@
using namespace ArduinoJson::detail;
template <typename TFloat>
static std::string toString(TFloat input) {
void check(TFloat input, const std::string& expected) {
std::string output;
Writer<std::string> sb(output);
TextFormatter<Writer<std::string>> writer(sb);
writer.writeFloat(input);
return output;
REQUIRE(writer.bytesWritten() == output.size());
CHECK(expected == output);
}
TEST_CASE("TextFormatter::writeFloat(double)") {
SECTION("Pi") {
REQUIRE(toString(3.14159265359) == "3.14159265");
check<double>(3.14159265359, "3.141592654");
}
SECTION("Signaling NaN") {
double nan = std::numeric_limits<double>::signaling_NaN();
REQUIRE(toString(nan) == "NaN");
check<double>(nan, "NaN");
}
SECTION("Quiet NaN") {
double nan = std::numeric_limits<double>::quiet_NaN();
REQUIRE(toString(nan) == "NaN");
check<double>(nan, "NaN");
}
SECTION("Infinity") {
double inf = std::numeric_limits<double>::infinity();
REQUIRE(toString(inf) == "Infinity");
REQUIRE(toString(-inf) == "-Infinity");
check<double>(inf, "Infinity");
check<double>(-inf, "-Infinity");
}
SECTION("Zero") {
REQUIRE(toString(0.0) == "0");
REQUIRE(toString(-0.0) == "0");
check<double>(0.0, "0");
check<double>(-0.0, "0");
}
SECTION("Espilon") {
REQUIRE(toString(2.2250738585072014E-308) == "2.22507386e-308");
REQUIRE(toString(-2.2250738585072014E-308) == "-2.22507386e-308");
check<double>(2.2250738585072014E-308, "2.225073859e-308");
check<double>(-2.2250738585072014E-308, "-2.225073859e-308");
}
SECTION("Max double") {
REQUIRE(toString(1.7976931348623157E+308) == "1.79769313e308");
REQUIRE(toString(-1.7976931348623157E+308) == "-1.79769313e308");
check<double>(1.7976931348623157E+308, "1.797693135e308");
check<double>(-1.7976931348623157E+308, "-1.797693135e308");
}
SECTION("Big exponent") {
REQUIRE(toString(1e255) == "1e255");
REQUIRE(toString(1e-255) == "1e-255");
// this test increases coverage of normalize()
check<double>(1e255, "1e255");
check<double>(1e-255, "1e-255");
}
SECTION("Exponentation when <= 1e-5") {
REQUIRE(toString(1e-4) == "0.0001");
REQUIRE(toString(1e-5) == "1e-5");
check<double>(1e-4, "0.0001");
check<double>(1e-5, "1e-5");
REQUIRE(toString(-1e-4) == "-0.0001");
REQUIRE(toString(-1e-5) == "-1e-5");
check<double>(-1e-4, "-0.0001");
check<double>(-1e-5, "-1e-5");
}
SECTION("Exponentation when >= 1e7") {
REQUIRE(toString(9999999.99) == "9999999.99");
REQUIRE(toString(10000000.0) == "1e7");
check<double>(9999999.999, "9999999.999");
check<double>(10000000.0, "1e7");
REQUIRE(toString(-9999999.99) == "-9999999.99");
REQUIRE(toString(-10000000.0) == "-1e7");
check<double>(-9999999.999, "-9999999.999");
check<double>(-10000000.0, "-1e7");
}
SECTION("Rounding when too many decimals") {
REQUIRE(toString(0.000099999999999) == "0.0001");
REQUIRE(toString(0.0000099999999999) == "1e-5");
REQUIRE(toString(0.9999999996) == "1");
check<double>(0.000099999999999, "0.0001");
check<double>(0.0000099999999999, "1e-5");
check<double>(0.9999999996, "1");
}
SECTION("9 decimal places") {
REQUIRE(toString(0.10000001) == "0.10000001");
REQUIRE(toString(0.99999999) == "0.99999999");
check<double>(0.100000001, "0.100000001");
check<double>(0.999999999, "0.999999999");
REQUIRE(toString(9.00000001) == "9.00000001");
REQUIRE(toString(9.99999999) == "9.99999999");
}
SECTION("9 decimal places") {
REQUIRE(toString(0.100000001) == "0.100000001");
REQUIRE(toString(0.999999999) == "0.999999999");
REQUIRE(toString(9.000000001) == "9");
REQUIRE(toString(9.999999999) == "10");
check<double>(9.000000001, "9.000000001");
check<double>(9.999999999, "9.999999999");
}
SECTION("10 decimal places") {
REQUIRE(toString(0.1000000001) == "0.1");
REQUIRE(toString(0.9999999999) == "1");
check<double>(0.1000000001, "0.1");
check<double>(0.9999999999, "1");
REQUIRE(toString(9.0000000001) == "9");
REQUIRE(toString(9.9999999999) == "10");
check<double>(9.0000000001, "9");
check<double>(9.9999999999, "10");
}
}
TEST_CASE("TextFormatter::writeFloat(float)") {
SECTION("Pi") {
REQUIRE(toString(3.14159265359f) == "3.141593");
check<float>(3.14159265359f, "3.141593");
}
SECTION("999.9") { // issue #543
REQUIRE(toString(999.9f) == "999.9");
check<float>(999.9f, "999.9");
}
SECTION("24.3") { // # issue #588
REQUIRE(toString(24.3f) == "24.3");
check<float>(24.3f, "24.3");
}
}

View File

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

View File

@@ -1,13 +1,13 @@
{
"name": "ArduinoJson",
"keywords": "json, rest, http, web",
"description": "A simple and efficient JSON library for embedded C++. ⭐ 6953 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++. ⭐ 6785 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",
"repository": {
"type": "git",
"url": "https://github.com/bblanchon/ArduinoJson.git"
},
"version": "7.4.2",
"version": "7.3.0",
"authors": {
"name": "Benoit Blanchon",
"url": "https://blog.benoitblanchon.fr"

View File

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

View File

@@ -26,15 +26,6 @@
# endif
#endif
// Remove true and false macros defined by some cores, such as Arduino Due's
// See issues #2181 and arduino/ArduinoCore-sam#50
#ifdef true
# undef true
#endif
#ifdef false
# undef false
#endif
#include "ArduinoJson/Array/JsonArray.hpp"
#include "ArduinoJson/Object/JsonObject.hpp"
#include "ArduinoJson/Variant/JsonVariantConst.hpp"
@@ -45,11 +36,13 @@
#include "ArduinoJson/Array/ElementProxy.hpp"
#include "ArduinoJson/Array/Utilities.hpp"
#include "ArduinoJson/Collection/CollectionImpl.hpp"
#include "ArduinoJson/Memory/ResourceManagerImpl.hpp"
#include "ArduinoJson/Object/MemberProxy.hpp"
#include "ArduinoJson/Object/ObjectImpl.hpp"
#include "ArduinoJson/Variant/ConverterImpl.hpp"
#include "ArduinoJson/Variant/JsonVariantCopier.hpp"
#include "ArduinoJson/Variant/VariantCompare.hpp"
#include "ArduinoJson/Variant/VariantImpl.hpp"
#include "ArduinoJson/Variant/VariantRefBaseImpl.hpp"
#include "ArduinoJson/Json/JsonDeserializer.hpp"

View File

@@ -0,0 +1,66 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Collection/CollectionData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class ArrayData : public CollectionData {
public:
VariantData* addElement(ResourceManager* resources);
static VariantData* addElement(ArrayData* array, ResourceManager* resources) {
if (!array)
return nullptr;
return array->addElement(resources);
}
template <typename T>
bool addValue(const T& value, ResourceManager* resources);
template <typename T>
static bool addValue(ArrayData* array, const T& value,
ResourceManager* resources) {
if (!array)
return false;
return array->addValue(value, resources);
}
VariantData* getOrAddElement(size_t index, ResourceManager* resources);
VariantData* getElement(size_t index, const ResourceManager* resources) const;
static VariantData* getElement(const ArrayData* array, size_t index,
const ResourceManager* resources) {
if (!array)
return nullptr;
return array->getElement(index, resources);
}
void removeElement(size_t index, ResourceManager* resources);
static void removeElement(ArrayData* array, size_t index,
ResourceManager* resources) {
if (!array)
return;
array->removeElement(index, resources);
}
void remove(iterator it, ResourceManager* resources) {
CollectionData::removeOne(it, resources);
}
static void remove(ArrayData* array, iterator it,
ResourceManager* resources) {
if (array)
return array->remove(it, resources);
}
private:
iterator at(size_t index, const ResourceManager* resources) const;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -4,47 +4,42 @@
#pragma once
#include <ArduinoJson/Array/ArrayData.hpp>
#include <ArduinoJson/Variant/VariantCompare.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline VariantImpl::iterator VariantImpl::at(size_t index) const {
if (!isArray())
return iterator();
auto it = createIterator();
inline ArrayData::iterator ArrayData::at(
size_t index, const ResourceManager* resources) const {
auto it = createIterator(resources);
while (!it.done() && index) {
it.move(resources_);
it.next(resources);
--index;
}
return it;
}
inline VariantData* VariantImpl::addNewElement(VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isArray());
ARDUINOJSON_ASSERT(resources != nullptr);
inline VariantData* ArrayData::addElement(ResourceManager* resources) {
auto slot = resources->allocVariant();
if (!slot)
return nullptr;
addElement(slot, data, resources);
CollectionData::appendOne(slot, resources);
return slot.ptr();
}
inline VariantData* VariantImpl::getOrAddElement(size_t index) {
auto it = createIterator();
inline VariantData* ArrayData::getOrAddElement(size_t index,
ResourceManager* resources) {
auto it = createIterator(resources);
while (!it.done() && index > 0) {
it.move(resources_);
it.next(resources);
index--;
}
if (it.done())
index++;
VariantData* element = it.data();
while (index > 0) {
element = addNewElement();
element = addElement(resources);
if (!element)
return nullptr;
index--;
@@ -52,17 +47,33 @@ inline VariantData* VariantImpl::getOrAddElement(size_t index) {
return element;
}
inline VariantData* VariantImpl::getElement(size_t index) const {
return at(index).data();
inline VariantData* ArrayData::getElement(
size_t index, const ResourceManager* resources) const {
return at(index, resources).data();
}
inline void VariantImpl::removeElement(size_t index) {
removeElement(at(index));
inline void ArrayData::removeElement(size_t index, ResourceManager* resources) {
remove(at(index, resources), resources);
}
template <typename T>
inline bool ArrayData::addValue(const T& value, ResourceManager* resources) {
ARDUINOJSON_ASSERT(resources != nullptr);
auto slot = resources->allocVariant();
if (!slot)
return false;
JsonVariant variant(slot.ptr(), resources);
if (!variant.set(value)) {
resources->freeVariant(slot);
return false;
}
CollectionData::appendOne(slot, resources);
return true;
}
// Returns the size (in bytes) of an array with n elements.
constexpr size_t sizeofArray(size_t n) {
return n * sizeof(VariantData);
return n * ResourceManager::slotSize;
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -55,15 +55,17 @@ class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
}
FORCE_INLINE VariantData* getData() const {
return VariantAttorney::getVariantImpl(upstream_).getElement(index_);
return VariantData::getElement(
VariantAttorney::getData(upstream_), index_,
VariantAttorney::getResourceManager(upstream_));
}
VariantData* getOrCreateData() const {
auto data = VariantAttorney::getOrCreateData(upstream_);
auto resources = VariantAttorney::getResourceManager(upstream_);
if (data && data->type == VariantType::Null)
data->toArray();
return VariantImpl(data, resources).getOrAddElement(index_);
if (!data)
return nullptr;
return data->getOrAddElement(
index_, VariantAttorney::getResourceManager(upstream_));
}
TUpstream upstream_;

View File

@@ -20,25 +20,24 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
using iterator = JsonArrayIterator;
// Constructs an unbound reference.
JsonArray() {}
JsonArray() : data_(0), resources_(0) {}
// INTERNAL USE ONLY
JsonArray(detail::VariantData* data, detail::ResourceManager* resources)
: impl_(data, resources) {}
// INTERNAL USE ONLY
JsonArray(const detail::VariantImpl& impl) : impl_(impl) {}
JsonArray(detail::ArrayData* data, detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
// Returns a JsonVariant pointing to the array.
// https://arduinojson.org/v7/api/jsonvariant/
operator JsonVariant() {
return JsonVariant(getData(), getResourceManager());
void* data = data_; // prevent warning cast-align
return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
resources_);
}
// Returns a read-only reference to the array.
// https://arduinojson.org/v7/api/jsonarrayconst/
operator JsonArrayConst() const {
return JsonArrayConst(getData(), getResourceManager());
return JsonArrayConst(data_, resources_);
}
// Appends a new (empty) element to the array.
@@ -56,16 +55,15 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
template <typename T, detail::enable_if_t<
detail::is_same<T, JsonVariant>::value, int> = 0>
JsonVariant add() const {
return JsonVariant(impl_.addNewElement(), impl_.resources());
return JsonVariant(detail::ArrayData::addElement(data_, resources_),
resources_);
}
// Appends a value to the array.
// https://arduinojson.org/v7/api/jsonarray/add/
template <typename T>
bool add(const T& value) const {
if (!impl_.isArray())
return false;
return addValue(value, impl_.data(), impl_.resources());
return detail::ArrayData::addValue(data_, value, resources_);
}
// Appends a value to the array.
@@ -73,15 +71,15 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
template <typename T,
detail::enable_if_t<!detail::is_const<T>::value, int> = 0>
bool add(T* value) const {
if (!impl_.isArray())
return false;
return addValue(value, impl_.data(), impl_.resources());
return detail::ArrayData::addValue(data_, value, resources_);
}
// Returns an iterator to the first element of the array.
// https://arduinojson.org/v7/api/jsonarray/begin/
iterator begin() const {
return iterator(impl_.createIterator(), impl_.resources());
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
}
// Returns an iterator following the last element of the array.
@@ -93,6 +91,9 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// Copies an array.
// https://arduinojson.org/v7/api/jsonarray/set/
bool set(JsonArrayConst src) const {
if (!data_)
return false;
clear();
for (auto element : src) {
if (!add(element))
@@ -105,13 +106,13 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// Removes the element at the specified iterator.
// https://arduinojson.org/v7/api/jsonarray/remove/
void remove(iterator it) const {
impl_.removeElement(it.iterator_);
detail::ArrayData::remove(data_, it.iterator_, resources_);
}
// Removes the element at the specified index.
// https://arduinojson.org/v7/api/jsonarray/remove/
void remove(size_t index) const {
impl_.removeElement(index);
detail::ArrayData::removeElement(data_, index, resources_);
}
// Removes the element at the specified index.
@@ -126,7 +127,7 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// Removes all the elements of the array.
// https://arduinojson.org/v7/api/jsonarray/clear/
void clear() const {
impl_.empty();
detail::ArrayData::clear(data_, resources_);
}
// Gets or sets the element at the specified index.
@@ -149,31 +150,31 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
}
operator JsonVariantConst() const {
return JsonVariantConst(getData(), getResourceManager());
return JsonVariantConst(collectionToVariant(data_), resources_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonarray/isnull/
bool isNull() const {
return impl_.isNull();
return data_ == 0;
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonarray/isnull/
operator bool() const {
return !isNull();
return data_ != 0;
}
// Returns the depth (nesting level) of the array.
// https://arduinojson.org/v7/api/jsonarray/nesting/
size_t nesting() const {
return impl_.nesting();
return detail::VariantData::nesting(collectionToVariant(data_), resources_);
}
// Returns the number of elements in the array.
// https://arduinojson.org/v7/api/jsonarray/size/
size_t size() const {
return impl_.size();
return data_ ? data_->size(resources_) : 0;
}
// DEPRECATED: use add<JsonVariant>() instead
@@ -200,40 +201,19 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
private:
detail::ResourceManager* getResourceManager() const {
return impl_.resources();
return resources_;
}
detail::VariantData* getData() const {
return impl_.data();
return collectionToVariant(data_);
}
detail::VariantData* getOrCreateData() const {
return impl_.data();
return collectionToVariant(data_);
}
// HACK: this function has been pulled out of VariantImpl to avoid the
// circular dependency between VariantImpl and JsonVariant
template <typename T>
static bool addValue(const T& value, detail::VariantData* data,
detail::ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isArray());
ARDUINOJSON_ASSERT(resources != nullptr);
auto slot = resources->allocVariant();
if (!slot)
return false;
if (!JsonVariant(slot.ptr(), resources).set(value)) {
detail::VariantImpl::freeVariant(slot, resources);
return false;
}
detail::VariantImpl::addElement(slot, data, resources);
return true;
}
mutable detail::VariantImpl impl_;
detail::ArrayData* data_;
detail::ResourceManager* resources_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -24,7 +24,9 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
// Returns an iterator to the first element of the array.
// https://arduinojson.org/v7/api/jsonarrayconst/begin/
iterator begin() const {
return iterator(impl_.createIterator(), impl_.resources());
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
}
// Returns an iterator to the element following the last element of the array.
@@ -34,21 +36,21 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
}
// Creates an unbound reference.
JsonArrayConst() {}
JsonArrayConst() : data_(0), resources_(0) {}
// INTERNAL USE ONLY
JsonArrayConst(detail::VariantData* data, detail::ResourceManager* resources)
: impl_(data, resources) {}
// INTERNAL USE ONLY
JsonArrayConst(const detail::VariantImpl& impl) : impl_(impl) {}
JsonArrayConst(const detail::ArrayData* data,
const detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
// Returns the element at the specified index.
// https://arduinojson.org/v7/api/jsonarrayconst/subscript/
template <typename T,
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
JsonVariantConst operator[](T index) const {
return JsonVariantConst(impl_.getElement(size_t(index)), impl_.resources());
return JsonVariantConst(
detail::ArrayData::getElement(data_, size_t(index), resources_),
resources_);
}
// Returns the element at the specified index.
@@ -63,31 +65,31 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
}
operator JsonVariantConst() const {
return JsonVariantConst(impl_.data(), impl_.resources());
return JsonVariantConst(getData(), resources_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonarrayconst/isnull/
bool isNull() const {
return impl_.isNull();
return data_ == 0;
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonarrayconst/isnull/
operator bool() const {
return !isNull();
return data_ != 0;
}
// Returns the depth (nesting level) of the array.
// https://arduinojson.org/v7/api/jsonarrayconst/nesting/
size_t nesting() const {
return impl_.nesting();
return detail::VariantData::nesting(getData(), resources_);
}
// Returns the number of elements in the array.
// https://arduinojson.org/v7/api/jsonarrayconst/size/
size_t size() const {
return impl_.size();
return data_ ? data_->size(resources_) : 0;
}
// DEPRECATED: always returns zero
@@ -98,10 +100,11 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
private:
const detail::VariantData* getData() const {
return impl_.data();
return collectionToVariant(data_);
}
detail::VariantImpl impl_;
const detail::ArrayData* data_;
const detail::ResourceManager* resources_;
};
// Compares the content of two arrays.

View File

@@ -30,7 +30,7 @@ class JsonArrayIterator {
public:
JsonArrayIterator() {}
explicit JsonArrayIterator(detail::CollectionIterator iterator,
explicit JsonArrayIterator(detail::ArrayData::iterator iterator,
detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
@@ -50,12 +50,12 @@ class JsonArrayIterator {
}
JsonArrayIterator& operator++() {
iterator_.move(resources_);
iterator_.next(resources_);
return *this;
}
private:
detail::CollectionIterator iterator_;
detail::ArrayData::iterator iterator_;
detail::ResourceManager* resources_;
};
@@ -64,8 +64,8 @@ class JsonArrayConstIterator {
public:
JsonArrayConstIterator() {}
explicit JsonArrayConstIterator(detail::CollectionIterator iterator,
detail::ResourceManager* resources)
explicit JsonArrayConstIterator(detail::ArrayData::iterator iterator,
const detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
JsonVariantConst operator*() const {
@@ -84,13 +84,13 @@ class JsonArrayConstIterator {
}
JsonArrayConstIterator& operator++() {
iterator_.move(resources_);
iterator_.next(resources_);
return *this;
}
private:
mutable detail::CollectionIterator iterator_;
mutable detail::ResourceManager* resources_;
detail::ArrayData::iterator iterator_;
const detail::ResourceManager* resources_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -0,0 +1,122 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Namespace.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
#include <stddef.h> // size_t
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class VariantData;
class ResourceManager;
class CollectionIterator {
friend class CollectionData;
public:
CollectionIterator() : slot_(nullptr), currentId_(NULL_SLOT) {}
void next(const ResourceManager* resources);
bool done() const {
return slot_ == nullptr;
}
bool operator==(const CollectionIterator& other) const {
return slot_ == other.slot_;
}
bool operator!=(const CollectionIterator& other) const {
return slot_ != other.slot_;
}
VariantData* operator->() {
ARDUINOJSON_ASSERT(slot_ != nullptr);
return data();
}
VariantData& operator*() {
ARDUINOJSON_ASSERT(slot_ != nullptr);
return *data();
}
const VariantData& operator*() const {
ARDUINOJSON_ASSERT(slot_ != nullptr);
return *data();
}
VariantData* data() {
return reinterpret_cast<VariantData*>(slot_);
}
const VariantData* data() const {
return reinterpret_cast<const VariantData*>(slot_);
}
private:
CollectionIterator(VariantData* slot, SlotId slotId);
VariantData* slot_;
SlotId currentId_, nextId_;
};
class CollectionData {
SlotId head_ = NULL_SLOT;
SlotId tail_ = NULL_SLOT;
public:
// Placement new
static void* operator new(size_t, void* p) noexcept {
return p;
}
static void operator delete(void*, void*) noexcept {}
using iterator = CollectionIterator;
iterator createIterator(const ResourceManager* resources) const;
size_t size(const ResourceManager*) const;
size_t nesting(const ResourceManager*) const;
void clear(ResourceManager* resources);
static void clear(CollectionData* collection, ResourceManager* resources) {
if (!collection)
return;
collection->clear(resources);
}
SlotId head() const {
return head_;
}
protected:
void appendOne(Slot<VariantData> slot, const ResourceManager* resources);
void appendPair(Slot<VariantData> key, Slot<VariantData> value,
const ResourceManager* resources);
void removeOne(iterator it, ResourceManager* resources);
void removePair(iterator it, ResourceManager* resources);
private:
Slot<VariantData> getPreviousSlot(VariantData*, const ResourceManager*) const;
};
inline const VariantData* collectionToVariant(
const CollectionData* collection) {
const void* data = collection; // prevent warning cast-align
return reinterpret_cast<const VariantData*>(data);
}
inline VariantData* collectionToVariant(CollectionData* collection) {
void* data = collection; // prevent warning cast-align
return reinterpret_cast<VariantData*>(data);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -4,144 +4,134 @@
#pragma once
#include <ArduinoJson/Variant/VariantImpl.hpp>
#include <ArduinoJson/Collection/CollectionData.hpp>
#include <ArduinoJson/Memory/Alignment.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <ArduinoJson/Variant/VariantCompare.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline void CollectionIterator::move(const ResourceManager* resources) {
ARDUINOJSON_ASSERT(slot_);
auto nextId = slot_->next;
slot_ = resources->getVariant(nextId);
currentId_ = nextId;
inline CollectionIterator::CollectionIterator(VariantData* slot, SlotId slotId)
: slot_(slot), currentId_(slotId) {
nextId_ = slot_ ? slot_->next() : NULL_SLOT;
}
inline VariantImpl::iterator VariantImpl::createIterator(
VariantData* data, ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isCollection());
ARDUINOJSON_ASSERT(resources != nullptr);
auto head = data->content.asCollection.head;
return iterator(resources->getVariant(head), head);
inline void CollectionIterator::next(const ResourceManager* resources) {
ARDUINOJSON_ASSERT(currentId_ != NULL_SLOT);
slot_ = resources->getVariant(nextId_);
currentId_ = nextId_;
if (slot_)
nextId_ = slot_->next();
}
inline void VariantImpl::addElement(Slot<VariantData> slot, VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isCollection());
ARDUINOJSON_ASSERT(resources != nullptr);
inline CollectionData::iterator CollectionData::createIterator(
const ResourceManager* resources) const {
return iterator(resources->getVariant(head_), head_);
}
auto coll = &data->content.asCollection;
if (coll->tail != NULL_SLOT) {
auto tail = resources->getVariant(coll->tail);
tail->next = slot.id();
coll->tail = slot.id();
inline void CollectionData::appendOne(Slot<VariantData> slot,
const ResourceManager* resources) {
if (tail_ != NULL_SLOT) {
auto tail = resources->getVariant(tail_);
tail->setNext(slot.id());
tail_ = slot.id();
} else {
coll->head = slot.id();
coll->tail = slot.id();
head_ = slot.id();
tail_ = slot.id();
}
}
inline void VariantImpl::appendPair(Slot<VariantData> key,
Slot<VariantData> value, VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(resources != nullptr);
inline void CollectionData::appendPair(Slot<VariantData> key,
Slot<VariantData> value,
const ResourceManager* resources) {
key->setNext(value.id());
key->next = value.id();
auto coll = &data->content.asCollection;
if (coll->tail != NULL_SLOT) {
auto tail = resources->getVariant(coll->tail);
tail->next = key.id();
coll->tail = value.id();
if (tail_ != NULL_SLOT) {
auto tail = resources->getVariant(tail_);
tail->setNext(key.id());
tail_ = value.id();
} else {
coll->head = key.id();
coll->tail = value.id();
head_ = key.id();
tail_ = value.id();
}
}
inline void VariantImpl::empty(VariantData* data, ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isCollection());
ARDUINOJSON_ASSERT(resources != nullptr);
auto coll = &data->content.asCollection;
auto next = coll->head;
inline void CollectionData::clear(ResourceManager* resources) {
auto next = head_;
while (next != NULL_SLOT) {
auto currId = next;
auto slot = resources->getVariant(next);
next = slot->next;
freeVariant({slot, currId}, resources);
next = slot->next();
resources->freeVariant({slot, currId});
}
coll->head = NULL_SLOT;
coll->tail = NULL_SLOT;
head_ = NULL_SLOT;
tail_ = NULL_SLOT;
}
inline Slot<VariantData> VariantImpl::getPreviousSlot(
VariantData* target) const {
ARDUINOJSON_ASSERT(data_ != nullptr);
ARDUINOJSON_ASSERT(data_->isCollection());
ARDUINOJSON_ASSERT(resources_ != nullptr);
inline Slot<VariantData> CollectionData::getPreviousSlot(
VariantData* target, const ResourceManager* resources) const {
auto prev = Slot<VariantData>();
auto currentId = data_->content.asCollection.head;
auto currentId = head_;
while (currentId != NULL_SLOT) {
auto currentSlot = resources_->getVariant(currentId);
auto currentSlot = resources->getVariant(currentId);
if (currentSlot == target)
break;
prev = Slot<VariantData>(currentSlot, currentId);
currentId = currentSlot->next;
currentId = currentSlot->next();
}
return prev;
}
inline void VariantImpl::removeOne(iterator it) {
inline void CollectionData::removeOne(iterator it, ResourceManager* resources) {
if (it.done())
return;
auto curr = it.slot_;
auto prev = getPreviousSlot(curr);
auto next = curr->next;
auto coll = &data_->content.asCollection;
auto prev = getPreviousSlot(curr, resources);
auto next = curr->next();
if (prev)
prev->next = next;
prev->setNext(next);
else
coll->head = next;
head_ = next;
if (next == NULL_SLOT)
coll->tail = prev.id();
freeVariant({it.slot_, it.currentId_}, resources_);
tail_ = prev.id();
resources->freeVariant({it.slot_, it.currentId_});
}
inline void VariantImpl::removePair(iterator it) {
inline void CollectionData::removePair(ObjectData::iterator it,
ResourceManager* resources) {
if (it.done())
return;
auto keySlot = it.slot_;
auto valueId = keySlot->next;
auto valueSlot = resources_->getVariant(valueId);
auto valueId = it.nextId_;
auto valueSlot = resources->getVariant(valueId);
// remove value slot
keySlot->next = valueSlot->next;
freeVariant({valueSlot, valueId}, resources_);
keySlot->setNext(valueSlot->next());
resources->freeVariant({valueSlot, valueId});
// remove key slot
removeOne(it);
removeOne(it, resources);
}
inline size_t VariantImpl::nesting() const {
if (!data_ || !data_->isCollection())
return 0;
inline size_t CollectionData::nesting(const ResourceManager* resources) const {
size_t maxChildNesting = 0;
for (auto it = createIterator(); !it.done(); it.move(resources_)) {
size_t childNesting = VariantImpl(it.data(), resources_).nesting();
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
size_t childNesting = it->nesting(resources);
if (childNesting > maxChildNesting)
maxChildNesting = childNesting;
}
return maxChildNesting + 1;
}
inline size_t CollectionData::size(const ResourceManager* resources) const {
size_t count = 0;
for (auto it = createIterator(resources); !it.done(); it.next(resources))
count++;
return count;
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -1,68 +0,0 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Namespace.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
#include <stddef.h> // size_t
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
struct VariantData;
class ResourceManager;
class CollectionIterator {
friend class VariantImpl;
public:
CollectionIterator() : slot_(nullptr), currentId_(NULL_SLOT) {}
void move(const ResourceManager* resources);
bool done() const {
return slot_ == nullptr;
}
bool operator==(const CollectionIterator& other) const {
return slot_ == other.slot_;
}
bool operator!=(const CollectionIterator& other) const {
return slot_ != other.slot_;
}
VariantData* operator->() {
ARDUINOJSON_ASSERT(slot_ != nullptr);
return data();
}
VariantData& operator*() {
ARDUINOJSON_ASSERT(slot_ != nullptr);
return *data();
}
const VariantData& operator*() const {
ARDUINOJSON_ASSERT(slot_ != nullptr);
return *data();
}
VariantData* data() {
return slot_;
}
const VariantData* data() const {
return slot_;
}
private:
CollectionIterator(VariantData* slot, SlotId slotId)
: slot_(slot), currentId_(slotId) {}
VariantData* slot_;
SlotId currentId_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

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

View File

@@ -50,7 +50,7 @@ DeserializationError doDeserialize(TDestination&& dst, TReader reader,
auto resources = VariantAttorney::getResourceManager(dst);
dst.clear();
auto err = TDeserializer<TReader>(resources, reader)
.parse(data, options.filter, options.nestingLimit);
.parse(*data, options.filter, options.nestingLimit);
shrinkJsonDocument(dst);
return err;
}

View File

@@ -11,6 +11,7 @@
#include <ArduinoJson/Object/MemberProxy.hpp>
#include <ArduinoJson/Polyfills/utility.hpp>
#include <ArduinoJson/Variant/JsonVariantConst.hpp>
#include <ArduinoJson/Variant/VariantTo.hpp>
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
@@ -87,7 +88,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// https://arduinojson.org/v7/api/jsondocument/clear/
void clear() {
resources_.clear();
data_.type = detail::VariantType::Null;
data_.reset();
}
// Returns true if the root is of the specified type.
@@ -119,13 +120,13 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// Returns the depth (nesting level) of the array.
// https://arduinojson.org/v7/api/jsondocument/nesting/
size_t nesting() const {
return getVariantImpl().nesting();
return data_.nesting(&resources_);
}
// Returns the number of elements in the root array or object.
// https://arduinojson.org/v7/api/jsondocument/size/
size_t size() const {
return getVariantImpl().size();
return data_.size(&resources_);
}
// Copies the specified document.
@@ -145,38 +146,18 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// Replaces the root with the specified value.
// https://arduinojson.org/v7/api/jsondocument/set/
template <typename TChar>
template <typename TChar,
detail::enable_if_t<!detail::is_const<TChar>::value, int> = 0>
bool set(TChar* src) {
return to<JsonVariant>().set(src);
}
// Sets the document to an empty array.
// Clears the document and converts it to the specified type.
// https://arduinojson.org/v7/api/jsondocument/to/
template <typename T,
detail::enable_if_t<detail::is_same<T, JsonArray>::value, int> = 0>
JsonArray to() {
template <typename T>
typename detail::VariantTo<T>::type to() {
clear();
data_.toArray();
return JsonArray(&data_, &resources_);
}
// Sets the document to an empty object.
// https://arduinojson.org/v7/api/jsondocument/to/
template <typename T,
detail::enable_if_t<detail::is_same<T, JsonObject>::value, int> = 0>
JsonObject to() {
clear();
data_.toObject();
return JsonObject(&data_, &resources_);
}
// Sets the document to null.
// https://arduinojson.org/v7/api/jsondocument/to/
template <typename T, detail::enable_if_t<
detail::is_same<T, JsonVariant>::value, int> = 0>
JsonVariant to() {
clear();
return JsonVariant(&data_, &resources_);
return getVariant().template to<T>();
}
// DEPRECATED: use obj["key"].is<T>() instead
@@ -184,7 +165,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename TChar>
ARDUINOJSON_DEPRECATED("use doc[\"key\"].is<T>() instead")
bool containsKey(TChar* key) const {
return getVariantImpl().getMember(detail::adaptString(key)) != 0;
return data_.getMember(detail::adaptString(key), &resources_) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -193,7 +174,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
ARDUINOJSON_DEPRECATED("use doc[key].is<T>() instead")
bool containsKey(const TString& key) const {
return getVariantImpl().getMember(detail::adaptString(key)) != 0;
return data_.getMember(detail::adaptString(key), &resources_) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -217,7 +198,9 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// Gets or sets a root object's member.
// https://arduinojson.org/v7/api/jsondocument/subscript/
template <typename TChar,
detail::enable_if_t<detail::IsString<TChar*>::value, int> = 0>
detail::enable_if_t<detail::IsString<TChar*>::value &&
!detail::is_const<TChar>::value,
int> = 0>
detail::MemberProxy<JsonDocument&, detail::AdaptedString<TChar*>> operator[](
TChar* key) {
return {*this, detail::adaptString(key)};
@@ -229,16 +212,18 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
JsonVariantConst operator[](const TString& key) const {
return JsonVariantConst(
getVariantImpl().getMember(detail::adaptString(key)), &resources_);
data_.getMember(detail::adaptString(key), &resources_), &resources_);
}
// Gets a root object's member.
// https://arduinojson.org/v7/api/jsondocument/subscript/
template <typename TChar,
detail::enable_if_t<detail::IsString<TChar*>::value, int> = 0>
detail::enable_if_t<detail::IsString<TChar*>::value &&
!detail::is_const<TChar>::value,
int> = 0>
JsonVariantConst operator[](TChar* key) const {
return JsonVariantConst(
getVariantImpl().getMember(detail::adaptString(key)), &resources_);
data_.getMember(detail::adaptString(key), &resources_), &resources_);
}
// Gets or sets a root array's element.
@@ -252,7 +237,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// Gets a root array's member.
// https://arduinojson.org/v7/api/jsondocument/subscript/
JsonVariantConst operator[](size_t index) const {
return JsonVariantConst(getVariantImpl().getElement(index), &resources_);
return JsonVariantConst(data_.getElement(index, &resources_), &resources_);
}
// Gets or sets a root object's member.
@@ -282,21 +267,22 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename T, detail::enable_if_t<
detail::is_same<T, JsonVariant>::value, int> = 0>
JsonVariant add() {
return getOrCreateArray().add<T>();
return JsonVariant(data_.addElement(&resources_), &resources_);
}
// Appends a value to the root array.
// https://arduinojson.org/v7/api/jsondocument/add/
template <typename TValue>
bool add(const TValue& value) {
return getOrCreateArray().add(value);
return data_.addValue(value, &resources_);
}
// Appends a value to the root array.
// https://arduinojson.org/v7/api/jsondocument/add/
template <typename TChar>
template <typename TChar,
detail::enable_if_t<!detail::is_const<TChar>::value, int> = 0>
bool add(TChar* value) {
return getOrCreateArray().add(value);
return data_.addValue(value, &resources_);
}
// Removes an element of the root array.
@@ -304,15 +290,19 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename T,
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
void remove(T index) {
getVariantImpl().removeElement(size_t(index));
detail::VariantData::removeElement(getData(), size_t(index),
getResourceManager());
}
// Removes a member of the root object.
// https://arduinojson.org/v7/api/jsondocument/remove/
template <typename TChar,
detail::enable_if_t<detail::IsString<TChar*>::value, int> = 0>
detail::enable_if_t<detail::IsString<TChar*>::value &&
!detail::is_const<TChar>::value,
int> = 0>
void remove(TChar* key) {
getVariantImpl().removeMember(detail::adaptString(key));
detail::VariantData::removeMember(getData(), detail::adaptString(key),
getResourceManager());
}
// Removes a member of the root object.
@@ -320,7 +310,8 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename TString,
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
void remove(const TString& key) {
getVariantImpl().removeMember(detail::adaptString(key));
detail::VariantData::removeMember(getData(), detail::adaptString(key),
getResourceManager());
}
// Removes a member of the root object or an element of the root array.
@@ -400,16 +391,6 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
}
private:
detail::VariantImpl getVariantImpl() const {
return detail::VariantImpl(&data_, &resources_);
}
JsonArray getOrCreateArray() const {
if (data_.type == detail::VariantType::Null)
data_.toArray();
return JsonArray(&data_, &resources_);
}
JsonVariant getVariant() {
return JsonVariant(&data_, &resources_);
}
@@ -434,12 +415,12 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
return &data_;
}
mutable detail::ResourceManager resources_;
mutable detail::VariantData data_;
detail::ResourceManager resources_;
detail::VariantData data_;
};
inline bool convertToJson(const JsonDocument& src, JsonVariant dst) {
return dst.set(src.as<JsonVariantConst>());
inline void convertToJson(const JsonDocument& src, JsonVariant dst) {
dst.set(src.as<JsonVariantConst>());
}
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -28,13 +28,13 @@ class JsonDeserializer {
resources_(resources) {}
template <typename TFilter>
DeserializationError parse(VariantData* variant, TFilter filter,
DeserializationError parse(VariantData& variant, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
err = parseVariant(variant, filter, nestingLimit);
if (!err && latch_.last() != 0 && variant->isFloat()) {
if (!err && latch_.last() != 0 && variant.isFloat()) {
// We don't detect trailing characters earlier, so we need to check now
return DeserializationError::InvalidInput;
}
@@ -60,7 +60,7 @@ class JsonDeserializer {
template <typename TFilter>
DeserializationError::Code parseVariant(
VariantData* variant, TFilter filter,
VariantData& variant, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
@@ -71,13 +71,13 @@ class JsonDeserializer {
switch (current()) {
case '[':
if (filter.allowArray())
return parseArray(variant, filter, nestingLimit);
return parseArray(variant.toArray(), filter, nestingLimit);
else
return skipArray(nestingLimit);
case '{':
if (filter.allowObject())
return parseObject(variant, filter, nestingLimit);
return parseObject(variant.toObject(), filter, nestingLimit);
else
return skipObject(nestingLimit);
@@ -90,12 +90,12 @@ class JsonDeserializer {
case 't':
if (filter.allowValue())
variant->setBoolean(true);
variant.setBoolean(true);
return skipKeyword("true");
case 'f':
if (filter.allowValue())
variant->setBoolean(false);
variant.setBoolean(false);
return skipKeyword("false");
case 'n':
@@ -146,12 +146,10 @@ class JsonDeserializer {
template <typename TFilter>
DeserializationError::Code parseArray(
VariantData* array, TFilter filter,
ArrayData& array, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
array->toArray();
if (nestingLimit.reached())
return DeserializationError::TooDeep;
@@ -174,12 +172,12 @@ class JsonDeserializer {
for (;;) {
if (elementFilter.allow()) {
// Allocate slot in array
VariantData* value = VariantImpl::addNewElement(array, resources_);
VariantData* value = array.addElement(resources_);
if (!value)
return DeserializationError::NoMemory;
// 1 - Parse value
err = parseVariant(value, elementFilter, nestingLimit.decrement());
err = parseVariant(*value, elementFilter, nestingLimit.decrement());
if (err)
return err;
} else {
@@ -234,12 +232,10 @@ class JsonDeserializer {
template <typename TFilter>
DeserializationError::Code parseObject(
VariantData* object, TFilter filter,
ObjectData& object, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
object->toObject();
if (nestingLimit.reached())
return DeserializationError::TooDeep;
@@ -277,20 +273,21 @@ class JsonDeserializer {
TFilter memberFilter = filter[key];
if (memberFilter.allow()) {
auto member =
VariantImpl::getMember(adaptString(key), object, resources_);
auto member = object.getMember(adaptString(key), resources_);
if (!member) {
auto keyVariant = VariantImpl::addPair(&member, object, resources_);
if (!keyVariant)
return DeserializationError::NoMemory;
// Save key in memory pool.
auto savedKey = stringBuilder_.save();
stringBuilder_.save(keyVariant);
// Allocate slot in object
member = object.addMember(savedKey, resources_);
if (!member)
return DeserializationError::NoMemory;
} else {
VariantImpl::clear(member, resources_);
member->clear(resources_);
}
// Parse value
err = parseVariant(member, memberFilter, nestingLimit.decrement());
err = parseVariant(*member, memberFilter, nestingLimit.decrement());
if (err)
return err;
} else {
@@ -384,7 +381,7 @@ class JsonDeserializer {
}
}
DeserializationError::Code parseStringValue(VariantData* variant) {
DeserializationError::Code parseStringValue(VariantData& variant) {
DeserializationError::Code err;
stringBuilder_.startString();
@@ -393,7 +390,7 @@ class JsonDeserializer {
if (err)
return err;
stringBuilder_.save(variant);
variant.setOwnedString(stringBuilder_.save());
return DeserializationError::Ok;
}
@@ -509,7 +506,7 @@ class JsonDeserializer {
return DeserializationError::Ok;
}
DeserializationError::Code parseNumericValue(VariantData* result) {
DeserializationError::Code parseNumericValue(VariantData& result) {
uint8_t n = 0;
char c = current();
@@ -523,28 +520,26 @@ class JsonDeserializer {
auto number = parseNumber(buffer_);
switch (number.type()) {
case NumberType::UnsignedInteger:
if (VariantImpl::setInteger(number.asUnsignedInteger(), result,
resources_))
if (result.setInteger(number.asUnsignedInteger(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
case NumberType::SignedInteger:
if (VariantImpl::setInteger(number.asSignedInteger(), result,
resources_))
if (result.setInteger(number.asSignedInteger(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
case NumberType::Float:
if (VariantImpl::setFloat(number.asFloat(), result, resources_))
if (result.setFloat(number.asFloat(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
#if ARDUINOJSON_USE_DOUBLE
case NumberType::Double:
if (VariantImpl::setFloat(number.asDouble(), result, resources_))
if (result.setFloat(number.asDouble(), resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;

View File

@@ -16,23 +16,20 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
public:
static const bool producesText = true;
JsonSerializer(TWriter writer, ResourceManager* resources)
JsonSerializer(TWriter writer, const ResourceManager* resources)
: formatter_(writer), resources_(resources) {}
size_t visitArray(VariantData* array) {
ARDUINOJSON_ASSERT(array != nullptr);
ARDUINOJSON_ASSERT(array->isArray());
size_t visit(const ArrayData& array) {
write('[');
auto slotId = array->content.asCollection.head;
auto slotId = array.head();
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
VariantImpl::accept(*this, slot, resources_);
slot->accept(*this, resources_);
slotId = slot->next;
slotId = slot->next();
if (slotId != NULL_SLOT)
write(',');
@@ -42,21 +39,18 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
return bytesWritten();
}
size_t visitObject(VariantData* object) {
ARDUINOJSON_ASSERT(object != nullptr);
ARDUINOJSON_ASSERT(object->isObject());
size_t visit(const ObjectData& object) {
write('{');
auto slotId = object->content.asCollection.head;
auto slotId = object.head();
bool isKey = true;
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
VariantImpl::accept(*this, slot, resources_);
slot->accept(*this, resources_);
slotId = slot->next;
slotId = slot->next();
if (slotId != NULL_SLOT)
write(isKey ? ':' : ',');
@@ -126,7 +120,7 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
TextFormatter<TWriter> formatter_;
protected:
ResourceManager* resources_;
const ResourceManager* resources_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -16,24 +16,20 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
using base = JsonSerializer<TWriter>;
public:
PrettyJsonSerializer(TWriter writer, ResourceManager* resources)
PrettyJsonSerializer(TWriter writer, const ResourceManager* resources)
: base(writer, resources), nesting_(0) {}
size_t visitArray(VariantData* array) {
ARDUINOJSON_ASSERT(array != nullptr);
ARDUINOJSON_ASSERT(array->isArray());
auto slotId = array->content.asCollection.head;
if (slotId != NULL_SLOT) {
size_t visit(const ArrayData& array) {
auto it = array.createIterator(base::resources_);
if (!it.done()) {
base::write("[\r\n");
nesting_++;
while (slotId != NULL_SLOT) {
while (!it.done()) {
indent();
auto slot = base::resources_->getVariant(slotId);
VariantImpl::accept(*this, slot, base::resources_);
it->accept(*this, base::resources_);
slotId = slot->next;
base::write(slotId == NULL_SLOT ? "\r\n" : ",\r\n");
it.next(base::resources_);
base::write(it.done() ? "\r\n" : ",\r\n");
}
nesting_--;
indent();
@@ -44,25 +40,21 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
return this->bytesWritten();
}
size_t visitObject(VariantData* object) {
ARDUINOJSON_ASSERT(object != nullptr);
ARDUINOJSON_ASSERT(object->isObject());
auto slotId = object->content.asCollection.head;
if (slotId != NULL_SLOT) {
size_t visit(const ObjectData& object) {
auto it = object.createIterator(base::resources_);
if (!it.done()) {
base::write("{\r\n");
nesting_++;
bool isKey = true;
while (slotId != NULL_SLOT) {
while (!it.done()) {
if (isKey)
indent();
auto slot = base::resources_->getVariant(slotId);
VariantImpl::accept(*this, slot, base::resources_);
slotId = slot->next;
it->accept(*this, base::resources_);
it.next(base::resources_);
if (isKey)
base::write(": ");
else
base::write(slotId == NULL_SLOT ? "\r\n" : ",\r\n");
base::write(it.done() ? "\r\n" : ",\r\n");
isKey = !isKey;
}
nesting_--;

View File

@@ -66,16 +66,13 @@ class TextFormatter {
template <typename T>
void writeFloat(T value) {
writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 7);
writeFloat(JsonFloat(value), sizeof(T) >= 8 ? 9 : 6);
}
void writeFloat(JsonFloat value, int8_t decimalPlaces) {
if (isnan(value))
return writeRaw(ARDUINOJSON_ENABLE_NAN ? "NaN" : "null");
if (!value)
return writeRaw("0");
#if ARDUINOJSON_ENABLE_INFINITY
if (value < 0.0) {
writeRaw('-');
@@ -96,28 +93,9 @@ class TextFormatter {
auto parts = decomposeFloat(value, decimalPlaces);
// buffer should be big enough for all digits and the dot
char buffer[32];
char* end = buffer + sizeof(buffer);
char* begin = end;
// write the string in reverse order
while (parts.mantissa != 0 || parts.pointIndex > 0) {
*--begin = char(parts.mantissa % 10 + '0');
parts.mantissa /= 10;
if (parts.pointIndex == 1) {
*--begin = '.';
}
parts.pointIndex--;
}
// Avoid a leading dot
if (parts.pointIndex == 0) {
*--begin = '0';
}
// and dump it in the right order
writeRaw(begin, end);
writeInteger(parts.integral);
if (parts.decimalPlaces)
writeDecimals(parts.decimal, parts.decimalPlaces);
if (parts.exponent) {
writeRaw('e');
@@ -154,6 +132,23 @@ class TextFormatter {
writeRaw(begin, end);
}
void writeDecimals(uint32_t value, int8_t width) {
// buffer should be big enough for all digits and the dot
char buffer[16];
char* end = buffer + sizeof(buffer);
char* begin = end;
// write the string in reverse order
while (width--) {
*--begin = char(value % 10 + '0');
value /= 10;
}
*--begin = '.';
// and dump it in the right order
writeRaw(begin, end);
}
void writeRaw(const char* s) {
writer_.write(reinterpret_cast<const uint8_t*>(s), strlen(s));
}

View File

@@ -34,6 +34,11 @@ class Slot {
return ptr_;
}
T& operator*() const {
ARDUINOJSON_ASSERT(ptr_ != nullptr);
return *ptr_;
}
T* operator->() const {
ARDUINOJSON_ASSERT(ptr_ != nullptr);
return ptr_;
@@ -76,6 +81,14 @@ class MemoryPool {
return slots_ + id;
}
SlotId find(const T& value) const {
for (SlotId i = 0; i < usage_; i++) {
if (slots_[i] == value)
return i;
}
return NULL_SLOT;
}
void clear() {
usage_ = 0;
}

View File

@@ -114,6 +114,15 @@ class MemoryPoolList {
return pools_[poolIndex].getSlot(indexInPool);
}
SlotId find(const T& value) const {
for (PoolCount i = 0; i < count_; i++) {
SlotId id = pools_[i].find(value);
if (id != NULL_SLOT)
return SlotId(i * ARDUINOJSON_POOL_CAPACITY + id);
}
return NULL_SLOT;
}
void clear(Allocator* allocator) {
for (PoolCount i = 0; i < count_; i++)
pools_[i].destroy(allocator);

View File

@@ -14,19 +14,27 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class VariantData;
class VariantWithId;
class ResourceManager {
union SlotData {
VariantData variant;
#if ARDUINOJSON_USE_EXTENSIONS
VariantExtension extension;
#endif
};
public:
constexpr static size_t slotSize = sizeof(SlotData);
ResourceManager(Allocator* allocator = DefaultAllocator::instance())
: allocator_(allocator), overflowed_(false) {}
~ResourceManager() {
stringPool_.clear(allocator_);
variantPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
staticStringsPools_.clear(allocator_);
}
ResourceManager(const ResourceManager&) = delete;
@@ -35,9 +43,7 @@ class ResourceManager {
friend void swap(ResourceManager& a, ResourceManager& b) {
swap(a.stringPool_, b.stringPool_);
swap(a.variantPools_, b.variantPools_);
#if ARDUINOJSON_USE_8_BYTE_POOL
swap(a.eightBytePools_, b.eightBytePools_);
#endif
swap(a.staticStringsPools_, b.staticStringsPools_);
swap_(a.allocator_, b.allocator_);
swap_(a.overflowed_, b.overflowed_);
}
@@ -54,43 +60,14 @@ class ResourceManager {
return overflowed_;
}
Slot<VariantData> allocVariant() {
auto slot = variantPools_.allocSlot(allocator_);
if (!slot) {
overflowed_ = true;
return {};
}
new (slot.ptr()) VariantData();
return slot;
}
Slot<VariantData> allocVariant();
void freeVariant(Slot<VariantData> slot);
VariantData* getVariant(SlotId id) const;
void freeVariant(Slot<VariantData> slot) {
ARDUINOJSON_ASSERT(slot->type == VariantType::Null);
variantPools_.freeSlot(slot);
}
VariantData* getVariant(SlotId id) const {
return reinterpret_cast<VariantData*>(variantPools_.getSlot(id));
}
#if ARDUINOJSON_USE_8_BYTE_POOL
Slot<EightByteValue> allocEightByte() {
auto slot = eightBytePools_.allocSlot(allocator_);
if (!slot) {
overflowed_ = true;
return {};
}
return slot;
}
void freeEightByte(SlotId id) {
auto p = getEightByte(id);
eightBytePools_.freeSlot({p, id});
}
EightByteValue* getEightByte(SlotId id) const {
return eightBytePools_.getSlot(id);
}
#if ARDUINOJSON_USE_EXTENSIONS
Slot<VariantExtension> allocExtension();
void freeExtension(SlotId slot);
VariantExtension* getExtension(SlotId id) const;
#endif
template <typename TAdaptedString>
@@ -136,30 +113,42 @@ class ResourceManager {
stringPool_.dereference(s, allocator_);
}
SlotId saveStaticString(const char* s) {
auto existingSlotId = staticStringsPools_.find(s);
if (existingSlotId != NULL_SLOT)
return existingSlotId;
auto slot = staticStringsPools_.allocSlot(allocator_);
if (slot)
*slot = s;
else
overflowed_ = true;
return slot.id();
}
const char* getStaticString(SlotId id) const {
return *staticStringsPools_.getSlot(id);
}
void clear() {
variantPools_.clear(allocator_);
overflowed_ = false;
variantPools_.clear(allocator_);
stringPool_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
staticStringsPools_.clear(allocator_);
}
void shrinkToFit() {
variantPools_.shrinkToFit(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.shrinkToFit(allocator_);
#endif
staticStringsPools_.shrinkToFit(allocator_);
}
private:
Allocator* allocator_;
bool overflowed_;
StringPool stringPool_;
MemoryPoolList<VariantData> variantPools_;
#if ARDUINOJSON_USE_8_BYTE_POOL
MemoryPoolList<EightByteValue> eightBytePools_;
#endif
MemoryPoolList<SlotData> variantPools_;
MemoryPoolList<const char*> staticStringsPools_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -0,0 +1,52 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Collection/CollectionData.hpp>
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Polyfills/alias_cast.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline Slot<VariantData> ResourceManager::allocVariant() {
auto p = variantPools_.allocSlot(allocator_);
if (!p) {
overflowed_ = true;
return {};
}
return {new (&p->variant) VariantData, p.id()};
}
inline void ResourceManager::freeVariant(Slot<VariantData> variant) {
variant->clear(this);
variantPools_.freeSlot({alias_cast<SlotData*>(variant.ptr()), variant.id()});
}
inline VariantData* ResourceManager::getVariant(SlotId id) const {
return reinterpret_cast<VariantData*>(variantPools_.getSlot(id));
}
#if ARDUINOJSON_USE_EXTENSIONS
inline Slot<VariantExtension> ResourceManager::allocExtension() {
auto p = variantPools_.allocSlot(allocator_);
if (!p) {
overflowed_ = true;
return {};
}
return {&p->extension, p.id()};
}
inline void ResourceManager::freeExtension(SlotId id) {
auto p = getExtension(id);
variantPools_.freeSlot({reinterpret_cast<SlotData*>(p), id});
}
inline VariantExtension* ResourceManager::getExtension(SlotId id) const {
return &variantPools_.getSlot(id)->extension;
}
#endif
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -5,7 +5,6 @@
#pragma once
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Strings/JsonString.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
@@ -33,26 +32,7 @@ class StringBuffer {
return node_->data;
}
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->setLongString(commitStringNode());
}
void saveRaw(VariantData* data) {
data->setRawString(commitStringNode());
}
private:
StringNode* commitStringNode() {
StringNode* save() {
ARDUINOJSON_ASSERT(node_ != nullptr);
node_->data[size_] = 0;
auto node = resources_->getString(adaptString(node_->data, size_));
@@ -72,6 +52,13 @@ class StringBuffer {
return node;
}
JsonString str() const {
ARDUINOJSON_ASSERT(node_ != nullptr);
return JsonString(node_->data, node_->length);
}
private:
ResourceManager* resources_;
StringNode* node_ = nullptr;
size_t size_ = 0;

View File

@@ -5,7 +5,6 @@
#pragma once
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Strings/JsonString.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
@@ -26,18 +25,10 @@ class StringBuilder {
node_ = resources_->createString(initialCapacity);
}
void save(VariantData* variant) {
ARDUINOJSON_ASSERT(variant != nullptr);
StringNode* save() {
ARDUINOJSON_ASSERT(node_ != nullptr);
char* p = node_->data;
if (isTinyString(p, size_)) {
variant->setTinyString(adaptString(p, size_));
return;
}
p[size_] = 0;
StringNode* node = resources_->getString(adaptString(p, size_));
node_->data[size_] = 0;
StringNode* node = resources_->getString(adaptString(node_->data, size_));
if (!node) {
node = resources_->resizeString(node_, size_);
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
@@ -46,7 +37,7 @@ class StringBuilder {
} else {
node->references++;
}
variant->setLongString(node);
return node;
}
void append(const char* s) {

View File

@@ -24,48 +24,45 @@ class MsgPackBinary {
template <>
struct Converter<MsgPackBinary> : private detail::VariantAttorney {
static bool toJson(MsgPackBinary src, JsonVariant dst) {
static void toJson(MsgPackBinary src, JsonVariant dst) {
auto data = VariantAttorney::getData(dst);
if (!data)
return false;
return;
auto resources = getResourceManager(dst);
detail::VariantImpl::clear(data, resources);
if (!src.data())
return true;
size_t headerSize = src.size() >= 0x10000 ? 5 : src.size() >= 0x100 ? 3 : 2;
auto str = resources->createString(src.size() + headerSize);
if (!str)
return false;
resources->saveString(str);
auto ptr = reinterpret_cast<uint8_t*>(str->data);
switch (headerSize) {
case 2:
ptr[0] = uint8_t(0xc4);
ptr[1] = uint8_t(src.size() & 0xff);
break;
case 3:
ptr[0] = uint8_t(0xc5);
ptr[1] = uint8_t(src.size() >> 8 & 0xff);
ptr[2] = uint8_t(src.size() & 0xff);
break;
case 5:
ptr[0] = uint8_t(0xc6);
ptr[1] = uint8_t(src.size() >> 24 & 0xff);
ptr[2] = uint8_t(src.size() >> 16 & 0xff);
ptr[3] = uint8_t(src.size() >> 8 & 0xff);
ptr[4] = uint8_t(src.size() & 0xff);
break;
default:
ARDUINOJSON_ASSERT(false);
data->clear(resources);
if (src.data()) {
size_t headerSize = src.size() >= 0x10000 ? 5
: src.size() >= 0x100 ? 3
: 2;
auto str = resources->createString(src.size() + headerSize);
if (str) {
resources->saveString(str);
auto ptr = reinterpret_cast<uint8_t*>(str->data);
switch (headerSize) {
case 2:
ptr[0] = uint8_t(0xc4);
ptr[1] = uint8_t(src.size() & 0xff);
break;
case 3:
ptr[0] = uint8_t(0xc5);
ptr[1] = uint8_t(src.size() >> 8 & 0xff);
ptr[2] = uint8_t(src.size() & 0xff);
break;
case 5:
ptr[0] = uint8_t(0xc6);
ptr[1] = uint8_t(src.size() >> 24 & 0xff);
ptr[2] = uint8_t(src.size() >> 16 & 0xff);
ptr[3] = uint8_t(src.size() >> 8 & 0xff);
ptr[4] = uint8_t(src.size() & 0xff);
break;
default:
ARDUINOJSON_ASSERT(false);
}
memcpy(ptr + headerSize, src.data(), src.size());
data->setRawString(str);
return;
}
}
memcpy(ptr + headerSize, src.data(), src.size());
data->setRawString(str);
return true;
}
static MsgPackBinary fromJson(JsonVariantConst src) {

View File

@@ -24,10 +24,10 @@ class MsgPackDeserializer {
foundSomething_(false) {}
template <typename TFilter>
DeserializationError parse(VariantData* variant, TFilter filter,
DeserializationError parse(VariantData& variant, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
err = parseVariant(variant, filter, nestingLimit);
err = parseVariant(&variant, filter, nestingLimit);
return foundSomething_ ? err : DeserializationError::EmptyInput;
}
@@ -91,7 +91,7 @@ class MsgPackDeserializer {
if (code <= 0x7f || code >= 0xe0) { // fixint
if (allowValue)
VariantImpl::setInteger(static_cast<int8_t>(code), variant, resources_);
variant->setInteger(static_cast<int8_t>(code), resources_);
return DeserializationError::Ok;
}
@@ -231,14 +231,14 @@ class MsgPackDeserializer {
if (isSigned) {
auto truncatedValue = static_cast<JsonInteger>(signedValue);
if (truncatedValue == signedValue) {
if (!VariantImpl::setInteger(truncatedValue, variant, resources_))
if (!variant->setInteger(truncatedValue, resources_))
return DeserializationError::NoMemory;
}
// else set null on overflow
} else {
auto truncatedValue = static_cast<JsonUInt>(unsignedValue);
if (truncatedValue == unsignedValue)
if (!VariantImpl::setInteger(truncatedValue, variant, resources_))
if (!variant->setInteger(truncatedValue, resources_))
return DeserializationError::NoMemory;
// else set null on overflow
}
@@ -257,7 +257,7 @@ class MsgPackDeserializer {
return err;
fixEndianness(value);
VariantImpl::setFloat(value, variant, resources_);
variant->setFloat(value, resources_);
return DeserializationError::Ok;
}
@@ -273,7 +273,7 @@ class MsgPackDeserializer {
return err;
fixEndianness(value);
if (VariantImpl::setFloat(value, variant, resources_))
if (variant->setFloat(value, resources_))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
@@ -293,7 +293,7 @@ class MsgPackDeserializer {
doubleToFloat(i, o);
fixEndianness(value);
VariantImpl::setFloat(value, variant, resources_);
variant->setFloat(value, resources_);
return DeserializationError::Ok;
}
@@ -305,7 +305,7 @@ class MsgPackDeserializer {
if (err)
return err;
stringBuffer_.save(variant);
variant->setOwnedString(stringBuffer_.save());
return DeserializationError::Ok;
}
@@ -334,7 +334,7 @@ class MsgPackDeserializer {
if (err)
return err;
stringBuffer_.saveRaw(variant);
variant->setRawString(stringBuffer_.save());
return DeserializationError::Ok;
}
@@ -349,9 +349,12 @@ class MsgPackDeserializer {
bool allowArray = filter.allowArray();
ArrayData* array;
if (allowArray) {
ARDUINOJSON_ASSERT(variant != 0);
variant->toArray();
array = &variant->toArray();
} else {
array = 0;
}
TFilter elementFilter = filter[0U];
@@ -360,7 +363,8 @@ class MsgPackDeserializer {
VariantData* value;
if (elementFilter.allow()) {
value = VariantImpl::addNewElement(variant, resources_);
ARDUINOJSON_ASSERT(array != 0);
value = array->addElement(resources_);
if (!value)
return DeserializationError::NoMemory;
} else {
@@ -384,9 +388,12 @@ class MsgPackDeserializer {
if (nestingLimit.reached())
return DeserializationError::TooDeep;
ObjectData* object;
if (filter.allowObject()) {
ARDUINOJSON_ASSERT(variant != 0);
variant->toObject();
object = &variant->toObject();
} else {
object = 0;
}
for (; n; --n) {
@@ -396,14 +403,19 @@ class MsgPackDeserializer {
JsonString key = stringBuffer_.str();
TFilter memberFilter = filter[key.c_str()];
VariantData* member = 0;
VariantData* member;
if (memberFilter.allow()) {
auto keyVariant = VariantImpl::addPair(&member, variant, resources_);
if (!keyVariant)
return DeserializationError::NoMemory;
ARDUINOJSON_ASSERT(object != 0);
stringBuffer_.save(keyVariant);
// Save key in memory pool.
auto savedKey = stringBuffer_.save();
member = object->addMember(savedKey, resources_);
if (!member)
return DeserializationError::NoMemory;
} else {
member = 0;
}
err = parseVariant(member, memberFilter, nestingLimit.decrement());

View File

@@ -30,57 +30,53 @@ class MsgPackExtension {
template <>
struct Converter<MsgPackExtension> : private detail::VariantAttorney {
static bool toJson(MsgPackExtension src, JsonVariant dst) {
auto data = getData(dst);
static void toJson(MsgPackExtension src, JsonVariant dst) {
auto data = VariantAttorney::getData(dst);
if (!data)
return false;
return;
auto resources = getResourceManager(dst);
detail::VariantImpl::clear(data, resources);
data->clear(resources);
if (src.data()) {
uint8_t format, sizeBytes;
if (src.size() >= 0x10000) {
format = 0xc9; // ext 32
sizeBytes = 4;
} else if (src.size() >= 0x100) {
format = 0xc8; // ext 16
sizeBytes = 2;
} else if (src.size() == 16) {
format = 0xd8; // fixext 16
sizeBytes = 0;
} else if (src.size() == 8) {
format = 0xd7; // fixext 8
sizeBytes = 0;
} else if (src.size() == 4) {
format = 0xd6; // fixext 4
sizeBytes = 0;
} else if (src.size() == 2) {
format = 0xd5; // fixext 2
sizeBytes = 0;
} else if (src.size() == 1) {
format = 0xd4; // fixext 1
sizeBytes = 0;
} else {
format = 0xc7; // ext 8
sizeBytes = 1;
}
if (!src.data())
return true;
uint8_t format, sizeBytes;
if (src.size() >= 0x10000) {
format = 0xc9; // ext 32
sizeBytes = 4;
} else if (src.size() >= 0x100) {
format = 0xc8; // ext 16
sizeBytes = 2;
} else if (src.size() == 16) {
format = 0xd8; // fixext 16
sizeBytes = 0;
} else if (src.size() == 8) {
format = 0xd7; // fixext 8
sizeBytes = 0;
} else if (src.size() == 4) {
format = 0xd6; // fixext 4
sizeBytes = 0;
} else if (src.size() == 2) {
format = 0xd5; // fixext 2
sizeBytes = 0;
} else if (src.size() == 1) {
format = 0xd4; // fixext 1
sizeBytes = 0;
} else {
format = 0xc7; // ext 8
sizeBytes = 1;
auto str = resources->createString(src.size() + 2 + sizeBytes);
if (str) {
resources->saveString(str);
auto ptr = reinterpret_cast<uint8_t*>(str->data);
*ptr++ = uint8_t(format);
for (uint8_t i = 0; i < sizeBytes; i++)
*ptr++ = uint8_t(src.size() >> (sizeBytes - i - 1) * 8 & 0xff);
*ptr++ = uint8_t(src.type());
memcpy(ptr, src.data(), src.size());
data->setRawString(str);
return;
}
}
auto str = resources->createString(src.size() + 2 + sizeBytes);
if (!str)
return false;
resources->saveString(str);
auto ptr = reinterpret_cast<uint8_t*>(str->data);
*ptr++ = uint8_t(format);
for (uint8_t i = 0; i < sizeBytes; i++)
*ptr++ = uint8_t(src.size() >> (sizeBytes - i - 1) * 8 & 0xff);
*ptr++ = uint8_t(src.type());
memcpy(ptr, src.data(), src.size());
data->setRawString(str);
return true;
}
static MsgPackExtension fromJson(JsonVariantConst src) {

View File

@@ -19,7 +19,7 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
public:
static const bool producesText = false;
MsgPackSerializer(TWriter writer, ResourceManager* resources)
MsgPackSerializer(TWriter writer, const ResourceManager* resources)
: writer_(writer), resources_(resources) {}
template <typename T>
@@ -47,11 +47,8 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
return bytesWritten();
}
size_t visitArray(VariantData* array) {
ARDUINOJSON_ASSERT(array != nullptr);
ARDUINOJSON_ASSERT(array->isArray());
auto n = VariantImpl::size(array, resources_);
size_t visit(const ArrayData& array) {
size_t n = array.size(resources_);
if (n < 0x10) {
writeByte(uint8_t(0x90 + n));
} else if (n < 0x10000) {
@@ -62,21 +59,18 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
writeInteger(uint32_t(n));
}
auto slotId = array->content.asCollection.head;
auto slotId = array.head();
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
VariantImpl::accept(*this, slot, resources_);
slotId = slot->next;
slot->accept(*this, resources_);
slotId = slot->next();
}
return bytesWritten();
}
size_t visitObject(VariantData* object) {
ARDUINOJSON_ASSERT(object != nullptr);
ARDUINOJSON_ASSERT(object->isObject());
auto n = VariantImpl::size(object, resources_);
size_t visit(const ObjectData& object) {
size_t n = object.size(resources_);
if (n < 0x10) {
writeByte(uint8_t(0x80 + n));
} else if (n < 0x10000) {
@@ -87,11 +81,11 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
writeInteger(uint32_t(n));
}
auto slotId = object->content.asCollection.head;
auto slotId = object.head();
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
VariantImpl::accept(*this, slot, resources_);
slotId = slot->next;
slot->accept(*this, resources_);
slotId = slot->next();
}
return bytesWritten();
@@ -215,7 +209,7 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
}
CountingDecorator<TWriter> writer_;
ResourceManager* resources_;
const ResourceManager* resources_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -7,86 +7,89 @@
#include <ArduinoJson/Configuration.hpp>
#include <ArduinoJson/Numbers/FloatTraits.hpp>
#include <ArduinoJson/Numbers/JsonFloat.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Polyfills/math.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
struct FloatParts {
uint32_t mantissa;
uint32_t integral;
uint32_t decimal;
int16_t exponent;
int8_t pointIndex;
int8_t decimalPlaces;
};
template <typename TFloat>
inline int16_t normalize(TFloat& value) {
using traits = FloatTraits<TFloat>;
int16_t powersOf10 = 0;
int8_t index = sizeof(TFloat) == 8 ? 8 : 5;
int bit = 1 << index;
if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
for (; index >= 0; index--) {
if (value >= traits::positiveBinaryPowersOfTen()[index]) {
value *= traits::negativeBinaryPowersOfTen()[index];
powersOf10 = int16_t(powersOf10 + bit);
}
bit >>= 1;
}
}
if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
for (; index >= 0; index--) {
if (value < traits::negativeBinaryPowersOfTen()[index] * 10) {
value *= traits::positiveBinaryPowersOfTen()[index];
powersOf10 = int16_t(powersOf10 - bit);
}
bit >>= 1;
}
}
return powersOf10;
}
constexpr uint32_t pow10(int exponent) {
return (exponent == 0) ? 1 : 10 * pow10(exponent - 1);
}
inline FloatParts decomposeFloat(JsonFloat value, int8_t significantDigits) {
ARDUINOJSON_ASSERT(value > 0);
ARDUINOJSON_ASSERT(significantDigits > 1);
ARDUINOJSON_ASSERT(significantDigits <= 9); // to prevent uint32_t overflow
inline FloatParts decomposeFloat(JsonFloat value, int8_t decimalPlaces) {
uint32_t maxDecimalPart = pow10(decimalPlaces);
using traits = FloatTraits<JsonFloat>;
int16_t exponent = normalize(value);
bool useScientificNotation =
value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD ||
value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD;
int16_t exponent = 0;
int8_t index = traits::binaryPowersOfTenArraySize - 1;
int bit = 1 << index;
// Normalize value to range [1..10) and compute exponent
if (value > 1) {
for (; index >= 0; index--) {
if (value >= traits::positiveBinaryPowersOfTen()[index]) {
value *= traits::negativeBinaryPowersOfTen()[index];
exponent = int16_t(exponent + bit);
}
bit >>= 1;
}
uint32_t integral = uint32_t(value);
// reduce number of decimal places by the number of integral places
for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) {
maxDecimalPart /= 10;
decimalPlaces--;
}
ARDUINOJSON_ASSERT(value < 10);
if (value < 1) {
for (; index >= 0; index--) {
if (value < traits::negativeBinaryPowersOfTen()[index] * 10) {
value *= traits::positiveBinaryPowersOfTen()[index];
exponent = int16_t(exponent - bit);
}
bit >>= 1;
JsonFloat remainder =
(value - JsonFloat(integral)) * JsonFloat(maxDecimalPart);
uint32_t decimal = uint32_t(remainder);
remainder = remainder - JsonFloat(decimal);
// rounding:
// increment by 1 if remainder >= 0.5
decimal += uint32_t(remainder * 2);
if (decimal >= maxDecimalPart) {
decimal = 0;
integral++;
if (exponent && integral >= 10) {
exponent++;
integral = 1;
}
}
ARDUINOJSON_ASSERT(value >= 1);
// ARDUINOJSON_ASSERT(value < 10);
value *= JsonFloat(pow10(significantDigits - 1));
auto mantissa = uint32_t(value);
ARDUINOJSON_ASSERT(mantissa > 0);
// rounding
auto remainder = value - JsonFloat(mantissa);
if (remainder >= 0.5)
mantissa++;
auto pointIndex = int8_t(significantDigits - 1);
if (!useScientificNotation) {
pointIndex = int8_t(pointIndex - int8_t(exponent));
exponent = 0;
}
// remove trailing zeros
while (mantissa % 10 == 0 && (useScientificNotation || pointIndex > 0)) {
mantissa /= 10;
if (pointIndex > 0)
pointIndex--;
else
exponent++;
while (decimal % 10 == 0 && decimalPlaces > 0) {
decimal /= 10;
decimalPlaces--;
}
return {mantissa, exponent, pointIndex};
return {integral, decimal, exponent, decimalPlaces};
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -29,8 +29,6 @@ struct FloatTraits<T, 8 /*64bits*/> {
using exponent_type = int16_t;
static const exponent_type exponent_max = 308;
static const int8_t binaryPowersOfTenArraySize = 9;
static pgm_ptr<T> positiveBinaryPowersOfTen() {
ARDUINOJSON_DEFINE_PROGMEM_ARRAY( //
uint64_t, factors,
@@ -115,8 +113,6 @@ struct FloatTraits<T, 4 /*32bits*/> {
using exponent_type = int8_t;
static const exponent_type exponent_max = 38;
static const int8_t binaryPowersOfTenArraySize = 6;
static pgm_ptr<T> positiveBinaryPowersOfTen() {
ARDUINOJSON_DEFINE_PROGMEM_ARRAY(uint32_t, factors,
{

View File

@@ -20,55 +20,56 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
using iterator = JsonObjectIterator;
// Creates an unbound reference.
JsonObject() {}
JsonObject() : data_(0), resources_(0) {}
// INTERNAL USE ONLY
JsonObject(const detail::VariantImpl& impl) : impl_(impl) {}
// INTERNAL USE ONLY
JsonObject(detail::VariantData* data, detail::ResourceManager* resource)
: impl_(data, resource) {}
JsonObject(detail::ObjectData* data, detail::ResourceManager* resource)
: data_(data), resources_(resource) {}
operator JsonVariant() const {
return JsonVariant(getData(), getResourceManager());
void* data = data_; // prevent warning cast-align
return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
resources_);
}
operator JsonObjectConst() const {
return JsonObjectConst(getData(), getResourceManager());
return JsonObjectConst(data_, resources_);
}
operator JsonVariantConst() const {
return JsonVariantConst(getData(), getResourceManager());
return JsonVariantConst(collectionToVariant(data_), resources_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonobject/isnull/
bool isNull() const {
return !impl_.isObject();
return data_ == 0;
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonobject/isnull/
operator bool() const {
return impl_.isObject();
return data_ != 0;
}
// Returns the depth (nesting level) of the object.
// https://arduinojson.org/v7/api/jsonobject/nesting/
size_t nesting() const {
return impl_.nesting();
return detail::VariantData::nesting(collectionToVariant(data_), resources_);
}
// Returns the number of members in the object.
// https://arduinojson.org/v7/api/jsonobject/size/
size_t size() const {
return impl_.size();
return data_ ? data_->size(resources_) : 0;
}
// Returns an iterator to the first key-value pair of the object.
// https://arduinojson.org/v7/api/jsonobject/begin/
iterator begin() const {
return iterator(impl_.createIterator(), impl_.resources());
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
}
// Returns an iterator following the last key-value pair of the object.
@@ -80,13 +81,13 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// Removes all the members of the object.
// https://arduinojson.org/v7/api/jsonobject/clear/
void clear() const {
impl_.empty();
detail::ObjectData::clear(data_, resources_);
}
// Copies an object.
// https://arduinojson.org/v7/api/jsonobject/set/
bool set(JsonObjectConst src) {
if (isNull() || src.isNull())
if (!data_ || !src.data_)
return false;
clear();
@@ -110,7 +111,9 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// Gets or sets the member with specified key.
// https://arduinojson.org/v7/api/jsonobject/subscript/
template <typename TChar,
detail::enable_if_t<detail::IsString<TChar*>::value, int> = 0>
detail::enable_if_t<detail::IsString<TChar*>::value &&
!detail::is_const<TChar>::value,
int> = 0>
detail::MemberProxy<JsonObject, detail::AdaptedString<TChar*>> operator[](
TChar* key) const {
return {*this, detail::adaptString(key)};
@@ -128,7 +131,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// Removes the member at the specified iterator.
// https://arduinojson.org/v7/api/jsonobject/remove/
FORCE_INLINE void remove(iterator it) const {
impl_.removeMember(it.iterator_);
detail::ObjectData::remove(data_, it.iterator_, resources_);
}
// Removes the member with the specified key.
@@ -136,7 +139,8 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
template <typename TString,
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
void remove(const TString& key) const {
impl_.removeMember(detail::adaptString(key));
detail::ObjectData::removeMember(data_, detail::adaptString(key),
resources_);
}
// Removes the member with the specified key.
@@ -152,7 +156,8 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// https://arduinojson.org/v7/api/jsonobject/remove/
template <typename TChar>
FORCE_INLINE void remove(TChar* key) const {
impl_.removeMember(detail::adaptString(key));
detail::ObjectData::removeMember(data_, detail::adaptString(key),
resources_);
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -161,16 +166,20 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
ARDUINOJSON_DEPRECATED("use obj[key].is<T>() instead")
bool containsKey(const TString& key) const {
return impl_.getMember(detail::adaptString(key)) != 0;
return detail::ObjectData::getMember(data_, detail::adaptString(key),
resources_) != 0;
}
// DEPRECATED: use obj["key"].is<T>() instead
// https://arduinojson.org/v7/api/jsonobject/containskey/
template <typename TChar,
detail::enable_if_t<detail::IsString<TChar*>::value, int> = 0>
detail::enable_if_t<detail::IsString<TChar*>::value &&
!detail::is_const<TChar>::value,
int> = 0>
ARDUINOJSON_DEPRECATED("use obj[\"key\"].is<T>() instead")
bool containsKey(TChar* key) const {
return impl_.getMember(detail::adaptString(key)) != 0;
return detail::ObjectData::getMember(data_, detail::adaptString(key),
resources_) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -218,18 +227,19 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
private:
detail::ResourceManager* getResourceManager() const {
return impl_.resources();
return resources_;
}
detail::VariantData* getData() const {
return impl_.data();
return detail::collectionToVariant(data_);
}
detail::VariantData* getOrCreateData() const {
return impl_.data();
return detail::collectionToVariant(data_);
}
mutable detail::VariantImpl impl_;
detail::ObjectData* data_;
detail::ResourceManager* resources_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -19,47 +19,47 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
using iterator = JsonObjectConstIterator;
// Creates an unbound reference.
JsonObjectConst() {}
JsonObjectConst() : data_(0), resources_(0) {}
// INTERNAL USE ONLY
JsonObjectConst(detail::VariantData* data, detail::ResourceManager* resources)
: impl_(data, resources) {}
// INTERNAL USE ONLY
JsonObjectConst(const detail::VariantImpl& impl) : impl_(impl) {}
JsonObjectConst(const detail::ObjectData* data,
const detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
operator JsonVariantConst() const {
return JsonVariantConst(impl_.data(), impl_.resources());
return JsonVariantConst(getData(), resources_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonobjectconst/isnull/
bool isNull() const {
return impl_.isNull();
return data_ == 0;
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonobjectconst/isnull/
operator bool() const {
return !isNull();
return data_ != 0;
}
// Returns the depth (nesting level) of the object.
// https://arduinojson.org/v7/api/jsonobjectconst/nesting/
size_t nesting() const {
return impl_.nesting();
return detail::VariantData::nesting(getData(), resources_);
}
// Returns the number of members in the object.
// https://arduinojson.org/v7/api/jsonobjectconst/size/
size_t size() const {
return impl_.size();
return data_ ? data_->size(resources_) : 0;
}
// Returns an iterator to the first key-value pair of the object.
// https://arduinojson.org/v7/api/jsonobjectconst/begin/
iterator begin() const {
return iterator(impl_.createIterator(), impl_.resources());
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
}
// Returns an iterator following the last key-value pair of the object.
@@ -74,7 +74,8 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
ARDUINOJSON_DEPRECATED("use obj[key].is<T>() instead")
bool containsKey(const TString& key) const {
return impl_.getMember(detail::adaptString(key)) != 0;
return detail::ObjectData::getMember(data_, detail::adaptString(key),
resources_) != 0;
}
// DEPRECATED: use obj["key"].is<T>() instead
@@ -82,7 +83,8 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
template <typename TChar>
ARDUINOJSON_DEPRECATED("use obj[\"key\"].is<T>() instead")
bool containsKey(TChar* key) const {
return impl_.getMember(detail::adaptString(key)) != 0;
return detail::ObjectData::getMember(data_, detail::adaptString(key),
resources_) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -99,17 +101,21 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
template <typename TString,
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
JsonVariantConst operator[](const TString& key) const {
return JsonVariantConst(impl_.getMember(detail::adaptString(key)),
impl_.resources());
return JsonVariantConst(detail::ObjectData::getMember(
data_, detail::adaptString(key), resources_),
resources_);
}
// Gets the member with specified key.
// https://arduinojson.org/v7/api/jsonobjectconst/subscript/
template <typename TChar,
detail::enable_if_t<detail::IsString<TChar*>::value, int> = 0>
detail::enable_if_t<detail::IsString<TChar*>::value &&
!detail::is_const<TChar>::value,
int> = 0>
JsonVariantConst operator[](TChar* key) const {
return JsonVariantConst(impl_.getMember(detail::adaptString(key)),
impl_.resources());
return JsonVariantConst(detail::ObjectData::getMember(
data_, detail::adaptString(key), resources_),
resources_);
}
// Gets the member with specified key.
@@ -131,10 +137,11 @@ class JsonObjectConst : public detail::VariantOperators<JsonObjectConst> {
private:
const detail::VariantData* getData() const {
return impl_.data();
return collectionToVariant(data_);
}
detail::VariantImpl impl_;
const detail::ObjectData* data_;
const detail::ResourceManager* resources_;
};
inline bool operator==(JsonObjectConst lhs, JsonObjectConst rhs) {

View File

@@ -14,7 +14,7 @@ class JsonObjectIterator {
public:
JsonObjectIterator() {}
explicit JsonObjectIterator(detail::VariantImpl::iterator iterator,
explicit JsonObjectIterator(detail::ObjectData::iterator iterator,
detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
@@ -34,13 +34,13 @@ class JsonObjectIterator {
}
JsonObjectIterator& operator++() {
iterator_.move(resources_); // key
iterator_.move(resources_); // value
iterator_.next(resources_); // key
iterator_.next(resources_); // value
return *this;
}
private:
detail::VariantImpl::iterator iterator_;
detail::ObjectData::iterator iterator_;
detail::ResourceManager* resources_;
};
@@ -50,8 +50,8 @@ class JsonObjectConstIterator {
public:
JsonObjectConstIterator() {}
explicit JsonObjectConstIterator(detail::VariantImpl::iterator iterator,
detail::ResourceManager* resources)
explicit JsonObjectConstIterator(detail::ObjectData::iterator iterator,
const detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
JsonPairConst operator*() const {
@@ -70,14 +70,14 @@ class JsonObjectConstIterator {
}
JsonObjectConstIterator& operator++() {
iterator_.move(resources_); // key
iterator_.move(resources_); // value
iterator_.next(resources_); // key
iterator_.next(resources_); // value
return *this;
}
private:
detail::VariantImpl::iterator iterator_;
detail::ResourceManager* resources_;
detail::ObjectData::iterator iterator_;
const detail::ResourceManager* resources_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -15,11 +15,11 @@ ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
class JsonPair {
public:
// INTERNAL USE ONLY
JsonPair(detail::VariantImpl::iterator iterator,
JsonPair(detail::ObjectData::iterator iterator,
detail::ResourceManager* resources) {
if (!iterator.done()) {
key_ = iterator->asString();
iterator.move(resources);
key_ = iterator->asString(resources);
iterator.next(resources);
value_ = JsonVariant(iterator.data(), resources);
}
}
@@ -43,11 +43,11 @@ class JsonPair {
// https://arduinojson.org/v7/api/jsonobjectconst/begin_end/
class JsonPairConst {
public:
JsonPairConst(detail::VariantImpl::iterator iterator,
detail::ResourceManager* resources) {
JsonPairConst(detail::ObjectData::iterator iterator,
const detail::ResourceManager* resources) {
if (!iterator.done()) {
key_ = iterator->asString();
iterator.move(resources);
key_ = iterator->asString(resources);
iterator.next(resources);
value_ = JsonVariantConst(iterator.data(), resources);
}
}

View File

@@ -39,7 +39,7 @@ class MemberProxy
return *this;
}
template <typename T>
template <typename T, enable_if_t<!is_const<T>::value, int> = 0>
MemberProxy& operator=(T* src) {
this->set(src);
return *this;
@@ -56,15 +56,17 @@ class MemberProxy
}
VariantData* getData() const {
return VariantAttorney::getVariantImpl(upstream_).getMember(key_);
return VariantData::getMember(
VariantAttorney::getData(upstream_), key_,
VariantAttorney::getResourceManager(upstream_));
}
VariantData* getOrCreateData() const {
auto data = VariantAttorney::getOrCreateData(upstream_);
auto resources = VariantAttorney::getResourceManager(upstream_);
if (data && data->type == VariantType::Null)
data->toObject();
return VariantImpl(data, resources).getOrAddMember(key_);
if (!data)
return nullptr;
return data->getOrAddMember(key_,
VariantAttorney::getResourceManager(upstream_));
}
private:

View File

@@ -0,0 +1,68 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Collection/CollectionData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class ObjectData : public CollectionData {
public:
template <typename TAdaptedString> // also works with StringNode*
VariantData* addMember(TAdaptedString key, ResourceManager* resources);
template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources);
template <typename TAdaptedString>
VariantData* getMember(TAdaptedString key,
const ResourceManager* resources) const;
template <typename TAdaptedString>
static VariantData* getMember(const ObjectData* object, TAdaptedString key,
const ResourceManager* resources) {
if (!object)
return nullptr;
return object->getMember(key, resources);
}
template <typename TAdaptedString>
void removeMember(TAdaptedString key, ResourceManager* resources);
template <typename TAdaptedString>
static void removeMember(ObjectData* obj, TAdaptedString key,
ResourceManager* resources) {
if (!obj)
return;
obj->removeMember(key, resources);
}
void remove(iterator it, ResourceManager* resources) {
CollectionData::removePair(it, resources);
}
static void remove(ObjectData* obj, ObjectData::iterator it,
ResourceManager* resources) {
if (!obj)
return;
obj->remove(it, resources);
}
size_t size(const ResourceManager* resources) const {
return CollectionData::size(resources) / 2;
}
static size_t size(const ObjectData* obj, const ResourceManager* resources) {
if (!obj)
return 0;
return obj->size(resources);
}
private:
template <typename TAdaptedString>
iterator findKey(TAdaptedString key, const ResourceManager* resources) const;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -4,49 +4,39 @@
#pragma once
#include <ArduinoJson/Object/ObjectData.hpp>
#include <ArduinoJson/Variant/VariantCompare.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
template <typename TAdaptedString>
inline VariantData* VariantImpl::getMember(TAdaptedString key,
VariantData* data,
ResourceManager* resources) {
auto it = findKey(key, data, resources);
inline VariantData* ObjectData::getMember(
TAdaptedString key, const ResourceManager* resources) const {
auto it = findKey(key, resources);
if (it.done())
return nullptr;
it.move(resources);
it.next(resources);
return it.data();
}
template <typename TAdaptedString>
VariantData* VariantImpl::getOrAddMember(TAdaptedString key, VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isObject());
ARDUINOJSON_ASSERT(resources != nullptr);
auto member = getMember(key, data, resources);
if (member)
return member;
return addMember(key, data, resources);
VariantData* ObjectData::getOrAddMember(TAdaptedString key,
ResourceManager* resources) {
auto data = getMember(key, resources);
if (data)
return data;
return addMember(key, resources);
}
template <typename TAdaptedString>
inline VariantImpl::iterator VariantImpl::findKey(TAdaptedString key,
VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isObject());
ARDUINOJSON_ASSERT(resources != nullptr);
inline ObjectData::iterator ObjectData::findKey(
TAdaptedString key, const ResourceManager* resources) const {
if (key.isNull())
return iterator();
bool isKey = true;
for (auto it = createIterator(data, resources); !it.done();
it.move(resources)) {
if (isKey && stringEquals(key, adaptString(it->asString())))
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
if (isKey && stringEquals(key, adaptString(it->asString(resources))))
return it;
isKey = !isKey;
}
@@ -54,16 +44,14 @@ inline VariantImpl::iterator VariantImpl::findKey(TAdaptedString key,
}
template <typename TAdaptedString>
inline VariantData* VariantImpl::addMember(TAdaptedString key,
VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isObject());
ARDUINOJSON_ASSERT(resources != nullptr);
if (key.isNull())
return nullptr; // Ignore null key
inline void ObjectData::removeMember(TAdaptedString key,
ResourceManager* resources) {
remove(findKey(key, resources), resources);
}
template <typename TAdaptedString>
inline VariantData* ObjectData::addMember(TAdaptedString key,
ResourceManager* resources) {
auto keySlot = resources->allocVariant();
if (!keySlot)
return nullptr;
@@ -72,38 +60,17 @@ inline VariantData* VariantImpl::addMember(TAdaptedString key,
if (!valueSlot)
return nullptr;
if (!VariantImpl::setString(key, keySlot.ptr(), resources))
if (!keySlot->setString(key, resources))
return nullptr;
appendPair(keySlot, valueSlot, data, resources);
CollectionData::appendPair(keySlot, valueSlot, resources);
return valueSlot.ptr();
}
inline VariantData* VariantImpl::addPair(VariantData** value, VariantData* data,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(value != nullptr);
ARDUINOJSON_ASSERT(data != nullptr);
ARDUINOJSON_ASSERT(data->isObject());
ARDUINOJSON_ASSERT(resources != nullptr);
auto keySlot = resources->allocVariant();
if (!keySlot)
return nullptr;
auto valueSlot = resources->allocVariant();
if (!valueSlot)
return nullptr;
*value = valueSlot.ptr();
appendPair(keySlot, valueSlot, data, resources);
return keySlot.ptr();
}
// Returns the size (in bytes) of an object with n members.
constexpr size_t sizeofObject(size_t n) {
return 2 * n * sizeof(VariantData);
return 2 * n * ResourceManager::slotSize;
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -14,7 +14,7 @@ size_t measure(ArduinoJson::JsonVariantConst source) {
auto data = VariantAttorney::getData(source);
auto resources = VariantAttorney::getResourceManager(source);
TSerializer<DummyWriter> serializer(dp, resources);
return VariantImpl::accept(serializer, data, resources);
return VariantData::accept(data, resources, serializer);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

Some files were not shown because too many files have changed in this diff Show More