Compare commits

...

51 Commits

Author SHA1 Message Date
Benoit Blanchon
589bbbfcca VariantImpl: add toArrayIfNull() and toObjectIfNull() 2025-07-24 18:40:38 +02:00
Benoit Blanchon
03da4aad99 MsgPackSerializer: use iterators to serialize array and objects 2025-07-23 16:31:19 +02:00
Benoit Blanchon
60a767c706 JsonSerializer: use iterators to serialize array and objects 2025-07-23 16:14:03 +02:00
Benoit Blanchon
274fe06b33 VariantImpl: add copy functions 2025-07-20 18:23:03 +02:00
Benoit Blanchon
f680598a2f VariantImpl: remove addValue()
This removes the two-way dependency between `JsonVariant` and `VariantImpl`
2025-07-20 14:56:06 +02:00
Benoit Blanchon
e86c6acf2e Return bool from all built-in toJson converters 2025-07-17 22:11:36 +02:00
Benoit Blanchon
0021ec81d1 StringPool: change dereference to take a StringNode* 2025-07-16 12:32:35 +02:00
Benoit Blanchon
372f2f2767 VariantImpl: extract asOwnedString() 2025-07-16 12:26:28 +02:00
Benoit Blanchon
fc8da90ba7 VariantImpl: extract setOwnedString() 2025-07-16 12:15:58 +02:00
Benoit Blanchon
38f61d322f VariantImpl: change setRawString() to return bool 2025-07-16 12:11:58 +02:00
Benoit Blanchon
3793996d83 VariantImpl: change setRawString() to take an adapted string 2025-07-16 12:08:30 +02:00
Benoit Blanchon
43548db37d ResourceManager: move out-of-class definitions back in the class 2025-07-15 18:32:38 +02:00
Benoit Blanchon
59573ac1f9 ResourceManager: decouple from VariantImpl 2025-07-14 19:51:25 +02:00
Benoit Blanchon
14a48978d9 CollectionIterator: rename next() to move() 2025-07-14 19:51:25 +02:00
Benoit Blanchon
2fa8b9664c VariantImpl: move out-of-class definitions back in the class 2025-07-14 19:51:25 +02:00
Benoit Blanchon
2e20ce0795 CollectionIterator: remove friendship with VariantImpl 2025-07-11 18:36:51 +02:00
Benoit Blanchon
a5164f7fe3 CollectionIterator: remove data() 2025-07-11 18:34:17 +02:00
Benoit Blanchon
a9996d2293 VariantImpl: rename getResourceManager() to resources() 2025-07-11 18:31:38 +02:00
Benoit Blanchon
f9655a90ed VariantImpl: rename getData() to data() 2025-07-11 18:30:10 +02:00
Benoit Blanchon
25942cce2d CollectionIterator: replace value() with operator* and operator-> 2025-07-11 18:24:51 +02:00
Benoit Blanchon
c6fa8c1c1f Add VariantImpl in CollectionIterator 2025-07-11 18:19:35 +02:00
Benoit Blanchon
5589633697 Add ResourceManager* in CollectionIterator +80 2025-07-11 18:06:47 +02:00
Benoit Blanchon
7b7ee6cdeb Add VariantImpl::isUnbound() 2025-07-11 14:32:30 +02:00
Benoit Blanchon
dca5ba481f CollectionIterator: add value() member function 2025-07-10 19:30:51 +02:00
Benoit Blanchon
ec53525452 Rename CollectionData.hpp to CollectionIterator.hpp 2025-07-10 19:30:21 +02:00
Benoit Blanchon
0b9a8b6d2b Replace getData()/getResourceManager() with getImpl() 2025-07-10 19:29:32 +02:00
Benoit Blanchon
f62d6f26e0 Merge ArrayImpl, CollectionImpl, and ObjectImpl into VariantImpl 2025-07-10 19:29:32 +02:00
Benoit Blanchon
1f1227d595 CollectionImpl: attach to VariantData* instead of CollectionData* 2025-07-10 19:29:32 +02:00
Benoit Blanchon
6f28ec0384 CollectionImpl: add allocVariant(), getVariant(), freeVariant() 2025-07-10 19:29:32 +02:00
Benoit Blanchon
6ba4507252 CollectionImpl: add helper getCollectionData() 2025-07-10 19:28:50 +02:00
Benoit Blanchon
a265ef4927 Move CollectionData definition 2025-07-10 19:28:50 +02:00
Benoit Blanchon
8790802ce3 Decouple VariantImpl from ArrayImpl and ObjectImpl 2025-07-10 19:28:50 +02:00
Benoit Blanchon
f1b06b588e Move VariantImpl definition to VariantImpl.hpp 2025-07-10 19:28:50 +02:00
Benoit Blanchon
84fcd90658 Extract VariantImpl 2025-07-10 19:28:50 +02:00
Benoit Blanchon
bd551fa4ce Extract ArrayImpl, CollectionImpl, and ObjectImpl 2025-07-10 19:27:44 +02:00
Benoit Blanchon
d9c6a70595 JsonObject: replace ObjectData* member with VariantData* 2025-07-10 19:17:13 +02:00
Benoit Blanchon
b44aa3a8b5 JsonArray: replace ArrayData* member with VariantData* 2025-07-10 19:17:13 +02:00
Benoit Blanchon
c2290a2386 JsonObjectConst: replace ObjectData* member with VariantData* 2025-07-10 19:17:13 +02:00
Benoit Blanchon
4457cb7cc5 JsonArrayConst: replace ArrayData* member with VariantData* 2025-07-10 19:17:13 +02:00
Benoit Blanchon
1e0bbd518c Add a pool dedicated to 8-byte values (double/int64_t/uint64_t)
This new pool replaced the "extension" slot where a secondary variant slot was used to store 8-byte values.
2025-07-10 19:17:13 +02:00
Benoit Blanchon
cc077c1b63 Store static strings in a dedicated pool
Because a slot id is smaller than a pointer, this change will ultimately allow reducing the slot size.
2025-07-10 19:17:13 +02:00
Benoit Blanchon
509807d3c2 Remove CollectionIterator::nextId_ 2025-07-10 19:16:47 +02:00
Benoit Blanchon
c07744dc0d CI: upgrade Windows runner 2025-06-26 20:47:57 +02:00
Benoit Blanchon
733bc4ee82 Set version to 7.4.2 2025-06-20 09:37:14 +02:00
Benoit Blanchon
9aa77994b4 Remove sponsor Programming Electronics Academy 2025-06-20 08:43:48 +02:00
Benoit Blanchon
f051f8328d Undef true and false macros
Replacing `true` with `1` changes `JsonString("ABC", true)` into `JsonString("ABC", 1)`, so all static strings had a length of 1.

This is a workaround for arduino/ArduinoCore-sam#50
Fixes #2181
2025-06-19 13:59:15 +02:00
Benoit Blanchon
411424b74e CI: upgrade clang-tidy 2025-05-20 20:55:01 +02:00
Benoit Blanchon
5e5c287978 CI: upgrade Clang versions 2025-05-20 18:24:46 +02:00
Benoit Blanchon
377cf63075 CI: upgrade runner to ubuntu-22.04 2025-05-20 14:48:42 +02:00
Benoit Blanchon
3252013509 Set version to 7.4.1 2025-04-11 15:43:42 +02:00
Benoit Blanchon
deab127a2f Fix crash with tiny Flash strings (issue #2170) 2025-04-11 10:23:43 +02:00
98 changed files with 1939 additions and 2051 deletions

View File

@@ -93,44 +93,40 @@ 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-20.04
runner: ubuntu-22.04
archive: focal
- clang: "8"
cxxflags: -fsanitize=leak -fno-sanitize-recover=all
runner: ubuntu-20.04
runner: ubuntu-22.04
archive: focal
- clang: "9"
cxxflags: -fsanitize=undefined -fno-sanitize-recover=all
runner: ubuntu-20.04
runner: ubuntu-22.04
archive: focal
- clang: "10"
cxxflags: -fsanitize=address -fno-sanitize-recover=all
runner: ubuntu-20.04
runner: ubuntu-22.04
archive: focal
- clang: "11"
runner: ubuntu-22.04
- clang: "12"
runner: ubuntu-22.04
- clang: "13"
runner: ubuntu-22.04
- clang: "14"
runner: ubuntu-22.04
- clang: "15"
runner: ubuntu-22.04
runs-on: ${{ matrix.runner }}
- clang: 14
- clang: 15
- clang: 16
- clang: 17
- clang: 18
- clang: 19
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
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 }}
@@ -138,10 +134,9 @@ 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
@@ -163,7 +158,7 @@ jobs:
conf_test:
name: Test configuration on Linux
needs: [gcc, clang]
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install
run: |
@@ -184,19 +179,19 @@ jobs:
conf_test_windows:
name: Test configuration on Windows
runs-on: windows-2019
runs-on: windows-2022
needs: [gcc, clang]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: 32-bit
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
cl /Isrc extras/conf_test/x86.cpp
shell: cmd
- name: 64-bit
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cl /Isrc extras/conf_test/x64.cpp
shell: cmd
@@ -257,7 +252,7 @@ jobs:
board: arduino:avr:uno
- core: arduino:samd
board: arduino:samd:mkr1000
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -412,7 +407,7 @@ jobs:
arm:
name: GCC for ARM processor
needs: gcc
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install
run: |
@@ -431,7 +426,7 @@ jobs:
coverage:
needs: gcc
name: Coverage
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install
run: sudo apt-get install -y lcov ninja-build
@@ -463,7 +458,7 @@ jobs:
valgrind:
needs: gcc
name: Valgrind
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Install
run: |
@@ -485,24 +480,24 @@ jobs:
clang-tidy:
needs: clang
name: Clang-Tidy
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- name: Install
run: sudo apt-get install -y clang-tidy cmake ninja-build
run: sudo apt-get install -y clang-tidy libc++-dev libc++abi-dev
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: cmake -G Ninja -DCMAKE_CXX_CLANG_TIDY="clang-tidy-10;--warnings-as-errors=*" -DCMAKE_BUILD_TYPE=Debug .
run: cmake -G Ninja -DCMAKE_CXX_CLANG_TIDY="clang-tidy;--warnings-as-errors=*" -DCMAKE_BUILD_TYPE=Debug .
env:
CC: clang-10
CXX: clang++-10
CC: clang
CXX: clang++
- name: Check
run: cmake --build . -- -k 0
amalgamate:
needs: gcc
name: Amalgamate ArduinoJson.h
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -578,7 +573,7 @@ jobs:
codeql:
name: CodeQL
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: gcc
permissions:
@@ -587,20 +582,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-20.04
runs-on: ubuntu-22.04
steps:
- name: Set variables
id: init

View File

@@ -1,6 +1,53 @@
ArduinoJson: change log
=======================
HEAD
----
* Optimize storage of static strings
> ### BREAKING CHANGES
>
> 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.
>
> For example, the following code produces different output in 7.3 and 7.4:
>
> ```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"}
> ```
>
> `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.
>
> For example, if you have something like this:
>
> ```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)
------

View File

@@ -10,7 +10,7 @@ if(ESP_PLATFORM)
return()
endif()
project(ArduinoJson VERSION 7.4.0)
project(ArduinoJson VERSION 7.4.2)
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 3.9, 4.0, 5.0, 6.0, 7, 8, 9, 10, 11, 12, 13, 14, 15](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
* [Clang 7 to 19](https://github.com/bblanchon/ArduinoJson/actions?query=workflow%3A%22Continuous+Integration%22)
* [Continuously fuzzed with Google OSS Fuzz](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:arduinojson)
* Passes all default checks of [clang-tidy](https://releases.llvm.org/10.0.0/tools/clang/tools/extra/docs/clang-tidy/)
* Well documented
@@ -139,11 +139,6 @@ 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.0.{build}
version: 7.4.2.{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(ArduinoJson::detail::ResourceManager::slotSize == 6, "slot size");
static_assert(sizeof(ArduinoJson::detail::VariantData) == 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(ArduinoJson::detail::ResourceManager::slotSize == 8, "slot size");
static_assert(sizeof(ArduinoJson::detail::VariantData) == 8, "slot size");
void setup() {}
void loop() {}

View File

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

View File

@@ -54,7 +54,7 @@ TEST_CASE("BasicJsonDocument") {
doc["hello"] = "world";
auto copy = doc;
REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(allocatorLog == "AA");
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);
}
inline size_t sizeofStaticStringPool(
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
using namespace ArduinoJson::detail;
return MemoryPool<VariantData>::slotsToBytes(n);
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 ArduinoJson::detail::sizeofArray;
using namespace ArduinoJson::detail;
TEST_CASE("JsonArray::add(T)") {
SpyingAllocator spy;
@@ -33,7 +33,8 @@ TEST_CASE("JsonArray::add(T)") {
REQUIRE(array[0].is<double>());
REQUIRE_FALSE(array[0].is<bool>());
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofPool<VariantData>()),
Allocate(sizeofPool<EightByteValue>()),
});
}
@@ -56,6 +57,7 @@ TEST_CASE("JsonArray::add(T)") {
REQUIRE(array[0].is<int>() == false);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}

View File

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

View File

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

View File

@@ -121,7 +121,7 @@ TEST_CASE("deserializeJson() returns NoMemory if string length overflows") {
}
}
TEST_CASE("deserializeJson() returns NoMemory if extension allocation fails") {
TEST_CASE("deserializeJson() returns NoMemory if 8-bit slot allocation fails") {
JsonDocument doc(FailingAllocator::instance());
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

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

View File

@@ -25,6 +25,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
@@ -34,6 +35,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
@@ -44,6 +46,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
@@ -55,8 +58,8 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
@@ -71,6 +74,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
@@ -399,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;
@@ -412,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,6 +32,7 @@ TEST_CASE("JsonDocument::add(T)") {
REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}

View File

@@ -69,6 +69,8 @@ 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

@@ -44,6 +44,8 @@ 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,6 +64,7 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
@@ -85,6 +88,7 @@ TEST_CASE("JsonDocument constructor") {
REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}

View File

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

View File

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

View File

@@ -75,7 +75,11 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() == AllocatorLog{});
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}
SECTION("owned string") {
@@ -110,7 +114,9 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}
@@ -137,7 +143,9 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofArray(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
}
@@ -164,20 +172,23 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(2)),
});
}
SECTION("owned string in object") {
doc["key"] = "abcdefg"_s;
doc["key1"_s] = "value"_s;
doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}");
REQUIRE(doc.as<std::string>() == "{\"key1\":\"value\"}");
REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("abcdefg")),
Allocate(sizeofString("key1")),
Allocate(sizeofString("value")),
Reallocate(sizeofPool(), sizeofPool(2)),
});
}

View File

