forked from bblanchon/ArduinoJson
Compare commits
22 Commits
v7.3.1
...
replace-ex
Author | SHA1 | Date | |
---|---|---|---|
53f858f55c | |||
6cdd59034d | |||
6ccac18fd4 | |||
3f6fff7d0c | |||
cc2fea45cd | |||
4159af66eb | |||
05942b620a | |||
23c73b061e | |||
c2e756d942 | |||
94aacf873e | |||
bd2dccda0e | |||
411424b74e | |||
5e5c287978 | |||
377cf63075 | |||
3252013509 | |||
deab127a2f | |||
96281de682 | |||
f0e84e4933 | |||
91397f9f06 | |||
7f75985e47 | |||
05b68fc7cc | |||
b06cee8f4d |
93
.github/workflows/ci.yml
vendored
93
.github/workflows/ci.yml
vendored
@ -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"
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -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
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@ -1,6 +1,54 @@
|
||||
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)
|
||||
------
|
||||
|
||||
* Fix crash with tiny Flash strings (issue #2170)
|
||||
|
||||
v7.4.0 (2025-04-09)
|
||||
------
|
||||
|
||||
* Optimize storage of tiny strings (up to 3 characters)
|
||||
* Fix support for `const char[]` (issue #2166)
|
||||
|
||||
v7.3.1 (2025-02-27)
|
||||
------
|
||||
|
||||
|
@ -10,7 +10,7 @@ if(ESP_PLATFORM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
project(ArduinoJson VERSION 7.3.1)
|
||||
project(ArduinoJson VERSION 7.4.1)
|
||||
|
||||
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
|
||||
include(CTest)
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
version: 7.3.1.{build}
|
||||
version: 7.4.1.{build}
|
||||
environment:
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
|
||||
|
@ -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() {}
|
||||
|
@ -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() {}
|
||||
|
@ -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() {}
|
||||
|
@ -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() {}
|
||||
|
@ -46,7 +46,7 @@ TEST_CASE("BasicJsonDocument") {
|
||||
deserializeJson(doc, "{\"hello\":\"world\"}");
|
||||
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\"}");
|
||||
doc.clear();
|
||||
REQUIRE(allocatorLog == "ARAARDDD");
|
||||
REQUIRE(allocatorLog == "AARARDDD");
|
||||
}
|
||||
|
||||
SECTION("copy") {
|
||||
@ -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") {
|
||||
|
@ -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) {
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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") {
|
||||
|
@ -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)),
|
||||
});
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ TEST_CASE("deserializeJson(char*)") {
|
||||
REQUIRE(spy.log() ==
|
||||
AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
Allocate(sizeofPool()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("world")),
|
||||
Reallocate(sizeofPool(), sizeofObject(1)),
|
||||
|
@ -292,22 +292,23 @@ TEST_CASE("deserialize JSON object") {
|
||||
}
|
||||
|
||||
SECTION("Repeated key") {
|
||||
DeserializationError err = deserializeJson(doc, "{a:{b:{c:1}},a:2}");
|
||||
DeserializationError err =
|
||||
deserializeJson(doc, "{alfa:{bravo:{charlie:1}},alfa:2}");
|
||||
|
||||
REQUIRE(err == DeserializationError::Ok);
|
||||
REQUIRE(doc.as<std::string>() == "{\"a\":2}");
|
||||
REQUIRE(doc.as<std::string>() == "{\"alfa\":2}");
|
||||
REQUIRE(spy.log() ==
|
||||
AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("a")),
|
||||
Allocate(sizeofPool()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("alfa")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("b")),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("bravo")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("c")),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("charlie")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Deallocate(sizeofString("b")),
|
||||
Deallocate(sizeofString("c")),
|
||||
Deallocate(sizeofString("bravo")),
|
||||
Deallocate(sizeofString("charlie")),
|
||||
Deallocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
|
||||
});
|
||||
@ -378,7 +379,7 @@ TEST_CASE("deserialize JSON object under memory constraints") {
|
||||
}
|
||||
|
||||
SECTION("pool allocation fails") {
|
||||
timebomb.setCountdown(2);
|
||||
timebomb.setCountdown(1);
|
||||
char input[] = "{\"a\":1}";
|
||||
|
||||
DeserializationError err = deserializeJson(doc, input);
|
||||
@ -389,11 +390,11 @@ TEST_CASE("deserialize JSON object under memory constraints") {
|
||||
|
||||
SECTION("string allocation fails") {
|
||||
timebomb.setCountdown(3);
|
||||
char input[] = "{\"a\":\"b\"}";
|
||||
char input[] = "{\"alfa\":\"bravo\"}";
|
||||
|
||||
DeserializationError err = deserializeJson(doc, input);
|
||||
|
||||
REQUIRE(err == DeserializationError::NoMemory);
|
||||
REQUIRE(doc.as<std::string>() == "{\"a\":null}");
|
||||
REQUIRE(doc.as<std::string>() == "{\"alfa\":null}");
|
||||
}
|
||||
}
|
||||
|
@ -133,8 +133,8 @@ TEST_CASE("Allocation of the key fails") {
|
||||
REQUIRE(spy.log() ==
|
||||
AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
Allocate(sizeofPool()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
AllocateFail(sizeofStringBuffer()),
|
||||
ReallocateFail(sizeofPool(), sizeofObject(1)),
|
||||
});
|
||||
@ -155,8 +155,8 @@ TEST_CASE("Allocation of the key fails") {
|
||||
REQUIRE(spy.log() ==
|
||||
AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
Allocate(sizeofPool()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
AllocateFail(sizeofStringBuffer()),
|
||||
ReallocateFail(sizeofPool(), sizeofObject(1)),
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ TEST_CASE("ElementProxy::add()") {
|
||||
REQUIRE(doc.as<std::string>() == "[[\"world\"]]");
|
||||
REQUIRE(spy.log() == AllocatorLog{
|
||||
Allocate(sizeofPool()),
|
||||
Allocate(sizeofStaticStringPool()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ TEST_CASE("JsonDocument::add(T)") {
|
||||
REQUIRE(doc.as<std::string>() == "[\"hello\"]");
|
||||
REQUIRE(spy.log() == AllocatorLog{
|
||||
Allocate(sizeofPool()),
|
||||
Allocate(sizeofStaticStringPool()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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{
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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}");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)),
|
||||
});
|
||||
}
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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")),
|
||||
});
|
||||
}
|
||||
|
||||
@ -100,17 +88,17 @@ TEST_CASE("JsonObject::set()") {
|
||||
JsonDocument doc3(&timebomb);
|
||||
JsonObject obj3 = doc3.to<JsonObject>();
|
||||
|
||||
obj1["a"_s] = 1;
|
||||
obj1["b"_s] = 2;
|
||||
obj1["alpha"_s] = 1;
|
||||
obj1["beta"_s] = 2;
|
||||
|
||||
bool success = obj3.set(obj1);
|
||||
|
||||
REQUIRE(success == false);
|
||||
REQUIRE(doc3.as<std::string>() == "{\"a\":1}");
|
||||
REQUIRE(doc3.as<std::string>() == "{\"alpha\":1}");
|
||||
}
|
||||
|
||||
SECTION("copy fails in the middle of an array") {
|
||||
TimebombAllocator timebomb(1);
|
||||
TimebombAllocator timebomb(2);
|
||||
JsonDocument doc3(&timebomb);
|
||||
JsonObject obj3 = doc3.to<JsonObject>();
|
||||
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ TEST_CASE("JsonVariant::as()") {
|
||||
REQUIRE(variant.as<JsonString>() == "hello");
|
||||
}
|
||||
|
||||
SECTION("set(std::string(\"4.2\"))") {
|
||||
SECTION("set(std::string(\"4.2\")) (tiny string optimization)") {
|
||||
variant.set("4.2"_s);
|
||||
|
||||
REQUIRE(variant.as<bool>() == true);
|
||||
@ -211,6 +211,18 @@ TEST_CASE("JsonVariant::as()") {
|
||||
REQUIRE(variant.as<JsonString>().isStatic() == false);
|
||||
}
|
||||
|
||||
SECTION("set(std::string(\"123.45\"))") {
|
||||
variant.set("123.45"_s);
|
||||
|
||||
REQUIRE(variant.as<bool>() == true);
|
||||
REQUIRE(variant.as<long>() == 123L);
|
||||
REQUIRE(variant.as<double>() == Approx(123.45));
|
||||
REQUIRE(variant.as<const char*>() == "123.45"_s);
|
||||
REQUIRE(variant.as<std::string>() == "123.45"_s);
|
||||
REQUIRE(variant.as<JsonString>() == "123.45");
|
||||
REQUIRE(variant.as<JsonString>().isStatic() == false);
|
||||
}
|
||||
|
||||
SECTION("set(\"true\")") {
|
||||
variant.set("true");
|
||||
|
||||
|
@ -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") {
|
||||
|
@ -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*") {
|
||||
@ -63,6 +65,18 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("char* (tiny string optimization)") {
|
||||
char str[16];
|
||||
|
||||
strcpy(str, "abc");
|
||||
bool result = variant.set(str);
|
||||
strcpy(str, "def");
|
||||
|
||||
REQUIRE(result == true);
|
||||
REQUIRE(variant == "abc"); // stores by copy
|
||||
REQUIRE(spy.log() == AllocatorLog{});
|
||||
}
|
||||
|
||||
SECTION("(char*)0") {
|
||||
bool result = variant.set(static_cast<char*>(0));
|
||||
|
||||
@ -137,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") {
|
||||
@ -181,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") {
|
||||
@ -204,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") {
|
||||
@ -227,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") {
|
||||
@ -253,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);
|
||||
|
||||
@ -348,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")),
|
||||
});
|
||||
@ -363,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>();
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ add_executable(MiscTests
|
||||
conflicts.cpp
|
||||
issue1967.cpp
|
||||
issue2129.cpp
|
||||
issue2166.cpp
|
||||
JsonString.cpp
|
||||
NoArduinoHeader.cpp
|
||||
printable.cpp
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -128,6 +128,7 @@ TEST_CASE("IsString<T>") {
|
||||
CHECK(IsString<const __FlashStringHelper*>::value == true);
|
||||
CHECK(IsString<const char*>::value == true);
|
||||
CHECK(IsString<const char[8]>::value == true);
|
||||
CHECK(IsString<const char[]>::value == true);
|
||||
CHECK(IsString<::String>::value == true);
|
||||
CHECK(IsString<::StringSumHelper>::value == true);
|
||||
CHECK(IsString<const EmptyStruct*>::value == false);
|
||||
|
22
extras/tests/Misc/issue2166.cpp
Normal file
22
extras/tests/Misc/issue2166.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <catch.hpp>
|
||||
|
||||
struct CCLASS {
|
||||
static const char mszKey[];
|
||||
};
|
||||
|
||||
TEST_CASE("Issue #2166") {
|
||||
JsonDocument doc;
|
||||
doc[CCLASS::mszKey] = 12;
|
||||
REQUIRE(doc.as<std::string>() == "{\"test3\":12}");
|
||||
|
||||
JsonObject obj = doc.to<JsonObject>();
|
||||
obj[CCLASS::mszKey] = 12;
|
||||
REQUIRE(doc.as<std::string>() == "{\"test3\":12}");
|
||||
}
|
||||
|
||||
const char CCLASS::mszKey[] = "test3";
|
@ -5,8 +5,11 @@
|
||||
#include <ArduinoJson.h>
|
||||
#include <catch.hpp>
|
||||
|
||||
#include "Allocators.hpp"
|
||||
|
||||
TEST_CASE("deserialize MsgPack array") {
|
||||
JsonDocument doc;
|
||||
SpyingAllocator spy;
|
||||
JsonDocument doc(&spy);
|
||||
|
||||
SECTION("fixarray") {
|
||||
SECTION("empty") {
|
||||
@ -30,6 +33,24 @@ TEST_CASE("deserialize MsgPack array") {
|
||||
REQUIRE(array[0] == 1);
|
||||
REQUIRE(array[1] == 2);
|
||||
}
|
||||
|
||||
SECTION("tiny strings") {
|
||||
DeserializationError error =
|
||||
deserializeMsgPack(doc, "\x92\xA3xxx\xA3yyy");
|
||||
|
||||
REQUIRE(error == DeserializationError::Ok);
|
||||
REQUIRE(doc.is<JsonArray>());
|
||||
REQUIRE(doc.size() == 2);
|
||||
REQUIRE(doc[0] == "xxx");
|
||||
REQUIRE(doc[1] == "yyy");
|
||||
REQUIRE(spy.log() == AllocatorLog{
|
||||
Allocate(sizeofPool()),
|
||||
Allocate(sizeofString("xxx")),
|
||||
// Buffer is reused for the next string
|
||||
Deallocate(sizeofString("xxx")),
|
||||
Reallocate(sizeofPool(), sizeofPool(2)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("array 16") {
|
||||
|
@ -348,13 +348,14 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
|
||||
SECTION("{}") {
|
||||
checkError(0, "\x80", DeserializationError::Ok);
|
||||
}
|
||||
SECTION("{H:1}") {
|
||||
checkError(1, "\x81\xA1H\x01", DeserializationError::NoMemory);
|
||||
checkError(2, "\x81\xA1H\x01", DeserializationError::Ok);
|
||||
SECTION("{Hello:1}") {
|
||||
checkError(1, "\x81\xA5Hello\x01", DeserializationError::NoMemory);
|
||||
checkError(2, "\x81\xA5Hello\x01", DeserializationError::Ok);
|
||||
}
|
||||
SECTION("{H:1,W:2}") {
|
||||
checkError(2, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
|
||||
checkError(3, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
|
||||
SECTION("{Hello:1,World:2}") {
|
||||
checkError(2, "\x82\xA5Hello\x01\xA5World\x02",
|
||||
DeserializationError::NoMemory);
|
||||
checkError(3, "\x82\xA5Hello\x01\xA5World\x02", DeserializationError::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
@ -362,14 +363,16 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
|
||||
SECTION("{}") {
|
||||
checkError(0, "\xDE\x00\x00", DeserializationError::Ok);
|
||||
}
|
||||
SECTION("{H:1}") {
|
||||
checkError(1, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory);
|
||||
checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
|
||||
}
|
||||
SECTION("{H:1,W:2}") {
|
||||
checkError(2, "\xDE\x00\x02\xA1H\x01\xA1W\x02",
|
||||
SECTION("{Hello:1}") {
|
||||
checkError(1, "\xDE\x00\x01\xA5Hello\x01",
|
||||
DeserializationError::NoMemory);
|
||||
checkError(3, "\xDE\x00\x02\xA1H\x01\xA1W\x02", DeserializationError::Ok);
|
||||
checkError(2, "\xDE\x00\x01\xA5Hello\x01", DeserializationError::Ok);
|
||||
}
|
||||
SECTION("{Hello:1,World:2}") {
|
||||
checkError(2, "\xDE\x00\x02\xA5Hello\x01\xA5World\x02",
|
||||
DeserializationError::NoMemory);
|
||||
checkError(3, "\xDE\x00\x02\xA5Hello\x01\xA5World\x02",
|
||||
DeserializationError::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,8 +385,8 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
|
||||
DeserializationError::NoMemory);
|
||||
checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
|
||||
}
|
||||
SECTION("{H:1,W:2}") {
|
||||
checkError(2, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
|
||||
SECTION("{Hello:1,World:2}") {
|
||||
checkError(2, "\xDF\x00\x00\x00\x02\xA5Hello\x01\xA5World\x02",
|
||||
DeserializationError::NoMemory);
|
||||
checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
|
||||
DeserializationError::Ok);
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@
|
||||
add_executable(ResourceManagerTests
|
||||
allocVariant.cpp
|
||||
clear.cpp
|
||||
saveStaticString.cpp
|
||||
saveString.cpp
|
||||
shrinkToFit.cpp
|
||||
size.cpp
|
||||
StringBuffer.cpp
|
||||
StringBuilder.cpp
|
||||
swap.cpp
|
||||
)
|
||||
|
50
extras/tests/ResourceManager/StringBuffer.cpp
Normal file
50
extras/tests/ResourceManager/StringBuffer.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
// ArduinoJson - https://arduinojson.org
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
#include <ArduinoJson.hpp>
|
||||
#include <catch.hpp>
|
||||
|
||||
#include "Allocators.hpp"
|
||||
#include "Literals.hpp"
|
||||
|
||||
using namespace ArduinoJson::detail;
|
||||
|
||||
TEST_CASE("StringBuffer") {
|
||||
SpyingAllocator spy;
|
||||
ResourceManager resources(&spy);
|
||||
StringBuffer sb(&resources);
|
||||
VariantData variant;
|
||||
|
||||
SECTION("Tiny string") {
|
||||
auto ptr = sb.reserve(3);
|
||||
strcpy(ptr, "hi!");
|
||||
sb.save(&variant);
|
||||
|
||||
REQUIRE(variant.type() == VariantType::TinyString);
|
||||
REQUIRE(variant.asString(&resources) == "hi!");
|
||||
}
|
||||
|
||||
SECTION("Tiny string can't contain NUL") {
|
||||
auto ptr = sb.reserve(3);
|
||||
memcpy(ptr, "a\0b", 3);
|
||||
sb.save(&variant);
|
||||
|
||||
REQUIRE(variant.type() == VariantType::OwnedString);
|
||||
|
||||
auto str = variant.asString(&resources);
|
||||
REQUIRE(str.size() == 3);
|
||||
REQUIRE(str.c_str()[0] == 'a');
|
||||
REQUIRE(str.c_str()[1] == 0);
|
||||
REQUIRE(str.c_str()[2] == 'b');
|
||||
}
|
||||
|
||||
SECTION("Tiny string can't have 4 characters") {
|
||||
auto ptr = sb.reserve(4);
|
||||
strcpy(ptr, "alfa");
|
||||
sb.save(&variant);
|
||||
|
||||
REQUIRE(variant.type() == VariantType::OwnedString);
|
||||
REQUIRE(variant.asString(&resources) == "alfa");
|
||||
}
|
||||
}
|
@ -2,11 +2,12 @@
|
||||
// Copyright © 2014-2025, Benoit BLANCHON
|
||||
// MIT License
|
||||
|
||||
#include <ArduinoJson/Memory/StringBuilder.hpp>
|
||||
#include <ArduinoJson.hpp>
|
||||
#include <catch.hpp>
|
||||
|
||||
#include "Allocators.hpp"
|
||||
|
||||
using namespace ArduinoJson;
|
||||
using namespace ArduinoJson::detail;
|
||||
|
||||
TEST_CASE("StringBuilder") {
|
||||
@ -16,17 +17,36 @@ TEST_CASE("StringBuilder") {
|
||||
|
||||
SECTION("Empty string") {
|
||||
StringBuilder str(&resources);
|
||||
VariantData data;
|
||||
|
||||
str.startString();
|
||||
str.save();
|
||||
str.save(&data);
|
||||
|
||||
REQUIRE(resources.size() == sizeofString(""));
|
||||
REQUIRE(resources.overflowed() == false);
|
||||
REQUIRE(spyingAllocator.log() ==
|
||||
AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("")),
|
||||
});
|
||||
REQUIRE(spyingAllocator.log() == AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
});
|
||||
REQUIRE(data.type() == VariantType::TinyString);
|
||||
}
|
||||
|
||||
SECTION("Tiny string") {
|
||||
StringBuilder str(&resources);
|
||||
|
||||
str.startString();
|
||||
str.append("url");
|
||||
|
||||
REQUIRE(str.isValid() == true);
|
||||
REQUIRE(str.str() == "url");
|
||||
REQUIRE(spyingAllocator.log() == AllocatorLog{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
});
|
||||
|
||||
VariantData data;
|
||||
str.save(&data);
|
||||
|
||||
REQUIRE(resources.overflowed() == false);
|
||||
REQUIRE(data.type() == VariantType::TinyString);
|
||||
REQUIRE(data.asString(&resources) == "url");
|
||||
}
|
||||
|
||||
SECTION("Short string fits in first allocation") {
|
||||
@ -96,48 +116,72 @@ TEST_CASE("StringBuilder") {
|
||||
}
|
||||
}
|
||||
|
||||
static StringNode* addStringToPool(ResourceManager& resources, const char* s) {
|
||||
StringBuilder str(&resources);
|
||||
str.startString();
|
||||
str.append(s);
|
||||
return str.save();
|
||||
static VariantData saveString(StringBuilder& builder, const char* s) {
|
||||
VariantData data;
|
||||
builder.startString();
|
||||
builder.append(s);
|
||||
builder.save(&data);
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST_CASE("StringBuilder::save() deduplicates strings") {
|
||||
ResourceManager resources;
|
||||
SpyingAllocator spy;
|
||||
ResourceManager resources(&spy);
|
||||
StringBuilder builder(&resources);
|
||||
|
||||
SECTION("Basic") {
|
||||
auto s1 = addStringToPool(resources, "hello");
|
||||
auto s2 = addStringToPool(resources, "world");
|
||||
auto s3 = addStringToPool(resources, "hello");
|
||||
auto s1 = saveString(builder, "hello");
|
||||
auto s2 = saveString(builder, "world");
|
||||
auto s3 = saveString(builder, "hello");
|
||||
|
||||
REQUIRE(s1 == s3);
|
||||
REQUIRE(s2 != s3);
|
||||
REQUIRE(s1->references == 2);
|
||||
REQUIRE(s2->references == 1);
|
||||
REQUIRE(s3->references == 2);
|
||||
REQUIRE(resources.size() == sizeofString("hello") + sizeofString("world"));
|
||||
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{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("world")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("Requires terminator") {
|
||||
auto s1 = addStringToPool(resources, "hello world");
|
||||
auto s2 = addStringToPool(resources, "hello");
|
||||
auto s1 = saveString(builder, "hello world");
|
||||
auto s2 = saveString(builder, "hello");
|
||||
|
||||
REQUIRE(s2 != s1);
|
||||
REQUIRE(s1->references == 1);
|
||||
REQUIRE(s2->references == 1);
|
||||
REQUIRE(resources.size() ==
|
||||
sizeofString("hello world") + sizeofString("hello"));
|
||||
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{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello")),
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("Don't overrun") {
|
||||
auto s1 = addStringToPool(resources, "hello world");
|
||||
auto s2 = addStringToPool(resources, "wor");
|
||||
auto s1 = saveString(builder, "hello world");
|
||||
auto s2 = saveString(builder, "worl");
|
||||
|
||||
REQUIRE(s2 != s1);
|
||||
REQUIRE(s1->references == 1);
|
||||
REQUIRE(s2->references == 1);
|
||||
REQUIRE(resources.size() ==
|
||||
sizeofString("hello world") + sizeofString("wor"));
|
||||
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{
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
|
||||
Allocate(sizeofStringBuffer()),
|
||||
Reallocate(sizeofStringBuffer(), sizeofString("worl")),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
47
extras/tests/ResourceManager/saveStaticString.cpp
Normal file
47
extras/tests/ResourceManager/saveStaticString.cpp
Normal 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()),
|
||||
});
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
version: "7.3.1"
|
||||
version: "7.4.1"
|
||||
description: >-
|
||||
A simple and efficient JSON library for embedded C++.
|
||||
★ 6849 stars on GitHub!
|
||||
★ 6898 stars on GitHub!
|
||||
Supports serialization, deserialization, MessagePack, streams, filtering, and more.
|
||||
Fully tested and documented.
|
||||
url: https://arduinojson.org/
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "ArduinoJson",
|
||||
"keywords": "json, rest, http, web",
|
||||
"description": "A simple and efficient JSON library for embedded C++. ⭐ 6849 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.",
|
||||
"description": "A simple and efficient JSON library for embedded C++. ⭐ 6898 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.",
|
||||
"homepage": "https://arduinojson.org/?utm_source=meta&utm_medium=library.json",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bblanchon/ArduinoJson.git"
|
||||
},
|
||||
"version": "7.3.1",
|
||||
"version": "7.4.1",
|
||||
"authors": {
|
||||
"name": "Benoit Blanchon",
|
||||
"url": "https://blog.benoitblanchon.fr"
|
||||
|
@ -1,9 +1,9 @@
|
||||
name=ArduinoJson
|
||||
version=7.3.1
|
||||
version=7.4.1
|
||||
author=Benoit Blanchon <blog.benoitblanchon.fr>
|
||||
maintainer=Benoit Blanchon <blog.benoitblanchon.fr>
|
||||
sentence=A simple and efficient JSON library for embedded C++.
|
||||
paragraph=⭐ 6849 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.
|
||||
paragraph=⭐ 6898 stars on GitHub! Supports serialization, deserialization, MessagePack, streams, filtering, and more. Fully tested and documented.
|
||||
category=Data Processing
|
||||
url=https://arduinojson.org/?utm_source=meta&utm_medium=library.properties
|
||||
architectures=*
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -275,13 +275,11 @@ class JsonDeserializer {
|
||||
if (memberFilter.allow()) {
|
||||
auto member = object.getMember(adaptString(key), resources_);
|
||||
if (!member) {
|
||||
// Save key in memory pool.
|
||||
auto savedKey = stringBuilder_.save();
|
||||
|
||||
// Allocate slot in object
|
||||
member = object.addMember(savedKey, resources_);
|
||||
if (!member)
|
||||
auto keyVariant = object.addPair(&member, resources_);
|
||||
if (!keyVariant)
|
||||
return DeserializationError::NoMemory;
|
||||
|
||||
stringBuilder_.save(keyVariant);
|
||||
} else {
|
||||
member->clear(resources_);
|
||||
}
|
||||
@ -390,7 +388,7 @@ class JsonDeserializer {
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
variant.setOwnedString(stringBuilder_.save());
|
||||
stringBuilder_.save(&variant);
|
||||
|
||||
return DeserializationError::Ok;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -32,7 +32,26 @@ class StringBuffer {
|
||||
return node_->data;
|
||||
}
|
||||
|
||||
StringNode* save() {
|
||||
JsonString str() const {
|
||||
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||
return JsonString(node_->data, node_->length);
|
||||
}
|
||||
|
||||
void save(VariantData* data) {
|
||||
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||
const char* s = node_->data;
|
||||
if (isTinyString(s, size_))
|
||||
data->setTinyString(adaptString(s, size_));
|
||||
else
|
||||
data->setOwnedString(commitStringNode());
|
||||
}
|
||||
|
||||
void saveRaw(VariantData* data) {
|
||||
data->setRawString(commitStringNode());
|
||||
}
|
||||
|
||||
private:
|
||||
StringNode* commitStringNode() {
|
||||
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||
node_->data[size_] = 0;
|
||||
auto node = resources_->getString(adaptString(node_->data, size_));
|
||||
@ -52,13 +71,6 @@ class StringBuffer {
|
||||
return node;
|
||||
}
|
||||
|
||||
JsonString str() const {
|
||||
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||
|
||||
return JsonString(node_->data, node_->length);
|
||||
}
|
||||
|
||||
private:
|
||||
ResourceManager* resources_;
|
||||
StringNode* node_ = nullptr;
|
||||
size_t size_ = 0;
|
||||
|
@ -25,10 +25,18 @@ class StringBuilder {
|
||||
node_ = resources_->createString(initialCapacity);
|
||||
}
|
||||
|
||||
StringNode* save() {
|
||||
void save(VariantData* variant) {
|
||||
ARDUINOJSON_ASSERT(variant != nullptr);
|
||||
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||
node_->data[size_] = 0;
|
||||
StringNode* node = resources_->getString(adaptString(node_->data, size_));
|
||||
|
||||
char* p = node_->data;
|
||||
if (isTinyString(p, size_)) {
|
||||
variant->setTinyString(adaptString(p, size_));
|
||||
return;
|
||||
}
|
||||
|
||||
p[size_] = 0;
|
||||
StringNode* node = resources_->getString(adaptString(p, size_));
|
||||
if (!node) {
|
||||
node = resources_->resizeString(node_, size_);
|
||||
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
|
||||
@ -37,7 +45,7 @@ class StringBuilder {
|
||||
} else {
|
||||
node->references++;
|
||||
}
|
||||
return node;
|
||||
variant->setOwnedString(node);
|
||||
}
|
||||
|
||||
void append(const char* s) {
|
||||
|
@ -305,7 +305,7 @@ class MsgPackDeserializer {
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
variant->setOwnedString(stringBuffer_.save());
|
||||
stringBuffer_.save(variant);
|
||||
return DeserializationError::Ok;
|
||||
}
|
||||
|
||||
@ -334,7 +334,7 @@ class MsgPackDeserializer {
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
variant->setRawString(stringBuffer_.save());
|
||||
stringBuffer_.saveRaw(variant);
|
||||
return DeserializationError::Ok;
|
||||
}
|
||||
|
||||
@ -403,19 +403,16 @@ class MsgPackDeserializer {
|
||||
|
||||
JsonString key = stringBuffer_.str();
|
||||
TFilter memberFilter = filter[key.c_str()];
|
||||
VariantData* member;
|
||||
VariantData* member = 0;
|
||||
|
||||
if (memberFilter.allow()) {
|
||||
ARDUINOJSON_ASSERT(object != 0);
|
||||
|
||||
// Save key in memory pool.
|
||||
auto savedKey = stringBuffer_.save();
|
||||
|
||||
member = object->addMember(savedKey, resources_);
|
||||
if (!member)
|
||||
auto keyVariant = object->addPair(&member, resources_);
|
||||
if (!keyVariant)
|
||||
return DeserializationError::NoMemory;
|
||||
} else {
|
||||
member = 0;
|
||||
|
||||
stringBuffer_.save(keyVariant);
|
||||
}
|
||||
|
||||
err = parseVariant(member, memberFilter, nestingLimit.decrement());
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -10,9 +10,11 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||
|
||||
class ObjectData : public CollectionData {
|
||||
public:
|
||||
template <typename TAdaptedString> // also works with StringNode*
|
||||
template <typename TAdaptedString>
|
||||
VariantData* addMember(TAdaptedString key, ResourceManager* resources);
|
||||
|
||||
VariantData* addPair(VariantData** value, ResourceManager* resources);
|
||||
|
||||
template <typename TAdaptedString>
|
||||
VariantData* getOrAddMember(TAdaptedString key, ResourceManager* resources);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
@ -68,9 +68,25 @@ inline VariantData* ObjectData::addMember(TAdaptedString key,
|
||||
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
|
||||
|
@ -76,12 +76,23 @@ struct StringAdapter<TChar*, enable_if_t<IsChar<TChar>::value>> {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TChar>
|
||||
struct StringAdapter<TChar[], enable_if_t<IsChar<TChar>::value>> {
|
||||
using AdaptedString = RamString;
|
||||
|
||||
static AdaptedString adapt(const TChar* p) {
|
||||
auto str = reinterpret_cast<const char*>(p);
|
||||
return AdaptedString(str, str ? ::strlen(str) : 0);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
@ -230,9 +230,9 @@ class StringBuilderPrint : public Print {
|
||||
copier_.startString();
|
||||
}
|
||||
|
||||
StringNode* save() {
|
||||
void save(VariantData* data) {
|
||||
ARDUINOJSON_ASSERT(!overflowed());
|
||||
return copier_.save();
|
||||
copier_.save(data);
|
||||
}
|
||||
|
||||
size_t write(uint8_t c) {
|
||||
@ -268,7 +268,7 @@ inline void convertToJson(const ::Printable& src, JsonVariant dst) {
|
||||
src.printTo(print);
|
||||
if (print.overflowed())
|
||||
return;
|
||||
data->setOwnedString(print.save());
|
||||
print.save(data);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -16,14 +16,13 @@ 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,
|
||||
};
|
||||
|
||||
enum class VariantType : uint8_t {
|
||||
Null = 0, // 0000 0000
|
||||
TinyString = 0x02, // 0000 0010
|
||||
RawString = 0x03, // 0000 0011
|
||||
LinkedString = 0x04, // 0000 0100
|
||||
OwnedString = 0x05, // 0000 0101
|
||||
@ -46,6 +45,8 @@ inline bool operator&(VariantType type, VariantTypeBits bit) {
|
||||
return (uint8_t(type) & uint8_t(bit)) != 0;
|
||||
}
|
||||
|
||||
const size_t tinyStringMaxLength = 3;
|
||||
|
||||
union VariantContent {
|
||||
VariantContent() {}
|
||||
|
||||
@ -53,18 +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;
|
||||
@ -73,6 +72,9 @@ union VariantExtension {
|
||||
double asDouble;
|
||||
# endif
|
||||
};
|
||||
|
||||
static_assert(sizeof(EightByteValue) == 8,
|
||||
"sizeof(EightByteValue) must be 8 bytes");
|
||||
#endif
|
||||
|
||||
ARDUINOJSON_END_PRIVATE_NAMESPACE
|
||||
|
@ -17,6 +17,16 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
||||
template <typename T>
|
||||
T parseNumber(const char* s);
|
||||
|
||||
template <typename T>
|
||||
static bool isTinyString(const T& s, size_t n) {
|
||||
if (n > tinyStringMaxLength)
|
||||
return false;
|
||||
bool containsNul = false;
|
||||
for (uint8_t i = 0; i < uint8_t(n); i++)
|
||||
containsNul |= !s[i];
|
||||
return !containsNul;
|
||||
}
|
||||
|
||||
class VariantData {
|
||||
VariantContent content_; // must be first to allow cast from array to variant
|
||||
VariantType type_;
|
||||
@ -43,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
|
||||
@ -54,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:
|
||||
@ -63,8 +73,11 @@ class VariantData {
|
||||
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));
|
||||
return visit.visit(JsonString(asLinkedString(resources), true));
|
||||
|
||||
case VariantType::OwnedString:
|
||||
return visit.visit(JsonString(content_.asOwnedString->data,
|
||||
@ -82,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:
|
||||
@ -132,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
|
||||
@ -147,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;
|
||||
@ -180,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
|
||||
@ -195,12 +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;
|
||||
@ -209,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;
|
||||
@ -222,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
|
||||
@ -237,12 +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;
|
||||
@ -251,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;
|
||||
@ -279,10 +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);
|
||||
@ -291,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,
|
||||
@ -355,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
|
||||
@ -368,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:
|
||||
@ -395,7 +418,8 @@ class VariantData {
|
||||
|
||||
bool isString() const {
|
||||
return type_ == VariantType::LinkedString ||
|
||||
type_ == VariantType::OwnedString;
|
||||
type_ == VariantType::OwnedString ||
|
||||
type_ == VariantType::TinyString;
|
||||
}
|
||||
|
||||
size_t nesting(const ResourceManager* resources) const {
|
||||
@ -488,11 +512,6 @@ class VariantData {
|
||||
template <typename TAdaptedString>
|
||||
bool setString(TAdaptedString value, ResourceManager* resources);
|
||||
|
||||
bool setString(StringNode* s, ResourceManager*) {
|
||||
setOwnedString(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename TAdaptedString>
|
||||
static void setString(VariantData* var, TAdaptedString value,
|
||||
ResourceManager* resources) {
|
||||
@ -502,11 +521,23 @@ class VariantData {
|
||||
var->setString(value, resources);
|
||||
}
|
||||
|
||||
void setLinkedString(const char* s) {
|
||||
bool setLinkedString(const char* s, ResourceManager* resources);
|
||||
|
||||
template <typename TAdaptedString>
|
||||
void setTinyString(const TAdaptedString& s) {
|
||||
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
|
||||
ARDUINOJSON_ASSERT(s);
|
||||
type_ = VariantType::LinkedString;
|
||||
content_.asLinkedString = s;
|
||||
ARDUINOJSON_ASSERT(s.size() <= tinyStringMaxLength);
|
||||
|
||||
type_ = VariantType::TinyString;
|
||||
|
||||
auto n = uint8_t(s.size());
|
||||
for (uint8_t i = 0; i < n; i++) {
|
||||
char c = s[i];
|
||||
ARDUINOJSON_ASSERT(c != 0); // no NUL in tiny string
|
||||
content_.asTinyString[i] = c;
|
||||
}
|
||||
|
||||
content_.asTinyString[n] = 0;
|
||||
}
|
||||
|
||||
void setOwnedString(StringNode* s) {
|
||||
|
@ -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,8 +40,11 @@ inline bool VariantData::setString(TAdaptedString value,
|
||||
if (value.isNull())
|
||||
return false;
|
||||
|
||||
if (value.isStatic()) {
|
||||
setLinkedString(value.data());
|
||||
if (value.isStatic())
|
||||
return setLinkedString(value.data(), resources);
|
||||
|
||||
if (isTinyString(value, value.size())) {
|
||||
setTinyString(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -44,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();
|
||||
@ -56,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) {
|
||||
@ -78,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;
|
||||
@ -104,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;
|
||||
@ -127,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;
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define ARDUINOJSON_VERSION "7.3.1"
|
||||
#define ARDUINOJSON_VERSION "7.4.1"
|
||||
#define ARDUINOJSON_VERSION_MAJOR 7
|
||||
#define ARDUINOJSON_VERSION_MINOR 3
|
||||
#define ARDUINOJSON_VERSION_MINOR 4
|
||||
#define ARDUINOJSON_VERSION_REVISION 1
|
||||
#define ARDUINOJSON_VERSION_MACRO V731
|
||||
#define ARDUINOJSON_VERSION_MACRO V741
|
||||
|
Reference in New Issue
Block a user