Compare commits

..

14 Commits

Author SHA1 Message Date
53f858f55c Remove SlotData 2025-06-02 15:57:34 +02:00
6cdd59034d Clang-format 2025-05-31 18:35:36 +02:00
6ccac18fd4 Fix comments 2025-05-31 18:34:07 +02:00
3f6fff7d0c Fix JsonVariantTests 2025-05-31 16:29:46 +00:00
cc2fea45cd All tests pass 2025-05-31 16:47:03 +02:00
4159af66eb JsonDeserializerTests pass 2025-05-31 16:41:09 +02:00
05942b620a JsonVariantTests pass 2025-05-31 16:38:36 +02:00
23c73b061e JsonArrayTests pass 2025-05-31 16:37:08 +02:00
c2e756d942 ResourceManagerTests pass 2025-05-31 16:31:31 +02:00
94aacf873e Replace extension with eight-byte values 2025-05-31 16:20:08 +02:00
bd2dccda0e Store static strings in a dedicated pool
Because a slot id is smaller than a pointer, this change will ultimately allow reducing the slot size.
2025-05-21 11:09:44 +02:00
411424b74e CI: upgrade clang-tidy 2025-05-20 20:55:01 +02:00
5e5c287978 CI: upgrade Clang versions 2025-05-20 18:24:46 +02:00
377cf63075 CI: upgrade runner to ubuntu-22.04 2025-05-20 14:48:42 +02:00
49 changed files with 459 additions and 255 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: |
@ -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,43 @@
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.1 (2025-04-11)
------

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

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(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*") {

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

@ -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

@ -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"
@ -22,7 +22,7 @@ TEST_CASE("StringBuffer") {
sb.save(&variant);
REQUIRE(variant.type() == VariantType::TinyString);
REQUIRE(variant.asString() == "hi!");
REQUIRE(variant.asString(&resources) == "hi!");
}
SECTION("Tiny string can't contain NUL") {
@ -32,7 +32,7 @@ TEST_CASE("StringBuffer") {
REQUIRE(variant.type() == VariantType::OwnedString);
auto str = variant.asString();
auto str = variant.asString(&resources);
REQUIRE(str.size() == 3);
REQUIRE(str.c_str()[0] == 'a');
REQUIRE(str.c_str()[1] == 0);
@ -45,6 +45,6 @@ TEST_CASE("StringBuffer") {
sb.save(&variant);
REQUIRE(variant.type() == VariantType::OwnedString);
REQUIRE(variant.asString() == "alfa");
REQUIRE(variant.asString(&resources) == "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"
@ -46,7 +46,7 @@ TEST_CASE("StringBuilder") {
REQUIRE(resources.overflowed() == false);
REQUIRE(data.type() == VariantType::TinyString);
REQUIRE(data.asString() == "url");
REQUIRE(data.asString(&resources) == "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(s1.asString(&resources) == "hello");
REQUIRE(s2.asString(&resources) == "world");
REQUIRE(+s1.asString(&resources).c_str() ==
+s3.asString(&resources).c_str()); // same address
REQUIRE(spy.log() ==
AllocatorLog{
@ -152,9 +153,10 @@ 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(s1.asString(&resources) == "hello world");
REQUIRE(s2.asString(&resources) == "hello");
REQUIRE(+s2.asString(&resources).c_str() !=
+s1.asString(&resources).c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{
@ -169,9 +171,10 @@ 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(s1.asString(&resources) == "hello world");
REQUIRE(s2.asString(&resources) == "worl");
REQUIRE(s2.asString(&resources).c_str() !=
s1.asString(&resources).c_str()); // different address
REQUIRE(spy.log() ==
AllocatorLog{

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

@ -73,7 +73,7 @@ inline bool ArrayData::addValue(const T& value, ResourceManager* resources) {
// 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

@ -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

@ -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

@ -18,22 +18,17 @@ class VariantData;
class VariantWithId;
class ResourceManager {
union SlotData {
VariantData variant;
#if ARDUINOJSON_USE_EXTENSIONS
VariantExtension extension;
#endif
};
public:
constexpr static size_t slotSize = sizeof(SlotData);
ResourceManager(Allocator* allocator = DefaultAllocator::instance())
: allocator_(allocator), overflowed_(false) {}
~ResourceManager() {
stringPool_.clear(allocator_);
variantPools_.clear(allocator_);
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_);
}
@ -62,10 +61,10 @@ class ResourceManager {
void freeVariant(Slot<VariantData> slot);
VariantData* getVariant(SlotId id) const;
#if ARDUINOJSON_USE_EXTENSIONS
Slot<VariantExtension> allocExtension();
void freeExtension(SlotId slot);
VariantExtension* getExtension(SlotId id) const;
#if ARDUINOJSON_USE_8_BYTE_POOL
Slot<EightByteValue> allocEightByte();
void freeEightByte(SlotId slot);
EightByteValue* getEightByte(SlotId id) const;
#endif
template <typename TAdaptedString>
@ -111,21 +110,51 @@ class ResourceManager {
stringPool_.dereference(s, allocator_);
}
SlotId saveStaticString(const char* s) {
auto existingSlotId = staticStringsPools_.find(s);
if (existingSlotId != NULL_SLOT)
return existingSlotId;
auto slot = staticStringsPools_.allocSlot(allocator_);
if (slot)
*slot = s;
else
overflowed_ = true;
return slot.id();
}
const char* getStaticString(SlotId id) const {
return *staticStringsPools_.getSlot(id);
}
void clear() {
variantPools_.clear(allocator_);
overflowed_ = false;
variantPools_.clear(allocator_);
stringPool_.clear(allocator_);
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

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

View File

@ -18,7 +18,7 @@ class JsonPair {
JsonPair(detail::ObjectData::iterator iterator,
detail::ResourceManager* resources) {
if (!iterator.done()) {
key_ = iterator->asString();
key_ = iterator->asString(resources);
iterator.next(resources);
value_ = JsonVariant(iterator.data(), resources);
}
@ -46,7 +46,7 @@ class JsonPairConst {
JsonPairConst(detail::ObjectData::iterator iterator,
const detail::ResourceManager* resources) {
if (!iterator.done()) {
key_ = iterator->asString();
key_ = iterator->asString(resources);
iterator.next(resources);
value_ = JsonVariantConst(iterator.data(), resources);
}

View File

@ -36,7 +36,7 @@ inline ObjectData::iterator ObjectData::findKey(
return iterator();
bool isKey = true;
for (auto it = createIterator(resources); !it.done(); it.next(resources)) {
if (isKey && stringEquals(key, adaptString(it->asString())))
if (isKey && stringEquals(key, adaptString(it->asString(resources))))
return it;
isKey = !isKey;
}
@ -86,7 +86,7 @@ inline VariantData* ObjectData::addPair(VariantData** value,
// 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

@ -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

@ -160,7 +160,7 @@ struct Converter<const char*> : private detail::VariantAttorney {
static const char* fromJson(JsonVariantConst src) {
auto data = getData(src);
return data ? data->asString().c_str() : 0;
return data ? data->asString(getResourceManager(src)).c_str() : 0;
}
static bool checkJson(JsonVariantConst src) {
@ -178,7 +178,7 @@ struct Converter<JsonString> : private detail::VariantAttorney {
static JsonString fromJson(JsonVariantConst src) {
auto data = getData(src);
return data ? data->asString() : JsonString();
return data ? data->asString(getResourceManager(src)) : JsonString();
}
static bool checkJson(JsonVariantConst src) {

View File

@ -16,9 +16,7 @@ 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
#endif
EightByteBit = 0x10, // 0001 0000
CollectionMask = 0x60,
};
@ -56,19 +54,16 @@ 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 +72,9 @@ union VariantExtension {
double asDouble;
# endif
};
static_assert(sizeof(EightByteValue) == 8,
"sizeof(EightByteValue) must be 8 bytes");
#endif
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -53,8 +53,8 @@ class VariantData {
template <typename TVisitor>
typename TVisitor::result_type accept(
TVisitor& visit, const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -64,7 +64,7 @@ class VariantData {
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return visit.visit(extension->asDouble);
return visit.visit(eightByteValue->asDouble);
#endif
case VariantType::Array:
@ -77,7 +77,7 @@ class VariantData {
return visit.visit(JsonString(content_.asTinyString));
case VariantType::LinkedString:
return visit.visit(JsonString(content_.asLinkedString, true));
return visit.visit(JsonString(asLinkedString(resources), true));
case VariantType::OwnedString:
return visit.visit(JsonString(content_.asOwnedString->data,
@ -95,10 +95,10 @@ class VariantData {
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Int64:
return visit.visit(extension->asInt64);
return visit.visit(eightByteValue->asInt64);
case VariantType::Uint64:
return visit.visit(extension->asUint64);
return visit.visit(eightByteValue->asUint64);
#endif
case VariantType::Boolean:
@ -145,8 +145,8 @@ class VariantData {
}
bool asBoolean(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_EXTENSIONS
auto extension = getExtension(resources);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -160,14 +160,14 @@ class VariantData {
return content_.asFloat != 0;
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return extension->asDouble != 0;
return eightByteValue->asDouble != 0;
#endif
case VariantType::Null:
return false;
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
case VariantType::Int64:
return extension->asUint64 != 0;
return eightByteValue->asUint64 != 0;
#endif
default:
return true;
@ -193,8 +193,8 @@ class VariantData {
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);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -208,15 +208,15 @@ class VariantData {
return static_cast<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return static_cast<T>(extension->asUint64);
return static_cast<T>(eightByteValue->asUint64);
case VariantType::Int64:
return static_cast<T>(extension->asInt64);
return static_cast<T>(eightByteValue->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString:
str = content_.asLinkedString;
str = asLinkedString(resources);
break;
case VariantType::OwnedString:
str = content_.asOwnedString->data;
@ -225,7 +225,7 @@ class VariantData {
return static_cast<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return static_cast<T>(extension->asDouble);
return static_cast<T>(eightByteValue->asDouble);
#endif
default:
return 0.0;
@ -238,8 +238,8 @@ class VariantData {
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);
#if ARDUINOJSON_USE_8_BYTE_POOL
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -253,15 +253,15 @@ class VariantData {
return convertNumber<T>(content_.asInt32);
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return convertNumber<T>(extension->asUint64);
return convertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64:
return convertNumber<T>(extension->asInt64);
return convertNumber<T>(eightByteValue->asInt64);
#endif
case VariantType::TinyString:
str = content_.asTinyString;
break;
case VariantType::LinkedString:
str = content_.asLinkedString;
str = asLinkedString(resources);
break;
case VariantType::OwnedString:
str = content_.asOwnedString->data;
@ -270,7 +270,7 @@ class VariantData {
return convertNumber<T>(content_.asFloat);
#if ARDUINOJSON_USE_DOUBLE
case VariantType::Double:
return convertNumber<T>(extension->asDouble);
return convertNumber<T>(eightByteValue->asDouble);
#endif
default:
return 0;
@ -298,12 +298,14 @@ class VariantData {
}
}
JsonString asString() const {
const char* asLinkedString(const ResourceManager* resources) const;
JsonString asString(const ResourceManager* resources) const {
switch (type_) {
case VariantType::TinyString:
return JsonString(content_.asTinyString);
case VariantType::LinkedString:
return JsonString(content_.asLinkedString, true);
return JsonString(asLinkedString(resources), true);
case VariantType::OwnedString:
return JsonString(content_.asOwnedString->data,
content_.asOwnedString->length);
@ -312,8 +314,8 @@ class VariantData {
}
}
#if ARDUINOJSON_USE_EXTENSIONS
const VariantExtension* getExtension(const ResourceManager* resources) const;
#if ARDUINOJSON_USE_8_BYTE_POOL
const EightByteValue* getEightByte(const ResourceManager* resources) const;
#endif
VariantData* getElement(size_t index,
@ -376,7 +378,7 @@ class VariantData {
template <typename T>
bool isInteger(const ResourceManager* resources) const {
#if ARDUINOJSON_USE_LONG_LONG
auto extension = getExtension(resources);
auto eightByteValue = getEightByte(resources);
#else
(void)resources; // silence warning
#endif
@ -389,10 +391,10 @@ class VariantData {
#if ARDUINOJSON_USE_LONG_LONG
case VariantType::Uint64:
return canConvertNumber<T>(extension->asUint64);
return canConvertNumber<T>(eightByteValue->asUint64);
case VariantType::Int64:
return canConvertNumber<T>(extension->asInt64);
return canConvertNumber<T>(eightByteValue->asInt64);
#endif
default:
@ -519,12 +521,7 @@ class VariantData {
var->setString(value, resources);
}
void setLinkedString(const char* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
type_ = VariantType::LinkedString;
content_.asLinkedString = s;
}
bool setLinkedString(const char* s, ResourceManager* resources);
template <typename TAdaptedString>
void setTinyString(const TAdaptedString& s) {

View File

@ -18,6 +18,20 @@ inline void VariantData::setRawString(SerializedValue<T> value,
setRawString(dup);
}
inline bool VariantData::setLinkedString(const char* s,
ResourceManager* resources) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
auto slotId = resources->saveStaticString(s);
if (slotId == NULL_SLOT)
return false;
type_ = VariantType::LinkedString;
content_.asSlotId = slotId;
return true;
}
template <typename TAdaptedString>
inline bool VariantData::setString(TAdaptedString value,
ResourceManager* resources) {
@ -26,10 +40,8 @@ inline bool VariantData::setString(TAdaptedString value,
if (value.isNull())
return false;
if (value.isStatic()) {
setLinkedString(value.data());
return true;
}
if (value.isStatic())
return setLinkedString(value.data(), resources);
if (isTinyString(value, value.size())) {
setTinyString(value);
@ -49,9 +61,9 @@ 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
if (type_ & VariantTypeBits::EightByteBit)
resources->freeEightByte(content_.asSlotId);
#endif
auto collection = asCollection();
@ -61,15 +73,21 @@ inline void VariantData::clear(ResourceManager* resources) {
type_ = VariantType::Null;
}
#if ARDUINOJSON_USE_EXTENSIONS
inline const VariantExtension* VariantData::getExtension(
#if ARDUINOJSON_USE_8_BYTE_POOL
inline const EightByteValue* VariantData::getEightByte(
const ResourceManager* resources) const {
return type_ & VariantTypeBits::ExtensionBit
? resources->getExtension(content_.asSlotId)
: nullptr;
return type_ & VariantTypeBits::EightByteBit
? resources->getEightByte(content_.asSlotId)
: 0;
}
#endif
inline const char* VariantData::asLinkedString(
const ResourceManager* resources) const {
ARDUINOJSON_ASSERT(type_ == VariantType::LinkedString);
return resources->getStaticString(content_.asSlotId);
}
template <typename T>
enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat(
T value, ResourceManager* resources) {
@ -83,12 +101,12 @@ enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat(
type_ = VariantType::Float;
content_.asFloat = valueAsFloat;
} else {
auto extension = resources->allocExtension();
if (!extension)
auto slot = resources->allocEightByte();
if (!slot)
return false;
type_ = VariantType::Double;
content_.asSlotId = extension.id();
extension->asDouble = value;
content_.asSlotId = slot.id();
slot->asDouble = value;
}
#else
type_ = VariantType::Float;
@ -109,12 +127,12 @@ enable_if_t<is_signed<T>::value, bool> VariantData::setInteger(
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto extension = resources->allocExtension();
if (!extension)
auto slot = resources->allocEightByte();
if (!slot)
return false;
type_ = VariantType::Int64;
content_.asSlotId = extension.id();
extension->asInt64 = value;
content_.asSlotId = slot.id();
slot->asInt64 = value;
}
#endif
return true;
@ -132,12 +150,12 @@ enable_if_t<is_unsigned<T>::value, bool> VariantData::setInteger(
}
#if ARDUINOJSON_USE_LONG_LONG
else {
auto extension = resources->allocExtension();
if (!extension)
auto slot = resources->allocEightByte();
if (!slot)
return false;
type_ = VariantType::Uint64;
content_.asSlotId = extension.id();
extension->asUint64 = value;
content_.asSlotId = slot.id();
slot->asUint64 = value;
}
#endif
return true;