@@ -25,8 +25,6 @@ TEST_CASE("JsonDocument::operator[]") {
SECTION("string literal") {
REQUIRE(doc["abc"] == "ABC");
REQUIRE(cdoc["abc"] == "ABC");
REQUIRE(doc["abc\0d"] == "ABCD");
REQUIRE(cdoc["abc\0d"] == "ABCD");
}
SECTION("std::string") {
@@ -114,6 +112,7 @@ TEST_CASE("JsonDocument::operator[] key storage") {
REQUIRE(doc.as<std::string>() == "{\"hello\":0}");
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}

View File

@@ -26,25 +26,12 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(obj2["hello"] == "world"_s);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}
SECTION("copy local string value") {
obj1["hello"] = "world"_s;
spy.clearLog();
bool success = obj2.set(obj1);
REQUIRE(success == true);
REQUIRE(obj2["hello"] == "world"_s);
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("world")),
});
}
SECTION("copy local key") {
obj1["hello"_s] = "world";
SECTION("copy local string key and value") {
obj1["hello"_s] = "world"_s;
spy.clearLog();
bool success = obj2.set(obj1);
@@ -54,6 +41,7 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
Allocate(sizeofString("world")),
});
}
@@ -110,7 +98,7 @@ TEST_CASE("JsonObject::set()") {
}
SECTION("copy fails in the middle of an array") {
TimebombAllocator timebomb(1);
TimebombAllocator timebomb(2);
JsonDocument doc3(&timebomb);
JsonObject obj3 = doc3.to<JsonObject>();

View File

@@ -102,21 +102,25 @@ TEST_CASE("JsonObject::operator[]") {
REQUIRE(42 == obj[key]);
}
SECTION("should not duplicate const char*") {
SECTION("string literals") {
obj["hello"] = "world";
REQUIRE(spy.log() == AllocatorLog{Allocate(sizeofPool())});
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")] = "world";
obj[const_cast<char*>("hello")] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
@@ -136,12 +140,13 @@ TEST_CASE("JsonObject::operator[]") {
obj["hello"] = "world"_s;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")),
});
}
SECTION("should duplicate std::string key") {
obj["hello"_s] = "world";
obj["hello"_s] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
@@ -158,7 +163,7 @@ TEST_CASE("JsonObject::operator[]") {
}
SECTION("should duplicate a non-static JsonString key") {
obj[JsonString("hello", false)] = "world";
obj[JsonString("hello", false)] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofString("hello")),
@@ -166,9 +171,10 @@ TEST_CASE("JsonObject::operator[]") {
}
SECTION("should not duplicate a static JsonString key") {
obj[JsonString("hello", true)] = "world";
obj[JsonString("hello", true)] = 42;
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
}

View File

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

View File

@@ -8,7 +8,7 @@
#include "Allocators.hpp"
#include "Literals.hpp"
using ArduinoJson::detail::sizeofObject;
using namespace ArduinoJson::detail;
enum ErrorCode { ERROR_01 = 1, ERROR_10 = 10 };
@@ -23,7 +23,9 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
CHECK(variant ==
"hello"_s); // linked string cannot contain '\0' at the moment
CHECK(spy.log() == AllocatorLog{});
CHECK(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
}
SECTION("const char*") {
@@ -149,7 +151,9 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true);
REQUIRE(variant == "world"); // stores by pointer
REQUIRE(spy.log() == AllocatorLog{});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
}
SECTION("non-static JsonString") {
@@ -193,11 +197,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()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
}
SECTION("int32_t") {
@@ -216,11 +220,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()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
}
SECTION("uint32_t") {
@@ -239,11 +243,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()),
Reallocate(sizeofPool(), sizeofPool(1)), // one extension slot
});
REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool<EightByteValue>()),
Reallocate(sizeofPool<EightByteValue>(),
sizeofPool<EightByteValue>(1)),
});
}
SECTION("JsonDocument") {
@@ -265,6 +269,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);
@@ -360,7 +378,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
}
SECTION("float") {
v.set(1.2);
v.set(1.2f);
REQUIRE(spy.log() == AllocatorLog{
Deallocate(sizeofString("world")),
});
@@ -375,7 +393,7 @@ TEST_CASE("JsonVariant::set() releases the previous value") {
}
}
TEST_CASE("JsonVariant::set() reuses extension slot") {
TEST_CASE("JsonVariant::set() reuses 8-bit slot") {
SpyingAllocator spy;
JsonDocument doc(&spy);
JsonVariant variant = doc.to<JsonVariant>();

View File

@@ -57,7 +57,7 @@ TEST_CASE("JsonVariantConst::operator[]") {
SECTION("string literal") {
REQUIRE(var["ab"] == "AB"_s);
REQUIRE(var["abc"] == "ABC"_s);
REQUIRE(var["abc\0d"] == "ABCD"_s);
REQUIRE(var["abc\0d"] == "ABC"_s);
REQUIRE(var["def"].isNull());
REQUIRE(var[0].isNull());
}

View File

@@ -29,3 +29,23 @@ 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

@@ -21,7 +21,7 @@ TEST_CASE("adaptString()") {
auto s = adaptString("bravo\0alpha");
CHECK(s.isNull() == false);
CHECK(s.size() == 11);
CHECK(s.size() == 5);
CHECK(s.isStatic() == true);
}

View File

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

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

View File

@@ -5,6 +5,7 @@
add_executable(ResourceManagerTests
allocVariant.cpp
clear.cpp
saveStaticString.cpp
saveString.cpp
shrinkToFit.cpp
size.cpp

View File

@@ -2,7 +2,7 @@
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/StringBuffer.hpp>
#include <ArduinoJson.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
@@ -21,8 +21,8 @@ TEST_CASE("StringBuffer") {
strcpy(ptr, "hi!");
sb.save(&variant);
REQUIRE(variant.type() == VariantType::TinyString);
REQUIRE(variant.asString() == "hi!");
REQUIRE(variant.type == VariantType::TinyString);
REQUIRE(VariantImpl(&variant, &resources).asString() == "hi!");
}
SECTION("Tiny string can't contain NUL") {
@@ -30,9 +30,9 @@ TEST_CASE("StringBuffer") {
memcpy(ptr, "a\0b", 3);
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
REQUIRE(variant.type == VariantType::OwnedString);
auto str = variant.asString();
auto str = VariantImpl(&variant, &resources).asString();
REQUIRE(str.size() == 3);
REQUIRE(str.c_str()[0] == 'a');
REQUIRE(str.c_str()[1] == 0);
@@ -44,7 +44,7 @@ TEST_CASE("StringBuffer") {
strcpy(ptr, "alfa");
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
REQUIRE(variant.asString() == "alfa");
REQUIRE(variant.type == VariantType::OwnedString);
REQUIRE(VariantImpl(&variant, &resources).asString() == "alfa");
}
}

View File

@@ -2,7 +2,7 @@
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#include <ArduinoJson/Memory/StringBuilder.hpp>
#include <ArduinoJson.hpp>
#include <catch.hpp>
#include "Allocators.hpp"
@@ -26,7 +26,7 @@ TEST_CASE("StringBuilder") {
REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStringBuffer()),
});
REQUIRE(data.type() == VariantType::TinyString);
REQUIRE(data.type == VariantType::TinyString);
}
SECTION("Tiny string") {
@@ -45,8 +45,8 @@ TEST_CASE("StringBuilder") {
str.save(&data);
REQUIRE(resources.overflowed() == false);
REQUIRE(data.type() == VariantType::TinyString);
REQUIRE(data.asString() == "url");
REQUIRE(data.type == VariantType::TinyString);
REQUIRE(VariantImpl(&data, &resources).asString() == "url");
}
SECTION("Short string fits in first allocation") {
@@ -116,12 +116,12 @@ TEST_CASE("StringBuilder") {
}
}
static JsonString saveString(StringBuilder& builder, const char* s) {
static VariantData saveString(StringBuilder& builder, const char* s) {
VariantData data;
builder.startString();
builder.append(s);
builder.save(&data);
return data.asString();
return data;
}
TEST_CASE("StringBuilder::save() deduplicates strings") {
@@ -134,9 +134,10 @@ TEST_CASE("StringBuilder::save() deduplicates strings") {
auto s2 = saveString(builder, "world");
auto s3 = saveString(builder, "hello");
REQUIRE(s1 == "hello");
REQUIRE(s2 == "world");
REQUIRE(+s1.c_str() == +s3.c_str()); // same address
REQUIRE(VariantImpl(&s1, &resources).asString() == "hello");
REQUIRE(VariantImpl(&s2, &resources).asString() == "world");
REQUIRE(+VariantImpl(&s1, &resources).asString().c_str() ==
+VariantImpl(&s3, &resources).asString().c_str()); // same address
REQUIRE(spy.log() ==
AllocatorLog{
@@ -152,9 +153,11 @@ TEST_CASE("StringBuilder::save() deduplicates strings") {
auto s1 = saveString(builder, "hello world");
auto s2 = saveString(builder, "hello");
REQUIRE(s1 == "hello world");
REQUIRE(s2 == "hello");
REQUIRE(+s2.c_str() != +s1.c_str()); // different address
REQUIRE(VariantImpl(&s1, &resources).asString() == "hello world");
REQUIRE(VariantImpl(&s2, &resources).asString() == "hello");
REQUIRE(
+VariantImpl(&s1, &resources).asString().c_str() !=
+VariantImpl(&s2, &resources).asString().c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{
@@ -169,9 +172,11 @@ TEST_CASE("StringBuilder::save() deduplicates strings") {
auto s1 = saveString(builder, "hello world");
auto s2 = saveString(builder, "worl");
REQUIRE(s1 == "hello world");
REQUIRE(s2 == "worl");
REQUIRE(s2.c_str() != s1.c_str()); // different address
REQUIRE(VariantImpl(&s1, &resources).asString() == "hello world");
REQUIRE(VariantImpl(&s2, &resources).asString() == "worl");
REQUIRE(
VariantImpl(&s1, &resources).asString().c_str() !=
VariantImpl(&s2, &resources).asString().c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{

View File

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

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,6 @@
// MIT License
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Memory/ResourceManagerImpl.hpp>
#include <catch.hpp>
#include "Allocators.hpp"

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
version: "7.4.0"
version: "7.4.2"
description: >-
A simple and efficient JSON library for embedded C++.
★ 6896 stars on GitHub!
★ 6953 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++. ⭐ 6896 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++. ⭐ 6953 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.0",
"version": "7.4.2",
"authors": {
"name": "Benoit Blanchon",
"url": "https://blog.benoitblanchon.fr"

View File

@@ -1,9 +1,9 @@
name=ArduinoJson
version=7.4.0
version=7.4.2
author=Benoit Blanchon <blog.benoitblanchon.fr>
maintainer=Benoit Blanchon <blog.benoitblanchon.fr>
sentence=A simple and efficient JSON library for embedded C++.
paragraph=⭐ 6896 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.
paragraph=⭐ 6953 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,6 +26,15 @@
# 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"
@@ -36,13 +45,10 @@
#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

@@ -1,66 +0,0 @@
// 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,42 +4,57 @@
#pragma once
#include <ArduinoJson/Array/ArrayData.hpp>
#include <ArduinoJson/Variant/VariantCompare.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
#include <ArduinoJson/Collection/CollectionIterator.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline ArrayData::iterator ArrayData::at(
size_t index, const ResourceManager* resources) const {
auto it = createIterator(resources);
inline VariantImpl::iterator VariantImpl::at(size_t index) const {
if (!isArray())
return iterator();
auto it = createIterator();
while (!it.done() && index) {
it.next(resources);
it.move();
--index;
}
return it;
}
inline VariantData* ArrayData::addElement(ResourceManager* resources) {
auto slot = resources->allocVariant();
inline VariantData* VariantImpl::addElement() {
if (!isArray())
return nullptr;
auto slot = allocVariant();
if (!slot)
return nullptr;
CollectionData::appendOne(slot, resources);
addElement(slot);
return slot.ptr();
}
inline VariantData* ArrayData::getOrAddElement(size_t index,
ResourceManager* resources) {
auto it = createIterator(resources);
inline void VariantImpl::addElement(Slot<VariantData> slot) {
auto coll = getCollectionData();
if (coll->tail != NULL_SLOT) {
auto tail = getVariant(coll->tail);
tail->next = slot.id();
coll->tail = slot.id();
} else {
coll->head = slot.id();
coll->tail = slot.id();
}
}
inline VariantData* VariantImpl::getOrAddElement(size_t index) {
auto it = createIterator();
while (!it.done() && index > 0) {
it.next(resources);
it.move();
index--;
}
if (it.done())
index++;
VariantData* element = it.data();
VariantData* element = it->data();
while (index > 0) {
element = addElement(resources);
element = addElement();
if (!element)
return nullptr;
index--;
@@ -47,33 +62,48 @@ inline VariantData* ArrayData::getOrAddElement(size_t index,
return element;
}
inline VariantData* ArrayData::getElement(
size_t index, const ResourceManager* resources) const {
return at(index, resources).data();
inline VariantData* VariantImpl::getElement(size_t index) const {
return at(index)->data();
}
inline void ArrayData::removeElement(size_t index, ResourceManager* resources) {
remove(at(index, resources), resources);
inline void VariantImpl::removeElement(iterator it) {
if (!isArray())
return;
removeOne(it);
}
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);
inline void VariantImpl::removeElement(size_t index) {
removeElement(at(index));
}
inline bool VariantImpl::copyArray(const VariantImpl& src) {
ARDUINOJSON_ASSERT(isNull());
if (!data_)
return false;
data_->toArray();
for (auto it = src.createIterator(); !it.done(); it.move()) {
auto slot = allocVariant();
if (!slot)
return false;
VariantImpl element(slot.ptr(), resources_);
if (!element.copyVariant(*it)) {
freeVariant(slot);
return false;
}
addElement(slot);
}
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 * ResourceManager::slotSize;
return n * sizeof(VariantData);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -50,22 +50,15 @@ class ElementProxy : public VariantRefBase<ElementProxy<TUpstream>>,
: upstream_(src.upstream_), index_(src.index_) {}
// clang-format on
ResourceManager* getResourceManager() const {
return VariantAttorney::getResourceManager(upstream_);
VariantImpl getImpl() const {
auto impl = VariantAttorney::getImpl(upstream_);
return VariantImpl(impl.getElement(index_), impl.resources());
}
FORCE_INLINE VariantData* getData() const {
return VariantData::getElement(
VariantAttorney::getData(upstream_), index_,
VariantAttorney::getResourceManager(upstream_));
}
VariantData* getOrCreateData() const {
auto data = VariantAttorney::getOrCreateData(upstream_);
if (!data)
return nullptr;
return data->getOrAddElement(
index_, VariantAttorney::getResourceManager(upstream_));
VariantImpl getOrCreateImpl() const {
auto impl = VariantAttorney::getOrCreateImpl(upstream_);
impl.toArrayIfNull();
return VariantImpl(impl.getOrAddElement(index_), impl.resources());
}
TUpstream upstream_;

View File

@@ -20,24 +20,21 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
using iterator = JsonArrayIterator;
// Constructs an unbound reference.
JsonArray() : data_(0), resources_(0) {}
JsonArray() {}
// INTERNAL USE ONLY
JsonArray(detail::ArrayData* data, detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
JsonArray(detail::VariantImpl impl) : impl_(impl) {}
// Returns a JsonVariant pointing to the array.
// https://arduinojson.org/v7/api/jsonvariant/
operator JsonVariant() {
void* data = data_; // prevent warning cast-align
return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
resources_);
return JsonVariant(impl_);
}
// Returns a read-only reference to the array.
// https://arduinojson.org/v7/api/jsonarrayconst/
operator JsonArrayConst() const {
return JsonArrayConst(data_, resources_);
return JsonArrayConst(impl_);
}
// Appends a new (empty) element to the array.
@@ -55,15 +52,14 @@ 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(detail::ArrayData::addElement(data_, resources_),
resources_);
return JsonVariant(impl_.addElement(), impl_.resources());
}
// Appends a value to the array.
// https://arduinojson.org/v7/api/jsonarray/add/
template <typename T>
bool add(const T& value) const {
return detail::ArrayData::addValue(data_, value, resources_);
return doAdd(value);
}
// Appends a value to the array.
@@ -71,15 +67,13 @@ 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 {
return detail::ArrayData::addValue(data_, value, resources_);
return doAdd(value);
}
// Returns an iterator to the first element of the array.
// https://arduinojson.org/v7/api/jsonarray/begin/
iterator begin() const {
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
return iterator(impl_.createIterator());
}
// Returns an iterator following the last element of the array.
@@ -91,28 +85,20 @@ 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))
return false;
}
return true;
impl_.clear();
return impl_.copyArray(detail::VariantAttorney::getImpl(src));
}
// Removes the element at the specified iterator.
// https://arduinojson.org/v7/api/jsonarray/remove/
void remove(iterator it) const {
detail::ArrayData::remove(data_, it.iterator_, resources_);
impl_.removeElement(it.iterator_);
}
// Removes the element at the specified index.
// https://arduinojson.org/v7/api/jsonarray/remove/
void remove(size_t index) const {
detail::ArrayData::removeElement(data_, index, resources_);
impl_.removeElement(index);
}
// Removes the element at the specified index.
@@ -127,7 +113,8 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// Removes all the elements of the array.
// https://arduinojson.org/v7/api/jsonarray/clear/
void clear() const {
detail::ArrayData::clear(data_, resources_);
if (impl_.isArray())
impl_.empty();
}
// Gets or sets the element at the specified index.
@@ -150,31 +137,31 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
}
operator JsonVariantConst() const {
return JsonVariantConst(collectionToVariant(data_), resources_);
return JsonVariantConst(impl_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonarray/isnull/
bool isNull() const {
return data_ == 0;
return impl_.isNull();
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonarray/isnull/
operator bool() const {
return data_ != 0;
return !isNull();
}
// Returns the depth (nesting level) of the array.
// https://arduinojson.org/v7/api/jsonarray/nesting/
size_t nesting() const {
return detail::VariantData::nesting(collectionToVariant(data_), resources_);
return impl_.nesting();
}
// Returns the number of elements in the array.
// https://arduinojson.org/v7/api/jsonarray/size/
size_t size() const {
return data_ ? data_->size(resources_) : 0;
return impl_.size();
}
// DEPRECATED: use add<JsonVariant>() instead
@@ -200,20 +187,36 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
}
private:
detail::ResourceManager* getResourceManager() const {
return resources_;
const detail::VariantImpl& getImpl() const {
return impl_;
}
detail::VariantData* getData() const {
return collectionToVariant(data_);
const detail::VariantImpl& getOrCreateImpl() const {
return impl_;
}
detail::VariantData* getOrCreateData() const {
return collectionToVariant(data_);
template <typename T>
bool doAdd(const T& value) const {
if (!impl_.isArray())
return false;
auto resources = impl_.resources();
ARDUINOJSON_ASSERT(resources != nullptr);
auto slot = resources->allocVariant();
if (!slot)
return false;
if (!JsonVariant(slot.ptr(), resources).set(value)) {
detail::VariantImpl(slot.ptr(), resources).clear();
resources->freeVariant(slot);
return false;
}
impl_.addElement(slot);
return true;
}
detail::ArrayData* data_;
detail::ResourceManager* resources_;
mutable detail::VariantImpl impl_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -24,9 +24,7 @@ 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 {
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
return iterator(impl_.createIterator());
}
// Returns an iterator to the element following the last element of the array.
@@ -36,21 +34,17 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
}
// Creates an unbound reference.
JsonArrayConst() : data_(0), resources_(0) {}
JsonArrayConst() {}
// INTERNAL USE ONLY
JsonArrayConst(const detail::ArrayData* data,
const detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
JsonArrayConst(const detail::VariantImpl& impl) : impl_(impl) {}
// 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(
detail::ArrayData::getElement(data_, size_t(index), resources_),
resources_);
return JsonVariantConst(impl_.getElement(size_t(index)), impl_.resources());
}
// Returns the element at the specified index.
@@ -65,31 +59,31 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
}
operator JsonVariantConst() const {
return JsonVariantConst(getData(), resources_);
return JsonVariantConst(impl_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonarrayconst/isnull/
bool isNull() const {
return data_ == 0;
return impl_.isNull();
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonarrayconst/isnull/
operator bool() const {
return data_ != 0;
return !isNull();
}
// Returns the depth (nesting level) of the array.
// https://arduinojson.org/v7/api/jsonarrayconst/nesting/
size_t nesting() const {
return detail::VariantData::nesting(getData(), resources_);
return impl_.nesting();
}
// Returns the number of elements in the array.
// https://arduinojson.org/v7/api/jsonarrayconst/size/
size_t size() const {
return data_ ? data_->size(resources_) : 0;
return impl_.size();
}
// DEPRECATED: always returns zero
@@ -99,12 +93,11 @@ class JsonArrayConst : public detail::VariantOperators<JsonArrayConst> {
}
private:
const detail::VariantData* getData() const {
return collectionToVariant(data_);
const detail::VariantImpl& getImpl() const {
return impl_;
}
const detail::ArrayData* data_;
const detail::ResourceManager* resources_;
detail::VariantImpl impl_;
};
// Compares the content of two arrays.

View File

@@ -4,6 +4,7 @@
#pragma once
#include <ArduinoJson/Collection/CollectionIterator.hpp>
#include <ArduinoJson/Variant/JsonVariant.hpp>
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
@@ -30,12 +31,11 @@ class JsonArrayIterator {
public:
JsonArrayIterator() {}
explicit JsonArrayIterator(detail::ArrayData::iterator iterator,
detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
explicit JsonArrayIterator(const detail::VariantImpl::iterator& iterator)
: iterator_(iterator) {}
JsonVariant operator*() {
return JsonVariant(iterator_.data(), resources_);
return JsonVariant(*iterator_);
}
Ptr<JsonVariant> operator->() {
return operator*();
@@ -50,13 +50,12 @@ class JsonArrayIterator {
}
JsonArrayIterator& operator++() {
iterator_.next(resources_);
iterator_.move();
return *this;
}
private:
detail::ArrayData::iterator iterator_;
detail::ResourceManager* resources_;
detail::VariantImpl::iterator iterator_;
};
class JsonArrayConstIterator {
@@ -64,12 +63,11 @@ class JsonArrayConstIterator {
public:
JsonArrayConstIterator() {}
explicit JsonArrayConstIterator(detail::ArrayData::iterator iterator,
const detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
explicit JsonArrayConstIterator(const detail::VariantImpl::iterator& iterator)
: iterator_(iterator) {}
JsonVariantConst operator*() const {
return JsonVariantConst(iterator_.data(), resources_);
return JsonVariantConst(*iterator_);
}
Ptr<JsonVariantConst> operator->() {
return operator*();
@@ -84,13 +82,12 @@ class JsonArrayConstIterator {
}
JsonArrayConstIterator& operator++() {
iterator_.next(resources_);
iterator_.move();
return *this;
}
private:
detail::ArrayData::iterator iterator_;
const detail::ResourceManager* resources_;
mutable detail::VariantImpl::iterator iterator_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -1,122 +0,0 @@
// 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,7 +4,6 @@
#pragma once
#include <ArduinoJson/Collection/CollectionData.hpp>
#include <ArduinoJson/Memory/Alignment.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <ArduinoJson/Variant/VariantCompare.hpp>
@@ -12,125 +11,104 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline CollectionIterator::CollectionIterator(VariantData* slot, SlotId slotId)
: slot_(slot), currentId_(slotId) {
nextId_ = slot_ ? slot_->next() : NULL_SLOT;
inline VariantImpl::iterator VariantImpl::createIterator() const {
if (!data_ || !data_->isCollection())
return iterator();
auto coll = getCollectionData();
return iterator(coll->head, resources_);
}
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::addMember(Slot<VariantData> key,
Slot<VariantData> value) {
ARDUINOJSON_ASSERT(isObject());
ARDUINOJSON_ASSERT(key);
ARDUINOJSON_ASSERT(value);
inline CollectionData::iterator CollectionData::createIterator(
const ResourceManager* resources) const {
return iterator(resources->getVariant(head_), head_);
}
auto coll = getCollectionData();
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();
key->next = value.id();
if (coll->tail != NULL_SLOT) {
auto tail = getVariant(coll->tail);
tail->next = key.id();
coll->tail = value.id();
} else {
head_ = slot.id();
tail_ = slot.id();
coll->head = key.id();
coll->tail = value.id();
}
}
inline void CollectionData::appendPair(Slot<VariantData> key,
Slot<VariantData> value,
const ResourceManager* resources) {
key->setNext(value.id());
if (tail_ != NULL_SLOT) {
auto tail = resources->getVariant(tail_);
tail->setNext(key.id());
tail_ = value.id();
} else {
head_ = key.id();
tail_ = value.id();
}
}
inline void CollectionData::clear(ResourceManager* resources) {
auto next = head_;
while (next != NULL_SLOT) {
auto currId = next;
auto slot = resources->getVariant(next);
next = slot->next();
resources->freeVariant({slot, currId});
}
head_ = NULL_SLOT;
tail_ = NULL_SLOT;
}
inline Slot<VariantData> CollectionData::getPreviousSlot(
VariantData* target, const ResourceManager* resources) const {
inline Slot<VariantData> VariantImpl::getPreviousSlot(
VariantData* target) const {
auto coll = getCollectionData();
auto prev = Slot<VariantData>();
auto currentId = head_;
auto currentId = coll->head;
while (currentId != NULL_SLOT) {
auto currentSlot = resources->getVariant(currentId);
auto currentSlot = getVariant(currentId);
if (currentSlot == target)
break;
prev = Slot<VariantData>(currentSlot, currentId);
currentId = currentSlot->next();
currentId = currentSlot->next;
}
return prev;
}
inline void CollectionData::removeOne(iterator it, ResourceManager* resources) {
inline void VariantImpl::removeOne(iterator it) {
if (it.done())
return;
auto curr = it.slot_;
auto prev = getPreviousSlot(curr, resources);
auto next = curr->next();
auto coll = getCollectionData();
auto curr = it->data();
auto prev = getPreviousSlot(curr);
auto next = curr->next;
if (prev)
prev->setNext(next);
prev->next = next;
else
head_ = next;
coll->head = next;
if (next == NULL_SLOT)
tail_ = prev.id();
resources->freeVariant({it.slot_, it.currentId_});
coll->tail = prev.id();
freeVariant({it->data(), it.slotId()});
}
inline void CollectionData::removePair(ObjectData::iterator it,
ResourceManager* resources) {
inline void VariantImpl::removePair(VariantImpl::iterator it) {
if (it.done())
return;
auto keySlot = it.slot_;
auto keySlot = it->data();
auto valueId = it.nextId_;
auto valueSlot = resources->getVariant(valueId);
auto valueId = keySlot->next;
auto valueSlot = getVariant(valueId);
// remove value slot
keySlot->setNext(valueSlot->next());
resources->freeVariant({valueSlot, valueId});
keySlot->next = valueSlot->next;
freeVariant({valueSlot, valueId});
// remove key slot
removeOne(it, resources);
removeOne(it);
}
inline size_t CollectionData::nesting(const ResourceManager* resources) const {
inline size_t VariantImpl::nesting() const {
if (!data_ || !data_->isCollection())
return 0;
size_t maxChildNesting = 0;
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
size_t childNesting = it->nesting(resources);
for (auto it = createIterator(); !it.done(); it.move()) {
auto childNesting = it->nesting();
if (childNesting > maxChildNesting)
maxChildNesting = childNesting;
}
return maxChildNesting + 1;
}
inline size_t CollectionData::size(const ResourceManager* resources) const {
inline size_t VariantImpl::size() const {
if (!data_)
return 0;
size_t count = 0;
for (auto it = createIterator(resources); !it.done(); it.next(resources))
for (auto it = createIterator(); !it.done(); it.move())
count++;
if (data_->type == VariantType::Object)
count /= 2; // TODO: do this in JsonObject?
return count;
}

View File

@@ -0,0 +1,57 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Namespace.hpp>
#include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class CollectionIterator {
public:
CollectionIterator() {}
CollectionIterator(SlotId slotId, ResourceManager* resources)
: value_(resources->getVariant(slotId), resources), slotId_(slotId) {}
void move() {
ARDUINOJSON_ASSERT(!done());
auto nextId = value_.data()->next;
auto resources = value_.resources();
value_ = VariantImpl(resources->getVariant(nextId), resources);
slotId_ = nextId;
}
const VariantImpl& operator*() const {
return value_;
}
const VariantImpl* operator->() const {
return &value_;
}
bool done() const {
return value_.isUnbound();
}
bool operator==(const CollectionIterator& other) const {
return value_.data() == other->data();
}
bool operator!=(const CollectionIterator& other) const {
return !operator==(other);
}
SlotId slotId() const {
return slotId_;
}
private:
VariantImpl value_;
SlotId slotId_ = NULL_SLOT;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

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

View File

@@ -44,13 +44,13 @@ template <template <typename> class TDeserializer, typename TDestination,
typename TReader, typename TOptions>
DeserializationError doDeserialize(TDestination&& dst, TReader reader,
TOptions options) {
auto data = VariantAttorney::getOrCreateData(dst);
if (!data)
auto impl = VariantAttorney::getOrCreateImpl(dst);
if (impl.isUnbound())
return DeserializationError::NoMemory;
auto resources = VariantAttorney::getResourceManager(dst);
auto resources = impl.resources();
dst.clear();
auto err = TDeserializer<TReader>(resources, reader)
.parse(*data, options.filter, options.nestingLimit);
.parse(impl.data(), options.filter, options.nestingLimit);
shrinkJsonDocument(dst);
return err;
}

View File

@@ -88,7 +88,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// https://arduinojson.org/v7/api/jsondocument/clear/
void clear() {
resources_.clear();
data_.reset();
data_.type = detail::VariantType::Null;
}
// Returns true if the root is of the specified type.
@@ -120,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 data_.nesting(&resources_);
return getImpl().nesting();
}
// Returns the number of elements in the root array or object.
// https://arduinojson.org/v7/api/jsondocument/size/
size_t size() const {
return data_.size(&resources_);
return getImpl().size();
}
// Copies the specified document.
@@ -165,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 data_.getMember(detail::adaptString(key), &resources_) != 0;
return getImpl().getMember(detail::adaptString(key)) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -174,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 data_.getMember(detail::adaptString(key), &resources_) != 0;
return getImpl().getMember(detail::adaptString(key)) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -211,8 +211,8 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename TString,
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
JsonVariantConst operator[](const TString& key) const {
return JsonVariantConst(
data_.getMember(detail::adaptString(key), &resources_), &resources_);
return JsonVariantConst(getImpl().getMember(detail::adaptString(key)),
&resources_);
}
// Gets a root object's member.
@@ -222,8 +222,8 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
!detail::is_const<TChar>::value,
int> = 0>
JsonVariantConst operator[](TChar* key) const {
return JsonVariantConst(
data_.getMember(detail::adaptString(key), &resources_), &resources_);
return JsonVariantConst(getImpl().getMember(detail::adaptString(key)),
&resources_);
}
// Gets or sets a root array's element.
@@ -237,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(data_.getElement(index, &resources_), &resources_);
return JsonVariantConst(getImpl().getElement(index), &resources_);
}
// Gets or sets a root object's member.
@@ -267,14 +267,14 @@ 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 JsonVariant(data_.addElement(&resources_), &resources_);
return getOrCreateArray().add<T>();
}
// Appends a value to the root array.
// https://arduinojson.org/v7/api/jsondocument/add/
template <typename TValue>
bool add(const TValue& value) {
return data_.addValue(value, &resources_);
return getOrCreateArray().add(value);
}
// Appends a value to the root array.
@@ -282,7 +282,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename TChar,
detail::enable_if_t<!detail::is_const<TChar>::value, int> = 0>
bool add(TChar* value) {
return data_.addValue(value, &resources_);
return getOrCreateArray().add(value);
}
// Removes an element of the root array.
@@ -290,8 +290,7 @@ 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) {
detail::VariantData::removeElement(getData(), size_t(index),
getResourceManager());
getImpl().removeElement(size_t(index));
}
// Removes a member of the root object.
@@ -301,8 +300,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
!detail::is_const<TChar>::value,
int> = 0>
void remove(TChar* key) {
detail::VariantData::removeMember(getData(), detail::adaptString(key),
getResourceManager());
getImpl().removeMember(detail::adaptString(key));
}
// Removes a member of the root object.
@@ -310,8 +308,7 @@ 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) {
detail::VariantData::removeMember(getData(), detail::adaptString(key),
getResourceManager());
getImpl().removeMember(detail::adaptString(key));
}
// Removes a member of the root object or an element of the root array.
@@ -391,6 +388,20 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
}
private:
detail::VariantImpl getImpl() const {
return {&data_, &resources_};
}
detail::VariantImpl getOrCreateImpl() {
return {&data_, &resources_};
}
JsonArray getOrCreateArray() {
auto impl = getImpl();
impl.toArrayIfNull();
return JsonArray(impl);
}
JsonVariant getVariant() {
return JsonVariant(&data_, &resources_);
}
@@ -399,28 +410,12 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
return JsonVariantConst(&data_, &resources_);
}
detail::ResourceManager* getResourceManager() {
return &resources_;
}
detail::VariantData* getData() {
return &data_;
}
const detail::VariantData* getData() const {
return &data_;
}
detail::VariantData* getOrCreateData() {
return &data_;
}
detail::ResourceManager resources_;
detail::VariantData data_;
mutable detail::ResourceManager resources_;
mutable detail::VariantData data_;
};
inline void convertToJson(const JsonDocument& src, JsonVariant dst) {
dst.set(src.as<JsonVariantConst>());
inline bool convertToJson(const JsonDocument& src, JsonVariant dst) {
return 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,10 +60,12 @@ class JsonDeserializer {
template <typename TFilter>
DeserializationError::Code parseVariant(
VariantData& variant, TFilter filter,
VariantData* variant, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
ARDUINOJSON_ASSERT(variant != nullptr);
err = skipSpacesAndComments();
if (err)
return err;
@@ -71,13 +73,13 @@ class JsonDeserializer {
switch (current()) {
case '[':
if (filter.allowArray())
return parseArray(variant.toArray(), filter, nestingLimit);
return parseArray(variant->toArray(), filter, nestingLimit);
else
return skipArray(nestingLimit);
case '{':
if (filter.allowObject())
return parseObject(variant.toObject(), filter, nestingLimit);
return parseObject(variant->toObject(), filter, nestingLimit);
else
return skipObject(nestingLimit);
@@ -90,12 +92,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,7 +148,7 @@ class JsonDeserializer {
template <typename TFilter>
DeserializationError::Code parseArray(
ArrayData& array, TFilter filter,
VariantData* arrayData, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
@@ -171,13 +173,15 @@ class JsonDeserializer {
// Read each value
for (;;) {
if (elementFilter.allow()) {
VariantImpl array(arrayData, resources_);
// Allocate slot in array
VariantData* value = array.addElement(resources_);
VariantData* value = array.addElement();
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 {
@@ -232,7 +236,7 @@ class JsonDeserializer {
template <typename TFilter>
DeserializationError::Code parseObject(
ObjectData& object, TFilter filter,
VariantData* objectData, TFilter filter,
DeserializationOption::NestingLimit nestingLimit) {
DeserializationError::Code err;
@@ -273,19 +277,27 @@ class JsonDeserializer {
TFilter memberFilter = filter[key];
if (memberFilter.allow()) {
auto member = object.getMember(adaptString(key), resources_);
VariantImpl object(objectData, resources_);
auto member = object.getMember(adaptString(key));
if (!member) {
auto keyVariant = object.addPair(&member, resources_);
if (!keyVariant)
auto keySlot = resources_->allocVariant();
if (!keySlot)
return DeserializationError::NoMemory;
stringBuilder_.save(keyVariant);
auto valueSlot = resources_->allocVariant();
if (!valueSlot)
return DeserializationError::NoMemory;
object.addMember(keySlot, valueSlot);
stringBuilder_.save(keySlot.ptr());
member = valueSlot.ptr();
} else {
member->clear(resources_);
VariantImpl(member, resources_).clear();
}
// Parse value
err = parseVariant(*member, memberFilter, nestingLimit.decrement());
err = parseVariant(member, memberFilter, nestingLimit.decrement());
if (err)
return err;
} else {
@@ -379,7 +391,7 @@ class JsonDeserializer {
}
}
DeserializationError::Code parseStringValue(VariantData& variant) {
DeserializationError::Code parseStringValue(VariantData* variant) {
DeserializationError::Code err;
stringBuilder_.startString();
@@ -388,7 +400,7 @@ class JsonDeserializer {
if (err)
return err;
stringBuilder_.save(&variant);
stringBuilder_.save(variant);
return DeserializationError::Ok;
}
@@ -504,7 +516,7 @@ class JsonDeserializer {
return DeserializationError::Ok;
}
DeserializationError::Code parseNumericValue(VariantData& result) {
DeserializationError::Code parseNumericValue(VariantData* result) {
uint8_t n = 0;
char c = current();
@@ -518,26 +530,28 @@ class JsonDeserializer {
auto number = parseNumber(buffer_);
switch (number.type()) {
case NumberType::UnsignedInteger:
if (result.setInteger(number.asUnsignedInteger(), resources_))
if (VariantImpl(result, resources_)
.setInteger(number.asUnsignedInteger()))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
case NumberType::SignedInteger:
if (result.setInteger(number.asSignedInteger(), resources_))
if (VariantImpl(result, resources_)
.setInteger(number.asSignedInteger()))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
case NumberType::Float:
if (result.setFloat(number.asFloat(), resources_))
if (VariantImpl(result, resources_).setFloat(number.asFloat()))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
#if ARDUINOJSON_USE_DOUBLE
case NumberType::Double:
if (result.setFloat(number.asDouble(), resources_))
if (VariantImpl(result, resources_).setFloat(number.asDouble()))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;

View File

@@ -16,46 +16,42 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
public:
static const bool producesText = true;
JsonSerializer(TWriter writer, const ResourceManager* resources)
JsonSerializer(TWriter writer, ResourceManager* resources)
: formatter_(writer), resources_(resources) {}
size_t visit(const ArrayData& array) {
size_t visitArray(const VariantImpl& array) {
write('[');
auto slotId = array.head();
bool first = true;
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
slot->accept(*this, resources_);
slotId = slot->next();
if (slotId != NULL_SLOT)
for (auto it = array.createIterator(); !it.done(); it.move()) {
if (!first)
write(',');
it->accept(*this);
first = false;
}
write(']');
return bytesWritten();
}
size_t visit(const ObjectData& object) {
size_t visitObject(const VariantImpl& object) {
write('{');
auto slotId = object.head();
bool first = true;
bool isValue = false;
bool isKey = true;
for (auto it = object.createIterator(); !it.done(); it.move()) {
if (isValue)
write(':');
else if (!first)
write(',');
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
slot->accept(*this, resources_);
it->accept(*this);
slotId = slot->next();
if (slotId != NULL_SLOT)
write(isKey ? ':' : ',');
isKey = !isKey;
first = false;
isValue = !isValue;
}
write('}');
@@ -120,7 +116,7 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
TextFormatter<TWriter> formatter_;
protected:
const ResourceManager* resources_;
ResourceManager* resources_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -16,19 +16,18 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
using base = JsonSerializer<TWriter>;
public:
PrettyJsonSerializer(TWriter writer, const ResourceManager* resources)
PrettyJsonSerializer(TWriter writer, ResourceManager* resources)
: base(writer, resources), nesting_(0) {}
size_t visit(const ArrayData& array) {
auto it = array.createIterator(base::resources_);
size_t visitArray(const VariantImpl& array) {
auto it = array.createIterator();
if (!it.done()) {
base::write("[\r\n");
nesting_++;
while (!it.done()) {
indent();
it->accept(*this, base::resources_);
it.next(base::resources_);
it->accept(*this);
it.move();
base::write(it.done() ? "\r\n" : ",\r\n");
}
nesting_--;
@@ -40,8 +39,8 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
return this->bytesWritten();
}
size_t visit(const ObjectData& object) {
auto it = object.createIterator(base::resources_);
size_t visitObject(const VariantImpl& object) {
auto it = object.createIterator();
if (!it.done()) {
base::write("{\r\n");
nesting_++;
@@ -49,8 +48,8 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
while (!it.done()) {
if (isKey)
indent();
it->accept(*this, base::resources_);
it.next(base::resources_);
it->accept(*this);
it.move();
if (isKey)
base::write(": ");
else

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,26 +14,21 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class VariantData;
struct 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_);
staticStringsPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
}
ResourceManager(const ResourceManager&) = delete;
@@ -42,6 +37,10 @@ class ResourceManager {
friend void swap(ResourceManager& a, ResourceManager& b) {
swap(a.stringPool_, b.stringPool_);
swap(a.variantPools_, b.variantPools_);
swap(a.staticStringsPools_, b.staticStringsPools_);
#if ARDUINOJSON_USE_8_BYTE_POOL
swap(a.eightBytePools_, b.eightBytePools_);
#endif
swap_(a.allocator_, b.allocator_);
swap_(a.overflowed_, b.overflowed_);
}
@@ -58,14 +57,42 @@ class ResourceManager {
return overflowed_;
}
Slot<VariantData> allocVariant();
void freeVariant(Slot<VariantData> slot);
VariantData* getVariant(SlotId id) const;
Slot<VariantData> allocVariant() {
auto slot = variantPools_.allocSlot(allocator_);
if (!slot) {
overflowed_ = true;
return {};
}
new (slot.ptr()) VariantData();
return slot;
}
#if ARDUINOJSON_USE_EXTENSIONS
Slot<VariantExtension> allocExtension();
void freeExtension(SlotId slot);
VariantExtension* getExtension(SlotId id) const;
void freeVariant(Slot<VariantData> slot) {
variantPools_.freeSlot(slot);
}
VariantData* getVariant(SlotId id) const {
return 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);
}
#endif
template <typename TAdaptedString>
@@ -107,25 +134,55 @@ class ResourceManager {
StringNode::destroy(node, allocator_);
}
void dereferenceString(const char* s) {
stringPool_.dereference(s, allocator_);
void dereferenceString(StringNode* node) {
stringPool_.dereference(node, 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_);
staticStringsPools_.clear(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.clear(allocator_);
#endif
}
void shrinkToFit() {
variantPools_.shrinkToFit(allocator_);
staticStringsPools_.shrinkToFit(allocator_);
#if ARDUINOJSON_USE_8_BYTE_POOL
eightBytePools_.shrinkToFit(allocator_);
#endif
}
private:
Allocator* allocator_;
bool overflowed_;
StringPool stringPool_;
MemoryPoolList<SlotData> variantPools_;
MemoryPoolList<VariantData> variantPools_;
MemoryPoolList<const char*> staticStringsPools_;
#if ARDUINOJSON_USE_8_BYTE_POOL
MemoryPoolList<EightByteValue> eightBytePools_;
#endif
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

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

@@ -41,7 +41,7 @@ class StringBuffer {
ARDUINOJSON_ASSERT(node_ != nullptr);
const char* s = node_->data;
if (isTinyString(s, size_))
data->setTinyString(s, static_cast<uint8_t>(size_));
data->setTinyString(adaptString(s, size_));
else
data->setOwnedString(commitStringNode());
}

View File

@@ -5,6 +5,7 @@
#pragma once
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Strings/JsonString.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
@@ -31,7 +32,7 @@ class StringBuilder {
char* p = node_->data;
if (isTinyString(p, size_)) {
variant->setTinyString(p, static_cast<uint8_t>(size_));
variant->setTinyString(adaptString(p, size_));
return;
}

View File

@@ -78,20 +78,20 @@ class StringPool {
return nullptr;
}
void dereference(const char* s, Allocator* allocator) {
void dereference(StringNode* nodeToRemove, Allocator* allocator) {
StringNode* prev = nullptr;
for (auto node = strings_; node; node = node->next) {
if (node->data == s) {
if (--node->references == 0) {
for (auto current = strings_; current; current = current->next) {
if (current == nodeToRemove) {
if (--current->references == 0) {
if (prev)
prev->next = node->next;
prev->next = current->next;
else
strings_ = node->next;
StringNode::destroy(node, allocator);
strings_ = current->next;
StringNode::destroy(current, allocator);
}
return;
}
prev = node;
prev = current;
}
}

View File

@@ -24,52 +24,51 @@ class MsgPackBinary {
template <>
struct Converter<MsgPackBinary> : private detail::VariantAttorney {
static void toJson(MsgPackBinary src, JsonVariant dst) {
auto data = VariantAttorney::getData(dst);
if (!data)
return;
auto resources = getResourceManager(dst);
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;
}
static bool toJson(MsgPackBinary src, JsonVariant dst) {
auto impl = getImpl(dst);
if (impl.isUnbound())
return false;
if (!src.data())
return true;
size_t headerSize = src.size() >= 0x10000 ? 5 : src.size() >= 0x100 ? 3 : 2;
auto resources = impl.resources();
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);
}
memcpy(ptr + headerSize, src.data(), src.size());
impl.data()->setRawString(str);
return true;
}
static MsgPackBinary fromJson(JsonVariantConst src) {
auto data = getData(src);
if (!data)
return {};
auto rawstr = data->asRawString();
auto variant = getImpl(src);
auto rawstr = variant.asRawString();
auto p = reinterpret_cast<const uint8_t*>(rawstr.c_str());
auto n = rawstr.size();
if (n >= 2 && p[0] == 0xc4) { // bin 8

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)
variant->setInteger(static_cast<int8_t>(code), resources_);
VariantImpl(variant, resources_).setInteger(static_cast<int8_t>(code));
return DeserializationError::Ok;
}
@@ -231,14 +231,14 @@ class MsgPackDeserializer {
if (isSigned) {
auto truncatedValue = static_cast<JsonInteger>(signedValue);
if (truncatedValue == signedValue) {
if (!variant->setInteger(truncatedValue, resources_))
if (!VariantImpl(variant, resources_).setInteger(truncatedValue))
return DeserializationError::NoMemory;
}
// else set null on overflow
} else {
auto truncatedValue = static_cast<JsonUInt>(unsignedValue);
if (truncatedValue == unsignedValue)
if (!variant->setInteger(truncatedValue, resources_))
if (!VariantImpl(variant, resources_).setInteger(truncatedValue))
return DeserializationError::NoMemory;
// else set null on overflow
}
@@ -257,7 +257,7 @@ class MsgPackDeserializer {
return err;
fixEndianness(value);
variant->setFloat(value, resources_);
VariantImpl(variant, resources_).setFloat(value);
return DeserializationError::Ok;
}
@@ -273,7 +273,7 @@ class MsgPackDeserializer {
return err;
fixEndianness(value);
if (variant->setFloat(value, resources_))
if (VariantImpl(variant, resources_).setFloat(value))
return DeserializationError::Ok;
else
return DeserializationError::NoMemory;
@@ -293,7 +293,7 @@ class MsgPackDeserializer {
doubleToFloat(i, o);
fixEndianness(value);
variant->setFloat(value, resources_);
VariantImpl(variant, resources_).setFloat(value);
return DeserializationError::Ok;
}
@@ -349,12 +349,10 @@ class MsgPackDeserializer {
bool allowArray = filter.allowArray();
ArrayData* array;
VariantImpl array;
if (allowArray) {
ARDUINOJSON_ASSERT(variant != 0);
array = &variant->toArray();
} else {
array = 0;
array = VariantImpl(variant->toArray(), resources_);
}
TFilter elementFilter = filter[0U];
@@ -363,8 +361,7 @@ class MsgPackDeserializer {
VariantData* value;
if (elementFilter.allow()) {
ARDUINOJSON_ASSERT(array != 0);
value = array->addElement(resources_);
value = array.addElement();
if (!value)
return DeserializationError::NoMemory;
} else {
@@ -388,12 +385,10 @@ class MsgPackDeserializer {
if (nestingLimit.reached())
return DeserializationError::TooDeep;
ObjectData* object;
VariantImpl object;
if (filter.allowObject()) {
ARDUINOJSON_ASSERT(variant != 0);
object = &variant->toObject();
} else {
object = 0;
object = VariantImpl(variant->toObject(), resources_);
}
for (; n; --n) {
@@ -406,13 +401,18 @@ class MsgPackDeserializer {
VariantData* member = 0;
if (memberFilter.allow()) {
ARDUINOJSON_ASSERT(object != 0);
auto keyVariant = object->addPair(&member, resources_);
if (!keyVariant)
auto keySlot = resources_->allocVariant();
if (!keySlot)
return DeserializationError::NoMemory;
stringBuffer_.save(keyVariant);
auto valueSlot = resources_->allocVariant();
if (!valueSlot)
return DeserializationError::NoMemory;
object.addMember(keySlot, valueSlot);
member = valueSlot.ptr();
stringBuffer_.save(keySlot.ptr());
}
err = parseVariant(member, memberFilter, nestingLimit.decrement());

View File

@@ -30,60 +30,62 @@ class MsgPackExtension {
template <>
struct Converter<MsgPackExtension> : private detail::VariantAttorney {
static void toJson(MsgPackExtension src, JsonVariant dst) {
auto data = VariantAttorney::getData(dst);
if (!data)
return;
auto resources = getResourceManager(dst);
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;
}
static bool toJson(MsgPackExtension src, JsonVariant dst) {
auto impl = getImpl(dst);
if (impl.isUnbound())
return false;
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;
}
impl.clear();
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 resources = impl.resources();
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());
impl.data()->setRawString(str);
return true;
}
static MsgPackExtension fromJson(JsonVariantConst src) {
auto data = getData(src);
if (!data)
return {};
auto rawstr = data->asRawString();
auto variant = detail::VariantAttorney::getImpl(src);
auto rawstr = variant.asRawString();
if (rawstr.size() == 0)
return {};
auto p = reinterpret_cast<const uint8_t*>(rawstr.c_str());

View File

@@ -19,7 +19,7 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
public:
static const bool producesText = false;
MsgPackSerializer(TWriter writer, const ResourceManager* resources)
MsgPackSerializer(TWriter writer, ResourceManager* resources)
: writer_(writer), resources_(resources) {}
template <typename T>
@@ -47,8 +47,8 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
return bytesWritten();
}
size_t visit(const ArrayData& array) {
size_t n = array.size(resources_);
size_t visitArray(const VariantImpl& array) {
size_t n = array.size();
if (n < 0x10) {
writeByte(uint8_t(0x90 + n));
} else if (n < 0x10000) {
@@ -59,18 +59,14 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
writeInteger(uint32_t(n));
}
auto slotId = array.head();
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
slot->accept(*this, resources_);
slotId = slot->next();
}
for (auto it = array.createIterator(); !it.done(); it.move())
it->accept(*this);
return bytesWritten();
}
size_t visit(const ObjectData& object) {
size_t n = object.size(resources_);
size_t visitObject(const VariantImpl& object) {
size_t n = object.size();
if (n < 0x10) {
writeByte(uint8_t(0x80 + n));
} else if (n < 0x10000) {
@@ -81,12 +77,8 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
writeInteger(uint32_t(n));
}
auto slotId = object.head();
while (slotId != NULL_SLOT) {
auto slot = resources_->getVariant(slotId);
slot->accept(*this, resources_);
slotId = slot->next();
}
for (auto it = object.createIterator(); !it.done(); it.move())
it->accept(*this);
return bytesWritten();
}
@@ -209,7 +201,7 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
}
CountingDecorator<TWriter> writer_;
const ResourceManager* resources_;
ResourceManager* resources_;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -20,56 +20,51 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
using iterator = JsonObjectIterator;
// Creates an unbound reference.
JsonObject() : data_(0), resources_(0) {}
JsonObject() {}
// INTERNAL USE ONLY
JsonObject(detail::ObjectData* data, detail::ResourceManager* resource)
: data_(data), resources_(resource) {}
JsonObject(detail::VariantImpl impl) : impl_(impl) {}
operator JsonVariant() const {
void* data = data_; // prevent warning cast-align
return JsonVariant(reinterpret_cast<detail::VariantData*>(data),
resources_);
return JsonVariant(impl_);
}
operator JsonObjectConst() const {
return JsonObjectConst(data_, resources_);
return JsonObjectConst(impl_);
}
operator JsonVariantConst() const {
return JsonVariantConst(collectionToVariant(data_), resources_);
return JsonVariantConst(impl_);
}
// Returns true if the reference is unbound.
// https://arduinojson.org/v7/api/jsonobject/isnull/
bool isNull() const {
return data_ == 0;
return !operator bool();
}
// Returns true if the reference is bound.
// https://arduinojson.org/v7/api/jsonobject/isnull/
operator bool() const {
return data_ != 0;
return impl_.isObject();
}
// Returns the depth (nesting level) of the object.
// https://arduinojson.org/v7/api/jsonobject/nesting/
size_t nesting() const {
return detail::VariantData::nesting(collectionToVariant(data_), resources_);
return impl_.nesting();
}
// Returns the number of members in the object.
// https://arduinojson.org/v7/api/jsonobject/size/
size_t size() const {
return data_ ? data_->size(resources_) : 0;
return impl_.size();
}
// Returns an iterator to the first key-value pair of the object.
// https://arduinojson.org/v7/api/jsonobject/begin/
iterator begin() const {
if (!data_)
return iterator();
return iterator(data_->createIterator(resources_), resources_);
return iterator(impl_.createIterator());
}
// Returns an iterator following the last key-value pair of the object.
@@ -81,22 +76,18 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// Removes all the members of the object.
// https://arduinojson.org/v7/api/jsonobject/clear/
void clear() const {
detail::ObjectData::clear(data_, resources_);
if (impl_.isObject())
impl_.empty();
}
// Copies an object.
// https://arduinojson.org/v7/api/jsonobject/set/
bool set(JsonObjectConst src) {
if (!data_ || !src.data_)
if (isNull() ||
src.isNull()) // TODO: this check is not consistent with JsonArray
return false;
clear();
for (auto kvp : src) {
if (!operator[](kvp.key()).set(kvp.value()))
return false;
}
return true;
impl_.clear();
return impl_.copyObject(detail::VariantAttorney::getImpl(src));
}
// Gets or sets the member with specified key.
@@ -131,7 +122,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 {
detail::ObjectData::remove(data_, it.iterator_, resources_);
impl_.removeMember(it.iterator_);
}
// Removes the member with the specified key.
@@ -139,8 +130,7 @@ 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 {
detail::ObjectData::removeMember(data_, detail::adaptString(key),
resources_);
impl_.removeMember(detail::adaptString(key));
}
// Removes the member with the specified key.
@@ -156,8 +146,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// https://arduinojson.org/v7/api/jsonobject/remove/
template <typename TChar>
FORCE_INLINE void remove(TChar* key) const {
detail::ObjectData::removeMember(data_, detail::adaptString(key),
resources_);
impl_.removeMember(detail::adaptString(key));
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -166,8 +155,7 @@ 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 detail::ObjectData::getMember(data_, detail::adaptString(key),
resources_) != 0;
return impl_.getMember(detail::adaptString(key)) != 0;
}
// DEPRECATED: use obj["key"].is<T>() instead
@@ -178,8 +166,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
int> = 0>
ARDUINOJSON_DEPRECATED("use obj[\"key\"].is<T>() instead")
bool containsKey(TChar* key) const {
return detail::ObjectData::getMember(data_, detail::adaptString(key),
resources_) != 0;
return impl_.getMember(detail::adaptString(key)) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -226,20 +213,15 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
}
private:
detail::ResourceManager* getResourceManager() const {
return resources_;
const detail::VariantImpl& getImpl() const {
return impl_;
}
detail::VariantData* getData() const {
return detail::collectionToVariant(data_);
const detail::VariantImpl& getOrCreateImpl() const {
return impl_;
}
detail::VariantData* getOrCreateData() const {
return detail::collectionToVariant(data_);
}
detail::ObjectData* data_;
detail::ResourceManager* resources_;
mutable detail::VariantImpl impl_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

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

View File

@@ -14,12 +14,11 @@ class JsonObjectIterator {
public:
JsonObjectIterator() {}
explicit JsonObjectIterator(detail::ObjectData::iterator iterator,
detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
explicit JsonObjectIterator(const detail::VariantImpl::iterator& iterator)
: iterator_(iterator) {}
JsonPair operator*() const {
return JsonPair(iterator_, resources_);
return JsonPair(iterator_);
}
Ptr<JsonPair> operator->() {
return operator*();
@@ -34,14 +33,13 @@ class JsonObjectIterator {
}
JsonObjectIterator& operator++() {
iterator_.next(resources_); // key
iterator_.next(resources_); // value
iterator_.move(); // key
iterator_.move(); // value
return *this;
}
private:
detail::ObjectData::iterator iterator_;
detail::ResourceManager* resources_;
detail::VariantImpl::iterator iterator_;
};
class JsonObjectConstIterator {
@@ -50,12 +48,12 @@ class JsonObjectConstIterator {
public:
JsonObjectConstIterator() {}
explicit JsonObjectConstIterator(detail::ObjectData::iterator iterator,
const detail::ResourceManager* resources)
: iterator_(iterator), resources_(resources) {}
explicit JsonObjectConstIterator(
const detail::VariantImpl::iterator& iterator)
: iterator_(iterator) {}
JsonPairConst operator*() const {
return JsonPairConst(iterator_, resources_);
return JsonPairConst(iterator_);
}
Ptr<JsonPairConst> operator->() {
return operator*();
@@ -70,14 +68,13 @@ class JsonObjectConstIterator {
}
JsonObjectConstIterator& operator++() {
iterator_.next(resources_); // key
iterator_.next(resources_); // value
iterator_.move(); // key
iterator_.move(); // value
return *this;
}
private:
detail::ObjectData::iterator iterator_;
const detail::ResourceManager* resources_;
detail::VariantImpl::iterator iterator_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

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

View File

@@ -51,22 +51,15 @@ class MemberProxy
: upstream_(src.upstream_), key_(src.key_) {}
// clang-format on
ResourceManager* getResourceManager() const {
return VariantAttorney::getResourceManager(upstream_);
VariantImpl getImpl() const {
auto impl = VariantAttorney::getImpl(upstream_);
return VariantImpl(impl.getMember(key_), impl.resources());
}
VariantData* getData() const {
return VariantData::getMember(
VariantAttorney::getData(upstream_), key_,
VariantAttorney::getResourceManager(upstream_));
}
VariantData* getOrCreateData() const {
auto data = VariantAttorney::getOrCreateData(upstream_);
if (!data)
return nullptr;
return data->getOrAddMember(key_,
VariantAttorney::getResourceManager(upstream_));
VariantImpl getOrCreateImpl() const {
auto impl = VariantAttorney::getOrCreateImpl(upstream_);
impl.toObjectIfNull();
return VariantImpl(impl.getOrAddMember(key_), impl.resources());
}
private:

View File

@@ -1,70 +0,0 @@
// 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>
VariantData* addMember(TAdaptedString key, ResourceManager* resources);
VariantData* addPair(VariantData** value, 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,38 +4,76 @@
#pragma once
#include <ArduinoJson/Object/ObjectData.hpp>
#include <ArduinoJson/Variant/VariantCompare.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline bool VariantImpl::copyObject(const VariantImpl& src) {
ARDUINOJSON_ASSERT(isNull());
if (!data_)
return false;
data_->toObject();
for (auto it = src.createIterator(); !it.done(); it.move()) {
auto keySlot = allocVariant();
if (!keySlot)
return false;
auto key = VariantImpl(keySlot.ptr(), resources_);
if (!key.copyVariant(*it)) {
freeVariant(keySlot);
return false;
}
it.move(); // move to value
ARDUINOJSON_ASSERT(!it.done());
auto valueSlot = allocVariant();
if (!valueSlot) {
freeVariant(keySlot);
return false;
}
// TODO: we add the pair before copying the value to be keep the old
// behavior but this is not consistent with issue #2081
addMember(keySlot, valueSlot);
auto value = VariantImpl(valueSlot.ptr(), resources_);
if (!value.copyVariant(*it))
return false;
}
return true;
}
template <typename TAdaptedString>
inline VariantData* ObjectData::getMember(
TAdaptedString key, const ResourceManager* resources) const {
auto it = findKey(key, resources);
inline VariantData* VariantImpl::getMember(TAdaptedString key) const {
auto it = findKey(key);
if (it.done())
return nullptr;
it.next(resources);
return it.data();
it.move();
return it->data();
}
template <typename TAdaptedString>
VariantData* ObjectData::getOrAddMember(TAdaptedString key,
ResourceManager* resources) {
auto data = getMember(key, resources);
VariantData* VariantImpl::getOrAddMember(TAdaptedString key) {
auto data = getMember(key);
if (data)
return data;
return addMember(key, resources);
return addMember(key);
}
template <typename TAdaptedString>
inline ObjectData::iterator ObjectData::findKey(
TAdaptedString key, const ResourceManager* resources) const {
inline VariantImpl::iterator VariantImpl::findKey(TAdaptedString key) const {
if (!isObject())
return iterator();
if (key.isNull())
return iterator();
bool isKey = true;
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
for (auto it = createIterator(); !it.done(); it.move()) {
if (isKey && stringEquals(key, adaptString(it->asString())))
return it;
isKey = !isKey;
@@ -44,49 +82,42 @@ inline ObjectData::iterator ObjectData::findKey(
}
template <typename TAdaptedString>
inline void ObjectData::removeMember(TAdaptedString key,
ResourceManager* resources) {
remove(findKey(key, resources), resources);
inline void VariantImpl::removeMember(TAdaptedString key) {
removeMember(findKey(key));
}
inline void VariantImpl::removeMember(iterator it) {
removePair(it);
}
template <typename TAdaptedString>
inline VariantData* ObjectData::addMember(TAdaptedString key,
ResourceManager* resources) {
auto keySlot = resources->allocVariant();
inline VariantData* VariantImpl::addMember(TAdaptedString key) {
if (!isObject())
return nullptr;
if (key.isNull())
return nullptr; // Ignore null key
auto keySlot = allocVariant();
if (!keySlot)
return nullptr;
auto valueSlot = resources->allocVariant();
auto valueSlot = allocVariant();
if (!valueSlot)
return nullptr;
if (!keySlot->setString(key, resources))
VariantImpl keyImpl(keySlot.ptr(), resources_);
if (!keyImpl.setString(key))
return nullptr;
CollectionData::appendPair(keySlot, valueSlot, resources);
addMember(keySlot, valueSlot);
return valueSlot.ptr();
}
inline VariantData* ObjectData::addPair(VariantData** value,
ResourceManager* resources) {
auto keySlot = resources->allocVariant();
if (!keySlot)
return nullptr;
auto valueSlot = resources->allocVariant();
if (!valueSlot)
return nullptr;
*value = valueSlot.ptr();
CollectionData::appendPair(keySlot, valueSlot, resources);
return keySlot.ptr();
}
// Returns the size (in bytes) of an object with n members.
constexpr size_t sizeofObject(size_t n) {
return 2 * n * ResourceManager::slotSize;
return 2 * n * sizeof(VariantData);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -11,10 +11,9 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
template <template <typename> class TSerializer>
size_t measure(ArduinoJson::JsonVariantConst source) {
DummyWriter dp;
auto data = VariantAttorney::getData(source);
auto resources = VariantAttorney::getResourceManager(source);
TSerializer<DummyWriter> serializer(dp, resources);
return VariantData::accept(data, resources, serializer);
auto impl = VariantAttorney::getImpl(source);
TSerializer<DummyWriter> serializer(dp, impl.resources());
return impl.accept(serializer);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -10,10 +10,9 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
template <template <typename> class TSerializer, typename TWriter>
size_t doSerialize(ArduinoJson::JsonVariantConst source, TWriter writer) {
auto data = VariantAttorney::getData(source);
auto resources = VariantAttorney::getResourceManager(source);
TSerializer<TWriter> serializer(writer, resources);
return VariantData::accept(data, resources, serializer);
auto impl = VariantAttorney::getImpl(source);
TSerializer<TWriter> serializer(writer, impl.resources());
return impl.accept(serializer);
}
template <template <typename> class TSerializer, typename TDestination>

View File

@@ -90,8 +90,9 @@ template <size_t N>
struct StringAdapter<const char (&)[N]> {
using AdaptedString = RamString;
static AdaptedString adapt(const char (&p)[N]) {
return RamString(p, N - 1, true);
static AdaptedString adapt(const char* p) {
ARDUINOJSON_ASSERT(p);
return RamString(p, ::strlen(p), true);
}
};

View File

@@ -28,8 +28,7 @@ class JsonString {
detail::enable_if_t<detail::is_integral<TSize>::value &&
!detail::is_same<TSize, bool>::value,
int> = 0>
JsonString(const char* data, TSize sz, bool isStatic = false)
: str_(data, size_t(sz), isStatic) {}
JsonString(const char* data, TSize sz) : str_(data, size_t(sz), false) {}
// Returns a pointer to the characters.
const char* c_str() const {

View File

@@ -25,9 +25,10 @@ struct Converter {
"type 'char' is not supported, use 'signed char', 'unsigned "
"char' or another integer type instead");
static void toJson(const T& src, JsonVariant dst) {
static auto toJson(const T& src, JsonVariant dst)
-> decltype(convertToJson(src, dst)) {
// clang-format off
convertToJson(src, dst); // Error here? See https://arduinojson.org/v7/unsupported-set/
return convertToJson(src, dst); // Error here? See https://arduinojson.org/v7/unsupported-set/
// clang-format on
}
@@ -60,25 +61,18 @@ struct Converter<T, detail::enable_if_t<detail::is_integral<T>::value &&
: private detail::VariantAttorney {
static bool toJson(T src, JsonVariant dst) {
ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T);
auto data = getData(dst);
if (!data)
return false;
auto resources = getResourceManager(dst);
data->clear(resources);
return data->setInteger(src, resources);
auto impl = getImpl(dst);
impl.clear();
return impl.setInteger(src);
}
static T fromJson(JsonVariantConst src) {
ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T);
auto data = getData(src);
auto resources = getResourceManager(src);
return data ? data->template asIntegral<T>(resources) : T();
return getImpl(src).template asIntegral<T>();
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return data && data->template isInteger<T>(resources);
return getImpl(src).template isInteger<T>();
}
};
@@ -90,40 +84,28 @@ struct Converter<T, detail::enable_if_t<detail::is_enum<T>::value>>
}
static T fromJson(JsonVariantConst src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return data ? static_cast<T>(data->template asIntegral<int>(resources))
: T();
return static_cast<T>(getImpl(src).template asIntegral<int>());
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return data && data->template isInteger<int>(resources);
return getImpl(src).template isInteger<int>();
}
};
template <>
struct Converter<bool> : private detail::VariantAttorney {
static bool toJson(bool src, JsonVariant dst) {
auto data = getData(dst);
if (!data)
return false;
auto resources = getResourceManager(dst);
data->clear(resources);
data->setBoolean(src);
return true;
auto impl = getImpl(dst);
impl.clear();
return impl.setBoolean(src);
}
static bool fromJson(JsonVariantConst src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return data ? data->asBoolean(resources) : false;
return getImpl(src).asBoolean();
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data && data->isBoolean();
return getImpl(src).type() == detail::VariantType::Boolean;
}
};
@@ -131,69 +113,60 @@ template <typename T>
struct Converter<T, detail::enable_if_t<detail::is_floating_point<T>::value>>
: private detail::VariantAttorney {
static bool toJson(T src, JsonVariant dst) {
auto data = getData(dst);
if (!data)
return false;
auto resources = getResourceManager(dst);
data->clear(resources);
return data->setFloat(src, resources);
auto impl = getImpl(dst);
impl.clear();
return impl.setFloat(src);
}
static T fromJson(JsonVariantConst src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return data ? data->template asFloat<T>(resources) : 0;
return getImpl(src).template asFloat<T>();
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data && data->isFloat();
return getImpl(src).isFloat();
}
};
template <>
struct Converter<const char*> : private detail::VariantAttorney {
static void toJson(const char* src, JsonVariant dst) {
detail::VariantData::setString(getData(dst), detail::adaptString(src),
getResourceManager(dst));
static bool toJson(const char* src, JsonVariant dst) {
auto impl = getImpl(dst);
impl.clear();
return impl.setString(detail::adaptString(src));
}
static const char* fromJson(JsonVariantConst src) {
auto data = getData(src);
return data ? data->asString().c_str() : 0;
return getImpl(src).asString().c_str();
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data && data->isString();
return getImpl(src).isString();
}
};
template <>
struct Converter<JsonString> : private detail::VariantAttorney {
static void toJson(JsonString src, JsonVariant dst) {
detail::VariantData::setString(getData(dst), detail::adaptString(src),
getResourceManager(dst));
static bool toJson(JsonString src, JsonVariant dst) {
auto impl = getImpl(dst);
impl.clear();
return impl.setString(detail::adaptString(src));
}
static JsonString fromJson(JsonVariantConst src) {
auto data = getData(src);
return data ? data->asString() : JsonString();
return getImpl(src).asString();
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data && data->isString();
return getImpl(src).isString();
}
};
template <typename T>
inline detail::enable_if_t<detail::IsString<T>::value> convertToJson(
inline detail::enable_if_t<detail::IsString<T>::value, bool> convertToJson(
const T& src, JsonVariant dst) {
using namespace detail;
auto data = VariantAttorney::getData(dst);
auto resources = VariantAttorney::getResourceManager(dst);
detail::VariantData::setString(data, adaptString(src), resources);
auto impl = detail::VariantAttorney::getImpl(dst);
impl.clear();
return impl.setString(detail::adaptString(src));
}
// SerializedValue<std::string>
@@ -201,23 +174,26 @@ inline detail::enable_if_t<detail::IsString<T>::value> convertToJson(
// SerializedValue<const __FlashStringHelper*>
template <typename T>
struct Converter<SerializedValue<T>> : private detail::VariantAttorney {
static void toJson(SerializedValue<T> src, JsonVariant dst) {
detail::VariantData::setRawString(getData(dst), src,
getResourceManager(dst));
static bool toJson(SerializedValue<T> src, JsonVariant dst) {
auto impl = getImpl(dst);
impl.clear();
return impl.setRawString(detail::adaptString(src.data(), src.size()));
}
};
template <>
struct Converter<detail::nullptr_t> : private detail::VariantAttorney {
static void toJson(detail::nullptr_t, JsonVariant dst) {
detail::VariantData::clear(getData(dst), getResourceManager(dst));
static bool toJson(detail::nullptr_t, JsonVariant dst) {
if (dst.isUnbound())
return false;
getImpl(dst).clear();
return true;
}
static detail::nullptr_t fromJson(JsonVariantConst) {
return nullptr;
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data == 0 || data->isNull();
return getImpl(src).isNull();
}
};
@@ -258,17 +234,17 @@ class StringBuilderPrint : public Print {
};
} // namespace detail
inline void convertToJson(const ::Printable& src, JsonVariant dst) {
auto resources = detail::VariantAttorney::getResourceManager(dst);
auto data = detail::VariantAttorney::getData(dst);
if (!resources || !data)
return;
data->clear(resources);
detail::StringBuilderPrint print(resources);
inline bool convertToJson(const ::Printable& src, JsonVariant dst) {
auto impl = detail::VariantAttorney::getImpl(dst);
if (impl.isUnbound())
return false;
impl.clear();
detail::StringBuilderPrint print(impl.resources());
src.printTo(print);
if (print.overflowed())
return;
print.save(data);
return false;
print.save(impl.data());
return true;
}
#endif
@@ -321,85 +297,73 @@ inline bool canConvertFromJson(JsonVariantConst src, const std::string_view&) {
template <>
struct Converter<JsonArrayConst> : private detail::VariantAttorney {
static void toJson(JsonArrayConst src, JsonVariant dst) {
static bool toJson(JsonArrayConst src, JsonVariant dst) {
if (src.isNull())
dst.set(nullptr);
return dst.set(nullptr);
else
dst.to<JsonArray>().set(src);
return dst.to<JsonArray>().set(src);
}
static JsonArrayConst fromJson(JsonVariantConst src) {
auto data = getData(src);
auto array = data ? data->asArray() : nullptr;
return JsonArrayConst(array, getResourceManager(src));
return JsonArrayConst(getImpl(src));
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data && data->isArray();
return getImpl(src).type() == detail::VariantType::Array;
}
};
template <>
struct Converter<JsonArray> : private detail::VariantAttorney {
static void toJson(JsonVariantConst src, JsonVariant dst) {
static bool toJson(JsonVariantConst src, JsonVariant dst) {
if (src.isNull())
dst.set(nullptr);
return dst.set(nullptr);
else
dst.to<JsonArray>().set(src);
return dst.to<JsonArray>().set(src);
}
static JsonArray fromJson(JsonVariant src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return JsonArray(data != 0 ? data->asArray() : 0, resources);
return JsonArray(getImpl(src));
}
static bool checkJson(JsonVariant src) {
auto data = getData(src);
return data && data->isArray();
return getImpl(src).type() == detail::VariantType::Array;
}
};
template <>
struct Converter<JsonObjectConst> : private detail::VariantAttorney {
static void toJson(JsonVariantConst src, JsonVariant dst) {
static bool toJson(JsonVariantConst src, JsonVariant dst) {
if (src.isNull())
dst.set(nullptr);
return dst.set(nullptr);
else
dst.to<JsonObject>().set(src);
return dst.to<JsonObject>().set(src);
}
static JsonObjectConst fromJson(JsonVariantConst src) {
auto data = getData(src);
auto object = data != 0 ? data->asObject() : nullptr;
return JsonObjectConst(object, getResourceManager(src));
return JsonObjectConst(getImpl(src));
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return data && data->isObject();
return getImpl(src).type() == detail::VariantType::Object;
}
};
template <>
struct Converter<JsonObject> : private detail::VariantAttorney {
static void toJson(JsonVariantConst src, JsonVariant dst) {
static bool toJson(JsonVariantConst src, JsonVariant dst) {
if (src.isNull())
dst.set(nullptr);
return dst.set(nullptr);
else
dst.to<JsonObject>().set(src);
return dst.to<JsonObject>().set(src);
}
static JsonObject fromJson(JsonVariant src) {
auto data = getData(src);
auto resources = getResourceManager(src);
return JsonObject(data != 0 ? data->asObject() : 0, resources);
return JsonObject(getImpl(src));
}
static bool checkJson(JsonVariant src) {
auto data = getData(src);
return data && data->isObject();
return getImpl(src).type() == detail::VariantType::Object;
}
};

View File

@@ -16,37 +16,33 @@ class JsonVariant : public detail::VariantRefBase<JsonVariant>,
public:
// Creates an unbound reference.
JsonVariant() : data_(0), resources_(0) {}
JsonVariant() {}
// INTERNAL USE ONLY
JsonVariant(detail::VariantData* data, detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
: impl_(data, resources) {}
// INTERNAL USE ONLY
JsonVariant(detail::VariantImpl impl) : impl_(impl) {}
private:
detail::ResourceManager* getResourceManager() const {
return resources_;
const detail::VariantImpl& getImpl() const {
return impl_;
}
detail::VariantData* getData() const {
return data_;
const detail::VariantImpl& getOrCreateImpl() const {
return impl_;
}
detail::VariantData* getOrCreateData() const {
return data_;
}
detail::VariantData* data_;
detail::ResourceManager* resources_;
mutable detail::VariantImpl impl_;
};
namespace detail {
bool copyVariant(JsonVariant dst, JsonVariantConst src);
}
template <>
struct Converter<JsonVariant> : private detail::VariantAttorney {
static void toJson(JsonVariantConst src, JsonVariant dst) {
copyVariant(dst, src);
static bool toJson(JsonVariantConst src, JsonVariant dst) {
auto impl = getImpl(dst);
impl.clear();
return impl.copyVariant(getImpl(src));
}
static JsonVariant fromJson(JsonVariant src) {
@@ -54,24 +50,24 @@ struct Converter<JsonVariant> : private detail::VariantAttorney {
}
static bool checkJson(JsonVariant src) {
auto data = getData(src);
return !!data;
return !getImpl(src).isUnbound();
}
};
template <>
struct Converter<JsonVariantConst> : private detail::VariantAttorney {
static void toJson(JsonVariantConst src, JsonVariant dst) {
copyVariant(dst, src);
static bool toJson(JsonVariantConst src, JsonVariant dst) {
auto impl = getImpl(dst);
impl.clear();
return impl.copyVariant(getImpl(src));
}
static JsonVariantConst fromJson(JsonVariantConst src) {
return JsonVariantConst(getData(src), getResourceManager(src));
return JsonVariantConst(getImpl(src));
}
static bool checkJson(JsonVariantConst src) {
auto data = getData(src);
return !!data;
return !getImpl(src).isUnbound();
}
};

View File

@@ -35,34 +35,37 @@ class JsonVariantConst : public detail::VariantTag,
public:
// Creates an unbound reference.
JsonVariantConst() : data_(nullptr), resources_(nullptr) {}
JsonVariantConst() {}
// INTERNAL USE ONLY
explicit JsonVariantConst(const detail::VariantData* data,
const detail::ResourceManager* resources)
: data_(data), resources_(resources) {}
explicit JsonVariantConst(detail::VariantData* data,
detail::ResourceManager* resources)
: impl_(data, resources) {}
// INTERNAL USE ONLY
explicit JsonVariantConst(detail::VariantImpl impl) : impl_(impl) {}
// Returns true if the value is null or the reference is unbound.
// https://arduinojson.org/v7/api/jsonvariantconst/isnull/
bool isNull() const {
return detail::VariantData::isNull(data_);
return impl_.isNull();
}
// Returns true if the reference is unbound.
bool isUnbound() const {
return !data_;
return impl_.isUnbound();
}
// Returns the depth (nesting level) of the value.
// https://arduinojson.org/v7/api/jsonvariantconst/nesting/
size_t nesting() const {
return detail::VariantData::nesting(data_, resources_);
return impl_.nesting();
}
// Returns the size of the array or object.
// https://arduinojson.org/v7/api/jsonvariantconst/size/
size_t size() const {
return detail::VariantData::size(data_, resources_);
return impl_.size();
}
// Casts the value to the specified type.
@@ -104,9 +107,7 @@ class JsonVariantConst : public detail::VariantTag,
template <typename T,
detail::enable_if_t<detail::is_integral<T>::value, int> = 0>
JsonVariantConst operator[](T index) const {
return JsonVariantConst(
detail::VariantData::getElement(data_, size_t(index), resources_),
resources_);
return JsonVariantConst(impl_.getElement(size_t(index)), impl_.resources());
}
// Gets object's member with specified key.
@@ -114,9 +115,8 @@ class JsonVariantConst : public detail::VariantTag,
template <typename TString,
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
JsonVariantConst operator[](const TString& key) const {
return JsonVariantConst(detail::VariantData::getMember(
data_, detail::adaptString(key), resources_),
resources_);
return JsonVariantConst(impl_.getMember(detail::adaptString(key)),
impl_.resources());
}
// Gets object's member with specified key.
@@ -126,9 +126,8 @@ class JsonVariantConst : public detail::VariantTag,
!detail::is_const<TChar>::value,
int> = 0>
JsonVariantConst operator[](TChar* key) const {
return JsonVariantConst(detail::VariantData::getMember(
data_, detail::adaptString(key), resources_),
resources_);
return JsonVariantConst(impl_.getMember(detail::adaptString(key)),
impl_.resources());
}
// Gets object's member with specified key or the array's element at the
@@ -149,8 +148,7 @@ class JsonVariantConst : public detail::VariantTag,
detail::enable_if_t<detail::IsString<TString>::value, int> = 0>
ARDUINOJSON_DEPRECATED("use var[key].is<T>() instead")
bool containsKey(const TString& key) const {
return detail::VariantData::getMember(getData(), detail::adaptString(key),
resources_) != 0;
return impl_.getMember(detail::adaptString(key)) != 0;
}
// DEPRECATED: use obj["key"].is<T>() instead
@@ -161,8 +159,7 @@ class JsonVariantConst : public detail::VariantTag,
int> = 0>
ARDUINOJSON_DEPRECATED("use obj[\"key\"].is<T>() instead")
bool containsKey(TChar* key) const {
return detail::VariantData::getMember(getData(), detail::adaptString(key),
resources_) != 0;
return impl_.getMember(detail::adaptString(key)) != 0;
}
// DEPRECATED: use obj[key].is<T>() instead
@@ -181,17 +178,12 @@ class JsonVariantConst : public detail::VariantTag,
}
protected:
const detail::VariantData* getData() const {
return data_;
}
const detail::ResourceManager* getResourceManager() const {
return resources_;
const detail::VariantImpl& getImpl() const {
return impl_;
}
private:
const detail::VariantData* data_;
const detail::ResourceManager* resources_;
mutable detail::VariantImpl impl_;
};
ARDUINOJSON_END_PUBLIC_NAMESPACE

View File

@@ -1,34 +0,0 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2025, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Variant/JsonVariant.hpp>
#include <ArduinoJson/Variant/JsonVariantVisitor.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class JsonVariantCopier {
public:
using result_type = bool;
JsonVariantCopier(JsonVariant dst) : dst_(dst) {}
template <typename T>
bool visit(T src) {
return dst_.set(src);
}
private:
JsonVariant dst_;
};
inline bool copyVariant(JsonVariant dst, JsonVariantConst src) {
if (dst.isUnbound())
return false;
JsonVariantCopier copier(dst);
return accept(src, copier);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -26,15 +26,14 @@ class VisitorAdapter {
public:
using result_type = typename TVisitor::result_type;
VisitorAdapter(TVisitor& visitor, const ResourceManager* resources)
: visitor_(&visitor), resources_(resources) {}
VisitorAdapter(TVisitor& visitor) : visitor_(&visitor) {}
result_type visit(const ArrayData& value) {
return visitor_->visit(JsonArrayConst(&value, resources_));
result_type visitArray(const VariantImpl& array) {
return visitor_->visit(JsonArrayConst(array));
}
result_type visit(const ObjectData& value) {
return visitor_->visit(JsonObjectConst(&value, resources_));
result_type visitObject(const VariantImpl& object) {
return visitor_->visit(JsonObjectConst(object));
}
template <typename T>
@@ -44,18 +43,14 @@ class VisitorAdapter {
private:
TVisitor* visitor_;
const ResourceManager* resources_;
};
template <typename TVisitor>
typename TVisitor::result_type accept(JsonVariantConst variant,
TVisitor& visit) {
auto data = VariantAttorney::getData(variant);
if (!data)
return visit.visit(nullptr);
auto resources = VariantAttorney::getResourceManager(variant);
VisitorAdapter<TVisitor> adapter(visit, resources);
return data->accept(adapter, resources);
auto impl = VariantAttorney::getImpl(variant);
VisitorAdapter<TVisitor> adapter(visit);
return impl.accept(adapter);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -6,7 +6,7 @@
#include <ArduinoJson/Polyfills/attributes.hpp>
#include <ArduinoJson/Polyfills/type_traits.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
#include <ArduinoJson/Variant/VariantImpl.hpp>
#include <ArduinoJson/Variant/VariantTo.hpp>
#include "JsonVariantConst.hpp"
@@ -16,19 +16,13 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class VariantAttorney {
public:
template <typename TClient>
static auto getResourceManager(TClient& client)
-> decltype(client.getResourceManager()) {
return client.getResourceManager();
static VariantImpl getImpl(TClient& client) {
return client.getImpl();
}
template <typename TClient>
static auto getData(TClient& client) -> decltype(client.getData()) {
return client.getData();
}
template <typename TClient>
static VariantData* getOrCreateData(TClient& client) {
return client.getOrCreateData();
static VariantImpl getOrCreateImpl(TClient& client) {
return client.getOrCreateImpl();
}
};

View File

@@ -199,7 +199,7 @@ struct Comparer<
T, enable_if_t<is_convertible<T, ArduinoJson::JsonVariantConst>::value>>
: VariantComparer {
explicit Comparer(const T& value)
: VariantComparer(static_cast<JsonVariantConst>(value)) {}
: VariantComparer(JsonVariantConst(VariantAttorney::getImpl(value))) {}
};
template <typename T>

View File

@@ -6,18 +6,16 @@
#include <stddef.h> // size_t
#include <ArduinoJson/Array/ArrayData.hpp>
#include <ArduinoJson/Numbers/JsonFloat.hpp>
#include <ArduinoJson/Numbers/JsonInteger.hpp>
#include <ArduinoJson/Object/ObjectData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
enum class VariantTypeBits : uint8_t {
OwnedStringBit = 0x01, // 0000 0001
NumberBit = 0x08, // 0000 1000
#if ARDUINOJSON_USE_EXTENSIONS
ExtensionBit = 0x10, // 0001 0000
#if ARDUINOJSON_USE_8_BYTE_POOL
EightByteBit = 0x10, // 0001 0000
#endif
CollectionMask = 0x60,
};
@@ -47,6 +45,18 @@ inline bool operator&(VariantType type, VariantTypeBits bit) {
return (uint8_t(type) & uint8_t(bit)) != 0;
}
struct CollectionData {
SlotId head = NULL_SLOT;
SlotId tail = NULL_SLOT;
// Placement new
static void* operator new(size_t, void* p) noexcept {
return p;
}
static void operator delete(void*, void*) noexcept {}
};
const size_t tinyStringMaxLength = 3;
union VariantContent {
@@ -56,19 +66,14 @@ union VariantContent {
bool asBoolean;
uint32_t asUint32;
int32_t asInt32;
#if ARDUINOJSON_USE_EXTENSIONS
SlotId asSlotId;
#endif
ArrayData asArray;
ObjectData asObject;
CollectionData asCollection;
const char* asLinkedString;
struct StringNode* asOwnedString;
char asTinyString[tinyStringMaxLength + 1];
};
#if ARDUINOJSON_USE_EXTENSIONS
union VariantExtension {
#if ARDUINOJSON_USE_8_BYTE_POOL
union EightByteValue {
# if ARDUINOJSON_USE_LONG_LONG
uint64_t asUint64;
int64_t asInt64;
@@ -77,6 +82,9 @@ union VariantExtension {
double asDouble;
# endif
};
static_assert(sizeof(EightByteValue) == 8,
"sizeof(EightByteValue) must be 8 bytes");
#endif
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -4,12 +4,7 @@
#pragma once
#include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Memory/StringNode.hpp>
#include <ArduinoJson/Misc/SerializedValue.hpp>
#include <ArduinoJson/Numbers/convertNumber.hpp>
#include <ArduinoJson/Strings/JsonString.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <ArduinoJson/Variant/VariantContent.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
@@ -27,12 +22,11 @@ static bool isTinyString(const T& s, size_t n) {
return !containsNul;
}
class VariantData {
VariantContent content_; // must be first to allow cast from array to variant
VariantType type_;
SlotId next_;
struct VariantData {
VariantContent content; // must be first to allow cast from array to variant
VariantType type = VariantType::Null;
SlotId next = NULL_SLOT;
public:
// Placement new
static void* operator new(size_t, void* p) noexcept {
return p;
@@ -40,561 +34,70 @@ class VariantData {
static void operator delete(void*, void*) noexcept {}
VariantData() : type_(VariantType::Null), next_(NULL_SLOT) {}
SlotId next() const {
return next_;
}
void setNext(SlotId slot) {
next_ = slot;
}
template <typename TVisitor>
typename TVisitor::result_type accept(
TVisitor& visit, const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#else
(void)resources; // silence warning
#endif
switch (type_) {
case VariantType::Float:
return visit.visit(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return visit.visit(extension->asDouble);
#endif
case VariantType::Array:
return visit.visit(content_.asArray);
case VariantType::Object:
return visit.visit(content_.asObject);
case VariantType::TinyString:
return visit.visit(JsonString(content_.asTinyString));
case VariantType::LinkedString:
return visit.visit(JsonString(content_.asLinkedString, true));
case VariantType::OwnedString:
return visit.visit(JsonString(content_.asOwnedString->data,
content_.asOwnedString->length));
case VariantType::RawString:
return visit.visit(RawString(content_.asOwnedString->data,
content_.asOwnedString->length));
case VariantType::Int32:
return visit.visit(static_cast<JsonInteger>(content_.asInt32));
case VariantType::Uint32:
return visit.visit(static_cast<JsonUInt>(content_.asUint32));
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Int64:
return visit.visit(extension->asInt64);
case VariantType::Uint64:
return visit.visit(extension->asUint64);
#endif
case VariantType::Boolean:
return visit.visit(content_.asBoolean != 0);
default:
return visit.visit(nullptr);
}
}
template <typename TVisitor>
static typename TVisitor::result_type accept(const VariantData* var,
const ResourceManager* resources,
TVisitor& visit) {
if (var != 0)
return var->accept(visit, resources);
else
return visit.visit(nullptr);
}
VariantData* addElement(ResourceManager* resources) {
auto array = isNull() ? &toArray() : asArray();
return detail::ArrayData::addElement(array, resources);
}
static VariantData* addElement(VariantData* var, ResourceManager* resources) {
if (!var)
return nullptr;
return var->addElement(resources);
}
template <typename T>
bool addValue(const T& value, ResourceManager* resources) {
auto array = isNull() ? &toArray() : asArray();
return detail::ArrayData::addValue(array, value, resources);
}
template <typename T>
static bool addValue(VariantData* var, const T& value,
ResourceManager* resources) {
if (!var)
return false;
return var->addValue(value, resources);
}
bool asBoolean(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#else
(void)resources; // silence warning
#endif
switch (type_) {
case VariantType::Boolean:
return content_.asBoolean;
case VariantType::Uint32:
case VariantType::Int32:
return content_.asUint32 != 0;
case VariantType::Float:
return content_.asFloat != 0;
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return extension->asDouble != 0;
#endif
case VariantType::Null:
return false;
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
case VariantType::Int64:
return extension->asUint64 != 0;
#endif
default:
return true;
}
}
ArrayData* asArray() {
return isArray() ? &content_.asArray : 0;
}
const ArrayData* asArray() const {
return const_cast<VariantData*>(this)->asArray();
}
CollectionData* asCollection() {
return isCollection() ? &content_.asCollection : 0;
}
const CollectionData* asCollection() const {
return const_cast<VariantData*>(this)->asCollection();
}
template <typename T>
T asFloat(const ResourceManager* resources) const {
static_assert(is_floating_point<T>::value, "T must be a floating point");
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#else
(void)resources; // silence warning
#endif
const char* str = nullptr;
switch (type_) {
case VariantType::Boolean:
return static_cast<T>(content_.asBoolean);
case VariantType::Uint32:
return static_cast<T>(content_.asUint32);
case VariantType::Int32:
return static_cast<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return static_cast<T>(extension->asUint64);
case VariantType::Int64:
return static_cast<T>(extension->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString:
str = content_.asLinkedString;
break;
case VariantType::OwnedString:
str = content_.asOwnedString->data;
break;
case VariantType::Float:
return static_cast<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return static_cast<T>(extension->asDouble);
#endif
default:
return 0.0;
}
ARDUINOJSON_ASSERT(str != nullptr);
return parseNumber<T>(str);
}
template <typename T>
T asIntegral(const ResourceManager* resources) const {
static_assert(is_integral<T>::value, "T must be an integral type");
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#else
(void)resources; // silence warning
#endif
const char* str = nullptr;
switch (type_) {
case VariantType::Boolean:
return content_.asBoolean;
case VariantType::Uint32:
return convertNumber<T>(content_.asUint32);
case VariantType::Int32:
return convertNumber<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return convertNumber<T>(extension->asUint64);
case VariantType::Int64:
return convertNumber<T>(extension->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString:
str = content_.asLinkedString;
break;
case VariantType::OwnedString:
str = content_.asOwnedString->data;
break;
case VariantType::Float:
return convertNumber<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return convertNumber<T>(extension->asDouble);
#endif
default:
return 0;
}
ARDUINOJSON_ASSERT(str != nullptr);
return parseNumber<T>(str);
}
ObjectData* asObject() {
return isObject() ? &content_.asObject : 0;
}
const ObjectData* asObject() const {
return const_cast<VariantData*>(this)->asObject();
}
JsonString asRawString() const {
switch (type_) {
case VariantType::RawString:
return JsonString(content_.asOwnedString->data,
content_.asOwnedString->length);
default:
return JsonString();
}
}
JsonString asString() const {
switch (type_) {
case VariantType::TinyString:
return JsonString(content_.asTinyString);
case VariantType::LinkedString:
return JsonString(content_.asLinkedString, true);
case VariantType::OwnedString:
return JsonString(content_.asOwnedString->data,
content_.asOwnedString->length);
default:
return JsonString();
}
}
#if ARDUINOJSON_USE_EXTENSIONS
const VariantExtension* getExtension(const ResourceManager* resources) const;
#endif
VariantData* getElement(size_t index,
const ResourceManager* resources) const {
return ArrayData::getElement(asArray(), index, resources);
}
static VariantData* getElement(const VariantData* var, size_t index,
const ResourceManager* resources) {
return var != 0 ? var->getElement(index, resources) : 0;
}
template <typename TAdaptedString>
VariantData* getMember(TAdaptedString key,
const ResourceManager* resources) const {
return ObjectData::getMember(asObject(), key, resources);
}
template <typename TAdaptedString>
static VariantData* getMember(const VariantData* var, TAdaptedString key,
const ResourceManager* resources) {
if (!var)
return 0;
return var->getMember(key, resources);
}
VariantData* getOrAddElement(size_t index, ResourceManager* resources) {
auto array = isNull() ? &toArray() : asArray();
if (!array)
return nullptr;
return array->getOrAddElement(index, resources);
}
template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources) {
if (key.isNull())
return nullptr;
auto obj = isNull() ? &toObject() : asObject();
if (!obj)
return nullptr;
return obj->getOrAddMember(key, resources);
}
bool isArray() const {
return type_ == VariantType::Array;
}
bool isBoolean() const {
return type_ == VariantType::Boolean;
}
bool isCollection() const {
return type_ & VariantTypeBits::CollectionMask;
}
bool isFloat() const {
return type_ & VariantTypeBits::NumberBit;
}
template <typename T>
bool isInteger(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_LONG_LONG
auto extension = getExtension(resources);
#else
(void)resources; // silence warning
#endif
switch (type_) {
case VariantType::Uint32:
return canConvertNumber<T>(content_.asUint32);
case VariantType::Int32:
return canConvertNumber<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return canConvertNumber<T>(extension->asUint64);
case VariantType::Int64:
return canConvertNumber<T>(extension->asInt64);
#endif
default:
return false;
}
}
bool isNull() const {
return type_ == VariantType::Null;
}
static bool isNull(const VariantData* var) {
if (!var)
return true;
return var->isNull();
}
bool isObject() const {
return type_ == VariantType::Object;
}
bool isString() const {
return type_ == VariantType::LinkedString ||
type_ == VariantType::OwnedString ||
type_ == VariantType::TinyString;
}
size_t nesting(const ResourceManager* resources) const {
auto collection = asCollection();
if (collection)
return collection->nesting(resources);
else
return 0;
}
static size_t nesting(const VariantData* var,
const ResourceManager* resources) {
if (!var)
return 0;
return var->nesting(resources);
}
void removeElement(size_t index, ResourceManager* resources) {
ArrayData::removeElement(asArray(), index, resources);
}
static void removeElement(VariantData* var, size_t index,
ResourceManager* resources) {
if (!var)
return;
var->removeElement(index, resources);
}
template <typename TAdaptedString>
void removeMember(TAdaptedString key, ResourceManager* resources) {
ObjectData::removeMember(asObject(), key, resources);
}
template <typename TAdaptedString>
static void removeMember(VariantData* var, TAdaptedString key,
ResourceManager* resources) {
if (!var)
return;
var->removeMember(key, resources);
}
void reset() { // TODO: remove
type_ = VariantType::Null;
}
void setBoolean(bool value) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
type_ = VariantType::Boolean;
content_.asBoolean = value;
}
template <typename T>
enable_if_t<sizeof(T) == 4, bool> setFloat(T value, ResourceManager*) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
type_ = VariantType::Float;
content_.asFloat = value;
return true;
}
template <typename T>
enable_if_t<sizeof(T) == 8, bool> setFloat(T value, ResourceManager*);
template <typename T>
enable_if_t<is_signed<T>::value, bool> setInteger(T value,
ResourceManager* resources);
template <typename T>
enable_if_t<is_unsigned<T>::value, bool> setInteger(
T value, ResourceManager* resources);
void setRawString(StringNode* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
type_ = VariantType::RawString;
content_.asOwnedString = s;
}
template <typename T>
void setRawString(SerializedValue<T> value, ResourceManager* resources);
template <typename T>
static void setRawString(VariantData* var, SerializedValue<T> value,
ResourceManager* resources) {
if (!var)
return;
var->clear(resources);
var->setRawString(value, resources);
ARDUINOJSON_ASSERT(type == VariantType::Null);
type = VariantType::Boolean;
content.asBoolean = value;
}
template <typename TAdaptedString>
bool setString(TAdaptedString value, ResourceManager* resources);
void setTinyString(const TAdaptedString& s) {
ARDUINOJSON_ASSERT(type == VariantType::Null);
ARDUINOJSON_ASSERT(s.size() <= tinyStringMaxLength);
template <typename TAdaptedString>
static void setString(VariantData* var, TAdaptedString value,
ResourceManager* resources) {
if (!var)
return;
var->clear(resources);
var->setString(value, resources);
}
type = VariantType::TinyString;
void setLinkedString(const char* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
type_ = VariantType::LinkedString;
content_.asLinkedString = s;
}
auto n = uint8_t(s.size());
for (uint8_t i = 0; i < n; i++) {
char c = s[i];
ARDUINOJSON_ASSERT(c != 0); // no NUL in tiny string
content.asTinyString[i] = c;
}
void setTinyString(const char* s, uint8_t n) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
type_ = VariantType::TinyString;
for (uint8_t i = 0; i < n; i++)
content_.asTinyString[i] = s[i];
content_.asTinyString[n] = 0;
content.asTinyString[n] = 0;
}
void setOwnedString(StringNode* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(type == VariantType::Null);
ARDUINOJSON_ASSERT(s);
type_ = VariantType::OwnedString;
content_.asOwnedString = s;
type = VariantType::OwnedString;
content.asOwnedString = s;
}
size_t size(const ResourceManager* resources) const {
if (isObject())
return content_.asObject.size(resources);
if (isArray())
return content_.asArray.size(resources);
return 0;
void setRawString(StringNode* s) {
ARDUINOJSON_ASSERT(type == VariantType::Null);
ARDUINOJSON_ASSERT(s);
type = VariantType::RawString;
content.asOwnedString = s;
}
static size_t size(const VariantData* var, const ResourceManager* resources) {
return var != 0 ? var->size(resources) : 0;
bool isCollection() const {
return type & VariantTypeBits::CollectionMask;
}
ArrayData& toArray() {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
type_ = VariantType::Array;
new (&content_.asArray) ArrayData();
return content_.asArray;
bool isFloat() const {
return type & VariantTypeBits::NumberBit;
}
static ArrayData* toArray(VariantData* var, ResourceManager* resources) {
if (!var)
return 0;
var->clear(resources);
return &var->toArray();
bool isString() const {
return type == VariantType::LinkedString ||
type == VariantType::OwnedString || type == VariantType::TinyString;
}
ObjectData& toObject() {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
type_ = VariantType::Object;
new (&content_.asObject) ObjectData();
return content_.asObject;
VariantData* toArray() {
return toCollection(VariantType::Array);
}
static ObjectData* toObject(VariantData* var, ResourceManager* resources) {
if (!var)
return 0;
var->clear(resources);
return &var->toObject();
VariantData* toCollection(VariantType collectionType) {
ARDUINOJSON_ASSERT(type == VariantType::Null);
ARDUINOJSON_ASSERT(collectionType & VariantTypeBits::CollectionMask);
type = collectionType;
new (&content.asCollection) CollectionData();
return this;
}
VariantType type() const {
return type_;
}
// Release the resources used by this variant and set it to null.
void clear(ResourceManager* resources);
static void clear(VariantData* var, ResourceManager* resources) {
if (!var)
return;
var->clear(resources);
VariantData* toObject() {
return toCollection(VariantType::Object);
}
};

View File

@@ -4,10 +4,8 @@
#pragma once
#include <ArduinoJson/Array/ArrayData.hpp>
#include <ArduinoJson/Numbers/JsonFloat.hpp>
#include <ArduinoJson/Numbers/JsonInteger.hpp>
#include <ArduinoJson/Object/ObjectData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
@@ -15,6 +13,14 @@ template <typename TResult>
struct VariantDataVisitor {
using result_type = TResult;
TResult visitArray(const VariantImpl&) {
return TResult();
}
TResult visitObject(const VariantImpl&) {
return TResult();
}
template <typename T>
TResult visit(const T&) {
return TResult();

View File

@@ -5,142 +5,611 @@
#pragma once
#include <ArduinoJson/Memory/ResourceManager.hpp>
#include <ArduinoJson/Misc/SerializedValue.hpp>
#include <ArduinoJson/Numbers/convertNumber.hpp>
#include <ArduinoJson/Strings/JsonString.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
template <typename T>
inline void VariantData::setRawString(SerializedValue<T> value,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
auto dup = resources->saveString(adaptString(value.data(), value.size()));
if (dup)
setRawString(dup);
}
class CollectionIterator;
template <typename TAdaptedString>
inline bool VariantData::setString(TAdaptedString value,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
class VariantImpl {
VariantData* data_;
ResourceManager* resources_;
if (value.isNull())
return false;
public:
using iterator = CollectionIterator;
if (value.isStatic()) {
setLinkedString(value.data());
return true;
VariantImpl() : data_(nullptr), resources_(nullptr) {}
VariantImpl(VariantData* data, ResourceManager* resources)
: data_(data), resources_(resources) {}
VariantData* data() const {
return data_;
}
if (isTinyString(value, value.size())) {
setTinyString(value.data(), uint8_t(value.size()));
return true;
ResourceManager* resources() const {
return resources_;
}
auto dup = resources->saveString(value);
if (dup) {
setOwnedString(dup);
return true;
}
template <typename TVisitor>
typename TVisitor::result_type accept(TVisitor& visit) const {
if (!data_)
return visit.visit(nullptr);
return false;
}
inline void VariantData::clear(ResourceManager* resources) {
if (type_ & VariantTypeBits::OwnedStringBit)
resources->dereferenceString(content_.asOwnedString->data);
#if ARDUINOJSON_USE_EXTENSIONS
if (type_ & VariantTypeBits::ExtensionBit)
resources->freeExtension(content_.asSlotId);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte();
#endif
auto collection = asCollection();
if (collection)
collection->clear(resources);
type_ = VariantType::Null;
}
#if ARDUINOJSON_USE_EXTENSIONS
inline const VariantExtension* VariantData::getExtension(
const ResourceManager* resources) const {
return type_ & VariantTypeBits::ExtensionBit
? resources->getExtension(content_.asSlotId)
: nullptr;
}
#endif
template <typename T>
enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat(
T value, ResourceManager* resources) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
(void)resources; // silence warning
float valueAsFloat = static_cast<float>(value);
switch (data_->type) {
case VariantType::Float:
return visit.visit(data_->content.asFloat);
#if ARDUINOJSON_USE_DOUBLE
if (value == valueAsFloat) {
type_ = VariantType::Float;
content_.asFloat = valueAsFloat;
} else {
auto extension = resources->allocExtension();
if (!extension)
return false;
type_ = VariantType::Double;
content_.asSlotId = extension.id();
extension->asDouble = value;
case VariantType::Double:
return visit.visit(eightByteValue->asDouble);
#endif
case VariantType::Array:
return visit.visitArray(VariantImpl(data_, resources_));
case VariantType::Object:
return visit.visitObject(VariantImpl(data_, resources_));
case VariantType::TinyString:
return visit.visit(JsonString(data_->content.asTinyString));
case VariantType::LinkedString:
return visit.visit(JsonString(asLinkedString(), true));
case VariantType::OwnedString: {
auto s = asOwnedString();
return visit.visit(JsonString(s->data, s->length));
}
case VariantType::RawString: {
auto s = asOwnedString();
return visit.visit(RawString(s->data, s->length));
}
case VariantType::Int32:
return visit.visit(static_cast<JsonInteger>(data_->content.asInt32));
case VariantType::Uint32:
return visit.visit(static_cast<JsonUInt>(data_->content.asUint32));
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Int64:
return visit.visit(eightByteValue->asInt64);
case VariantType::Uint64:
return visit.visit(eightByteValue->asUint64);
#endif
case VariantType::Boolean:
return visit.visit(data_->content.asBoolean != 0);
default:
return visit.visit(nullptr);
}
}
VariantData* addElement();
void addElement(Slot<VariantData> slot);
bool asBoolean() const {
if (!data_)
return false;
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte();
#endif
switch (data_->type) {
case VariantType::Boolean:
return data_->content.asBoolean;
case VariantType::Uint32:
case VariantType::Int32:
return data_->content.asUint32 != 0;
case VariantType::Float:
return data_->content.asFloat != 0;
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return eightByteValue->asDouble != 0;
#endif
case VariantType::Null:
return false;
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
case VariantType::Int64:
return eightByteValue->asUint64 != 0;
#endif
default:
return true;
}
}
template <typename T>
T asFloat() const {
if (!data_)
return 0.0;
static_assert(is_floating_point<T>::value, "T must be a floating point");
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte();
#endif
const char* str = nullptr;
switch (data_->type) {
case VariantType::Boolean:
return static_cast<T>(data_->content.asBoolean);
case VariantType::Uint32:
return static_cast<T>(data_->content.asUint32);
case VariantType::Int32:
return static_cast<T>(data_->content.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return static_cast<T>(eightByteValue->asUint64);
case VariantType::Int64:
return static_cast<T>(eightByteValue->asInt64);
#endif
case VariantType::TinyString:
str = data_->content.asTinyString;
break;
case VariantType::LinkedString:
str = asLinkedString();
break;
case VariantType::OwnedString:
str = asOwnedString()->data;
break;
case VariantType::Float:
return static_cast<T>(data_->content.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return static_cast<T>(eightByteValue->asDouble);
#endif
default:
return 0.0;
}
ARDUINOJSON_ASSERT(str != nullptr);
return parseNumber<T>(str);
}
template <typename T>
T asIntegral() const {
if (!data_)
return 0;
static_assert(is_integral<T>::value, "T must be an integral type");
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte();
#endif
const char* str = nullptr;
switch (data_->type) {
case VariantType::Boolean:
return data_->content.asBoolean;
case VariantType::Uint32:
return convertNumber<T>(data_->content.asUint32);
case VariantType::Int32:
return convertNumber<T>(data_->content.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return convertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64:
return convertNumber<T>(eightByteValue->asInt64);
#endif
case VariantType::TinyString:
str = data_->content.asTinyString;
break;
case VariantType::LinkedString:
str = asLinkedString();
break;
case VariantType::OwnedString:
str = asOwnedString()->data;
break;
case VariantType::Float:
return convertNumber<T>(data_->content.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return convertNumber<T>(eightByteValue->asDouble);
#endif
default:
return 0;
}
ARDUINOJSON_ASSERT(str != nullptr);
return parseNumber<T>(str);
}
JsonString asRawString() const {
switch (type()) {
case VariantType::RawString: {
auto s = asOwnedString();
return JsonString(s->data, s->length);
}
default:
return JsonString();
}
}
const char* asLinkedString() const {
ARDUINOJSON_ASSERT(type() == VariantType::LinkedString);
return resources_->getStaticString(data_->content.asSlotId);
}
JsonString asString() const {
switch (type()) {
case VariantType::TinyString:
return JsonString(data_->content.asTinyString);
case VariantType::LinkedString:
return JsonString(asLinkedString(), true);
case VariantType::OwnedString: {
auto s = asOwnedString();
return JsonString(s->data, s->length);
}
default:
return JsonString();
}
}
StringNode* asOwnedString() const {
ARDUINOJSON_ASSERT(type() & VariantTypeBits::OwnedStringBit);
return data_->content.asOwnedString;
}
#if ARDUINOJSON_USE_8_BYTE_POOL
const EightByteValue* getEightByte() const {
return type() & VariantTypeBits::EightByteBit
? resources_->getEightByte(data_->content.asSlotId)
: 0;
}
#endif
iterator createIterator() const;
VariantData* getElement(size_t index) const;
VariantData* getOrAddElement(size_t index);
void addMember(Slot<VariantData> key, Slot<VariantData> value);
template <typename TAdaptedString>
VariantData* addMember(TAdaptedString key);
template <typename TAdaptedString>
VariantData* getMember(TAdaptedString key) const;
template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key);
bool isArray() const {
return type() == VariantType::Array;
}
bool isBoolean() const {
return type() == VariantType::Boolean;
}
bool isFloat() const {
return data_ && data_->isFloat();
}
template <typename T>
bool isInteger() const {
if (!data_)
return false;
#if ARDUINOJSON_USE_LONG_LONG
auto eightByteValue = getEightByte();
#endif
switch (data_->type) {
case VariantType::Uint32:
return canConvertNumber<T>(data_->content.asUint32);
case VariantType::Int32:
return canConvertNumber<T>(data_->content.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return canConvertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64:
return canConvertNumber<T>(eightByteValue->asInt64);
#endif
default:
return false;
}
}
bool isUnbound() const {
return !data_;
}
bool isNull() const {
return type() == VariantType::Null;
}
bool isObject() const {
return type() == VariantType::Object;
}
bool isString() const {
return data_ && data_->isString();
}
size_t nesting() const;
void removeElement(iterator it);
void removeElement(size_t index);
template <typename TAdaptedString>
void removeMember(TAdaptedString key);
void removeMember(iterator it);
bool copyVariant(const VariantImpl& src) {
switch (src.type()) {
case VariantType::Null:
return true;
case VariantType::Array:
return copyArray(src);
case VariantType::Object:
return copyObject(src);
case VariantType::RawString:
return setRawString(adaptString(src.asRawString()));
case VariantType::LinkedString:
return setLinkedString(src.asLinkedString());
case VariantType::OwnedString:
return setOwnedString(adaptString(src.asString()));
default:
data_->content = src.data_->content;
data_->type = src.data_->type;
return true;
}
}
bool copyArray(const VariantImpl& src);
bool copyObject(const VariantImpl& src);
bool setBoolean(bool value) {
if (!data_)
return false;
data_->setBoolean(value);
return true;
}
template <typename T>
enable_if_t<sizeof(T) == 4, bool> setFloat(T value) {
ARDUINOJSON_ASSERT(type() == VariantType::Null); // must call clear() first
if (!data_)
return false;
data_->type = VariantType::Float;
data_->content.asFloat = value;
return true;
}
template <typename T>
enable_if_t<sizeof(T) == 8, bool> setFloat(T value) {
ARDUINOJSON_ASSERT(isNull()); // must call clear() first
if (!data_)
return false;
float valueAsFloat = static_cast<float>(value);
#if ARDUINOJSON_USE_DOUBLE
if (value == valueAsFloat) {
data_->type = VariantType::Float;
data_->content.asFloat = valueAsFloat;
} else {
auto slot = resources_->allocEightByte();
if (!slot)
return false;
data_->type = VariantType::Double;
data_->content.asSlotId = slot.id();
slot->asDouble = value;
}
#else
type_ = VariantType::Float;
content_.asFloat = valueAsFloat;
data_->type = VariantType::Float;
data_->content.asFloat = valueAsFloat;
#endif
return true;
}
template <typename T>
enable_if_t<is_signed<T>::value, bool> VariantData::setInteger(
T value, ResourceManager* resources) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
(void)resources; // silence warning
if (canConvertNumber<int32_t>(value)) {
type_ = VariantType::Int32;
content_.asInt32 = static_cast<int32_t>(value);
return true;
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto extension = resources->allocExtension();
if (!extension)
template <typename T>
enable_if_t<is_signed<T>::value, bool> setInteger(T value) {
ARDUINOJSON_ASSERT(isNull()); // must call clear() first
if (!data_)
return false;
type_ = VariantType::Int64;
content_.asSlotId = extension.id();
extension->asInt64 = value;
}
#endif
return true;
}
template <typename T>
enable_if_t<is_unsigned<T>::value, bool> VariantData::setInteger(
T value, ResourceManager* resources) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
(void)resources; // silence warning
if (canConvertNumber<uint32_t>(value)) {
type_ = VariantType::Uint32;
content_.asUint32 = static_cast<uint32_t>(value);
}
if (canConvertNumber<int32_t>(value)) {
data_->type = VariantType::Int32;
data_->content.asInt32 = static_cast<int32_t>(value);
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto extension = resources->allocExtension();
if (!extension)
return false;
type_ = VariantType::Uint64;
content_.asSlotId = extension.id();
extension->asUint64 = value;
}
else {
auto slot = resources_->allocEightByte();
if (!slot)
return false;
data_->type = VariantType::Int64;
data_->content.asSlotId = slot.id();
slot->asInt64 = value;
}
#endif
return true;
}
return true;
}
template <typename T>
enable_if_t<is_unsigned<T>::value, bool> setInteger(T value) {
ARDUINOJSON_ASSERT(isNull()); // must call clear() first
if (!data_)
return false;
if (canConvertNumber<uint32_t>(value)) {
data_->type = VariantType::Uint32;
data_->content.asUint32 = static_cast<uint32_t>(value);
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto slot = resources_->allocEightByte();
if (!slot)
return false;
data_->type = VariantType::Uint64;
data_->content.asSlotId = slot.id();
slot->asUint64 = value;
}
#endif
return true;
}
template <typename TAdaptedString>
bool setRawString(TAdaptedString value) {
if (!data_)
return false;
auto dup = resources_->saveString(value);
if (!dup)
return false;
data_->setRawString(dup);
return true;
}
template <typename TAdaptedString>
bool setString(TAdaptedString value) {
ARDUINOJSON_ASSERT(isNull()); // must call clear() first
if (!data_)
return false;
if (value.isNull())
return true;
if (value.isStatic())
return setLinkedString(value.data());
if (isTinyString(value, value.size())) {
data_->setTinyString(value);
return true;
}
return setOwnedString(value);
}
template <typename TAdaptedString>
bool setOwnedString(TAdaptedString value) {
auto dup = resources_->saveString(value);
if (!dup)
return false;
data_->setOwnedString(dup);
return true;
}
bool setLinkedString(const char* s) {
ARDUINOJSON_ASSERT(isNull()); // must call clear() first
ARDUINOJSON_ASSERT(s);
auto slotId = resources_->saveStaticString(s);
if (slotId == NULL_SLOT)
return false;
data_->type = VariantType::LinkedString;
data_->content.asSlotId = slotId;
return true;
}
void toArrayIfNull() {
if (data_ && data_->type == VariantType::Null)
data_->toArray();
}
void toObjectIfNull() {
if (data_ && data_->type == VariantType::Null)
data_->toObject();
}
void empty() {
auto coll = getCollectionData();
auto next = coll->head;
while (next != NULL_SLOT) {
auto currId = next;
auto slot = getVariant(next);
next = slot->next;
freeVariant({slot, currId});
}
coll->head = NULL_SLOT;
coll->tail = NULL_SLOT;
}
size_t size() const;
VariantType type() const {
return data_ ? data_->type : VariantType::Null;
}
// Release the resources used by this variant and set it to null.
void clear() {
if (!data_)
return;
if (data_->type & VariantTypeBits::OwnedStringBit)
resources_->dereferenceString(asOwnedString());
#if ARDUINOJSON_USE_8_BYTE_POOL
if (data_->type & VariantTypeBits::EightByteBit)
resources_->freeEightByte(data_->content.asSlotId);
#endif
if (data_->type & VariantTypeBits::CollectionMask)
empty();
data_->type = VariantType::Null;
}
private:
template <typename TAdaptedString>
iterator findKey(TAdaptedString key) const;
iterator at(size_t index) const;
void removeOne(iterator it);
void removePair(iterator it);
VariantData* getVariant(SlotId id) const {
ARDUINOJSON_ASSERT(resources_ != nullptr);
return resources_->getVariant(id);
}
void freeVariant(Slot<VariantData> slot) {
ARDUINOJSON_ASSERT(resources_ != nullptr);
VariantImpl(slot.ptr(), resources_).clear();
resources_->freeVariant(slot);
}
Slot<VariantData> allocVariant() {
ARDUINOJSON_ASSERT(resources_ != nullptr);
return resources_->allocVariant();
}
Slot<VariantData> getPreviousSlot(VariantData*) const;
CollectionData* getCollectionData() const {
ARDUINOJSON_ASSERT(data_ != nullptr);
ARDUINOJSON_ASSERT(data_->isCollection());
return &data_->content.asCollection;
}
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -29,18 +29,18 @@ class VariantRefBase : public VariantTag {
// Sets the value to null.
// https://arduinojson.org/v7/api/jsonvariant/clear/
void clear() const {
VariantData::clear(getOrCreateData(), getResourceManager());
getOrCreateImpl().clear();
}
// Returns true if the value is null or the reference is unbound.
// https://arduinojson.org/v7/api/jsonvariant/isnull/
bool isNull() const {
return VariantData::isNull(getData());
return getImpl().isNull();
}
// Returns true if the reference is unbound.
bool isUnbound() const {
return !getData();
return getImpl().isUnbound();
}
// Casts the value to the specified type.
@@ -93,13 +93,13 @@ class VariantRefBase : public VariantTag {
// Returns the size of the array or object.
// https://arduinojson.org/v7/api/jsonvariant/size/
size_t size() const {
return VariantData::size(getData(), getResourceManager());
return getImpl().size();
}
// Returns the depth (nesting level) of the value.
// https://arduinojson.org/v7/api/jsonvariant/nesting/
size_t nesting() const {
return VariantData::nesting(getData(), getResourceManager());
return getImpl().nesting();
}
// Appends a new (empty) element to the array.
@@ -120,38 +120,34 @@ class VariantRefBase : public VariantTag {
// https://arduinojson.org/v7/api/jsonvariant/add/
template <typename T>
bool add(const T& value) const {
return detail::VariantData::addValue(getOrCreateData(), value,
getResourceManager());
return getOrCreateArray().add(value);
}
// Appends a value to the array.
// https://arduinojson.org/v7/api/jsonvariant/add/
template <typename T, enable_if_t<!is_const<T>::value, int> = 0>
bool add(T* value) const {
return detail::VariantData::addValue(getOrCreateData(), value,
getResourceManager());
return getOrCreateArray().add(value);
}
// Removes an element of the array.
// https://arduinojson.org/v7/api/jsonvariant/remove/
void remove(size_t index) const {
VariantData::removeElement(getData(), index, getResourceManager());
getImpl().removeElement(index);
}
// Removes a member of the object.
// https://arduinojson.org/v7/api/jsonvariant/remove/
template <typename TChar, enable_if_t<IsString<TChar*>::value, int> = 0>
void remove(TChar* key) const {
VariantData::removeMember(getData(), adaptString(key),
getResourceManager());
getImpl().removeMember(adaptString(key));
}
// Removes a member of the object.
// https://arduinojson.org/v7/api/jsonvariant/remove/
template <typename TString, enable_if_t<IsString<TString>::value, int> = 0>
void remove(const TString& key) const {
VariantData::removeMember(getData(), adaptString(key),
getResourceManager());
getImpl().removeMember(adaptString(key));
}
// Removes a member of the object or an element of the array.
@@ -263,22 +259,20 @@ class VariantRefBase : public VariantTag {
return static_cast<const TDerived&>(*this);
}
ResourceManager* getResourceManager() const {
return VariantAttorney::getResourceManager(derived());
VariantImpl getImpl() const {
return VariantAttorney::getImpl(derived());
}
VariantData* getData() const {
return VariantAttorney::getData(derived());
VariantImpl getOrCreateImpl() const {
return VariantAttorney::getOrCreateImpl(derived());
}
VariantData* getOrCreateData() const {
return VariantAttorney::getOrCreateData(derived());
}
JsonArray getOrCreateArray() const;
FORCE_INLINE ArduinoJson::JsonVariant getVariant() const;
FORCE_INLINE ArduinoJson::JsonVariantConst getVariantConst() const {
return ArduinoJson::JsonVariantConst(getData(), getResourceManager());
return ArduinoJson::JsonVariantConst(getImpl());
}
template <typename T>
@@ -306,8 +300,6 @@ class VariantRefBase : public VariantTag {
template <typename TConverter, typename T>
bool doSet(const T& value, true_type) const;
ArduinoJson::JsonVariant getOrCreateVariant() const;
};
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -61,31 +61,27 @@ inline JsonObject VariantRefBase<TDerived>::createNestedObject(
}
template <typename TDerived>
inline void convertToJson(const VariantRefBase<TDerived>& src,
inline bool convertToJson(const VariantRefBase<TDerived>& src,
JsonVariant dst) {
dst.set(src.template as<JsonVariantConst>());
return dst.set(src.template as<JsonVariantConst>());
}
template <typename TDerived>
template <typename T, enable_if_t<is_same<T, JsonVariant>::value, int>>
inline T VariantRefBase<TDerived>::add() const {
return JsonVariant(
detail::VariantData::addElement(getOrCreateData(), getResourceManager()),
getResourceManager());
return getOrCreateArray().template add<T>();
}
template <typename TDerived>
template <typename TString, enable_if_t<IsString<TString>::value, int>>
inline bool VariantRefBase<TDerived>::containsKey(const TString& key) const {
return VariantData::getMember(getData(), adaptString(key),
getResourceManager()) != 0;
return getImpl().getMember(adaptString(key)) != 0;
}
template <typename TDerived>
template <typename TChar, enable_if_t<IsString<TChar*>::value, int>>
inline bool VariantRefBase<TDerived>::containsKey(TChar* key) const {
return VariantData::getMember(getData(), adaptString(key),
getResourceManager()) != 0;
return getImpl().getMember(adaptString(key)) != 0;
}
template <typename TDerived>
@@ -96,12 +92,7 @@ inline bool VariantRefBase<TDerived>::containsKey(const TVariant& key) const {
template <typename TDerived>
inline JsonVariant VariantRefBase<TDerived>::getVariant() const {
return JsonVariant(getData(), getResourceManager());
}
template <typename TDerived>
inline JsonVariant VariantRefBase<TDerived>::getOrCreateVariant() const {
return JsonVariant(getOrCreateData(), getResourceManager());
return JsonVariant(getImpl());
}
template <typename TDerived>
@@ -136,40 +127,54 @@ VariantRefBase<TDerived>::operator[](const TString& key) const {
template <typename TDerived>
template <typename TConverter, typename T>
inline bool VariantRefBase<TDerived>::doSet(const T& value, false_type) const {
TConverter::toJson(value, getOrCreateVariant());
auto resources = getResourceManager();
auto impl = getOrCreateImpl();
TConverter::toJson(value, JsonVariant(impl));
auto resources = impl.resources();
return resources && !resources->overflowed();
}
template <typename TDerived>
template <typename TConverter, typename T>
inline bool VariantRefBase<TDerived>::doSet(const T& value, true_type) const {
return TConverter::toJson(value, getOrCreateVariant());
auto impl = getOrCreateImpl();
return TConverter::toJson(value, JsonVariant(impl));
}
template <typename TDerived>
inline JsonArray VariantRefBase<TDerived>::getOrCreateArray() const {
auto impl = getOrCreateImpl();
impl.toArrayIfNull();
return JsonArray(impl);
}
template <typename TDerived>
template <typename T, enable_if_t<is_same<T, JsonArray>::value, int>>
inline JsonArray VariantRefBase<TDerived>::to() const {
return JsonArray(
VariantData::toArray(getOrCreateData(), getResourceManager()),
getResourceManager());
auto impl = getOrCreateImpl();
if (impl.isUnbound())
return JsonArray();
impl.clear();
impl.data()->toArray();
return JsonArray(impl);
}
template <typename TDerived>
template <typename T, enable_if_t<is_same<T, JsonObject>::value, int>>
JsonObject VariantRefBase<TDerived>::to() const {
return JsonObject(
VariantData::toObject(getOrCreateData(), getResourceManager()),
getResourceManager());
auto impl = getOrCreateImpl();
if (impl.isUnbound())
return JsonObject();
impl.clear();
impl.data()->toObject();
return JsonObject(impl);
}
template <typename TDerived>
template <typename T, enable_if_t<is_same<T, JsonVariant>::value, int>>
JsonVariant VariantRefBase<TDerived>::to() const {
auto data = getOrCreateData();
auto resources = getResourceManager();
detail::VariantData::clear(data, resources);
return JsonVariant(data, resources);
auto impl = getOrCreateImpl();
impl.clear();
return JsonVariant(impl);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@@ -4,8 +4,8 @@
#pragma once
#define ARDUINOJSON_VERSION "7.4.0"
#define ARDUINOJSON_VERSION "7.4.2"
#define ARDUINOJSON_VERSION_MAJOR 7
#define ARDUINOJSON_VERSION_MINOR 4
#define ARDUINOJSON_VERSION_REVISION 0
#define ARDUINOJSON_VERSION_MACRO V740
#define ARDUINOJSON_VERSION_REVISION 2
#define ARDUINOJSON_VERSION_MACRO V742