Skip to content

Commit 5b6da39

Browse files
committed
Store static strings in a dedicated pool
1 parent 9f3cf04 commit 5b6da39

34 files changed

+264
-73
lines changed

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
ArduinoJson: change log
22
=======================
33

4+
HEAD
5+
----
6+
7+
* Optimize storage of static strings
8+
9+
> ### BREAKING CHANGES
10+
>
11+
> Static string cannot contain NUL characters anymore (they could since 7.3.0).
12+
> This is an extremely rare case, so you probably won't be affected.
13+
>
14+
> For example, the following code produces different output in 7.3 and 7.4:
15+
>
16+
> ```cpp
17+
> JsonDocument doc;
18+
> doc["a\0b"] = "c\0d";
19+
> serializeJson(doc, Serial);
20+
> // With Arduino 7.3 -> {"a\u0000b":"c\u0000d"}
21+
> // With Arduino 7.4 -> {"a":"c"}
22+
> ```
23+
>
24+
> `JsonString` contructor now only accepts two arguments, not three.
25+
> If your code uses `JsonString` to store a string as a pointer, you must remove the size argument.
26+
>
27+
> For example, if you have something like this:
28+
>
29+
> ```cpp
30+
> doc["key"] = JsonString(str.c_str(), str.size(), true);
31+
> ```
32+
>
33+
> You must replace with either:
34+
>
35+
> ```cpp
36+
> doc["key"] = JsonString(str.c_str(), true); // store as pointer, cannot contain NUL characters
37+
> doc["key"] = JsonString(str.c_str(), str.size()); // store by copy, NUL characters allowed
38+
> doc["key"] = str; // same as previous line for supported string classes (`String`, `std::string`, etc.)
39+
> ```
40+
441
v7.3.0 (2024-12-29)
542
------
643

extras/tests/Deprecated/BasicJsonDocument.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ TEST_CASE("BasicJsonDocument") {
5454
doc["hello"] = "world";
5555
auto copy = doc;
5656
REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}");
57-
REQUIRE(allocatorLog == "AA");
57+
REQUIRE(allocatorLog == "AAAA");
5858
}
5959

6060
SECTION("capacity") {

extras/tests/Helpers/Allocators.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ inline size_t sizeofPool(
275275
return MemoryPool<VariantData>::slotsToBytes(n);
276276
}
277277

278+
inline size_t sizeofStaticStringPool(
279+
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
280+
using namespace ArduinoJson::detail;
281+
return MemoryPool<const char*>::slotsToBytes(n);
282+
}
283+
278284
inline size_t sizeofStringBuffer(size_t iteration = 1) {
279285
// returns 31, 63, 127, 255, etc.
280286
auto capacity = ArduinoJson::detail::StringBuilder::initialCapacity;

extras/tests/JsonArray/add.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ TEST_CASE("JsonArray::add(T)") {
5656
REQUIRE(array[0].is<int>() == false);
5757
REQUIRE(spy.log() == AllocatorLog{
5858
Allocate(sizeofPool()),
59+
Allocate(sizeofStaticStringPool()),
5960
});
6061
}
6162

extras/tests/JsonDeserializer/destination_types.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ TEST_CASE("deserializeJson(MemberProxy)") {
104104

105105
REQUIRE(err == DeserializationError::Ok);
106106
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
107-
REQUIRE(spy.log() == AllocatorLog{});
107+
REQUIRE(spy.log() == AllocatorLog{
108+
Allocate(sizeofStaticStringPool()),
109+
});
108110
}
109111
}

extras/tests/JsonDeserializer/filter.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -825,7 +825,9 @@ TEST_CASE("shrink filter") {
825825

826826
deserializeJson(doc, "{}", DeserializationOption::Filter(filter));
827827

828-
REQUIRE(spy.log() == AllocatorLog{
829-
Reallocate(sizeofPool(), sizeofObject(1)),
830-
});
828+
REQUIRE(spy.log() ==
829+
AllocatorLog{
830+
Reallocate(sizeofPool(), sizeofObject(1)),
831+
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
832+
});
831833
}

extras/tests/JsonDocument/ElementProxy.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ TEST_CASE("ElementProxy::add()") {
3131
REQUIRE(doc.as<std::string>() == "[[\"world\"]]");
3232
REQUIRE(spy.log() == AllocatorLog{
3333
Allocate(sizeofPool()),
34+
Allocate(sizeofStaticStringPool()),
3435
});
3536
}
3637

extras/tests/JsonDocument/MemberProxy.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ TEST_CASE("MemberProxy::add()") {
2525
REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}");
2626
REQUIRE(spy.log() == AllocatorLog{
2727
Allocate(sizeofPool()),
28+
Allocate(sizeofStaticStringPool()),
2829
});
2930
}
3031

@@ -34,6 +35,7 @@ TEST_CASE("MemberProxy::add()") {
3435
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
3536
REQUIRE(spy.log() == AllocatorLog{
3637
Allocate(sizeofPool()),
38+
Allocate(sizeofStaticStringPool()),
3739
});
3840
}
3941

@@ -44,6 +46,7 @@ TEST_CASE("MemberProxy::add()") {
4446
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
4547
REQUIRE(spy.log() == AllocatorLog{
4648
Allocate(sizeofPool()),
49+
Allocate(sizeofStaticStringPool()),
4750
Allocate(sizeofString("world")),
4851
});
4952
}
@@ -55,8 +58,8 @@ TEST_CASE("MemberProxy::add()") {
5558
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
5659
REQUIRE(spy.log() == AllocatorLog{
5760
Allocate(sizeofPool()),
61+
Allocate(sizeofStaticStringPool()),
5862
Allocate(sizeofString("world")),
59-
6063
});
6164
}
6265

@@ -71,6 +74,7 @@ TEST_CASE("MemberProxy::add()") {
7174
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
7275
REQUIRE(spy.log() == AllocatorLog{
7376
Allocate(sizeofPool()),
77+
Allocate(sizeofStaticStringPool()),
7478
Allocate(sizeofString("world")),
7579
});
7680
}
@@ -399,7 +403,7 @@ TEST_CASE("MemberProxy under memory constraints") {
399403
}
400404

401405
SECTION("value slot allocation fails") {
402-
timebomb.setCountdown(1);
406+
timebomb.setCountdown(2);
403407

404408
// fill the pool entirely, but leave one slot for the key
405409
doc["foo"][ARDUINOJSON_POOL_CAPACITY - 4] = 1;
@@ -412,6 +416,7 @@ TEST_CASE("MemberProxy under memory constraints") {
412416
REQUIRE(doc.overflowed() == true);
413417
REQUIRE(spy.log() == AllocatorLog{
414418
Allocate(sizeofPool()),
419+
Allocate(sizeofStaticStringPool()),
415420
AllocateFail(sizeofPool()),
416421
});
417422
}

extras/tests/JsonDocument/add.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ TEST_CASE("JsonDocument::add(T)") {
3232
REQUIRE(doc.as<std::string>() == "[\"hello\"]");
3333
REQUIRE(spy.log() == AllocatorLog{
3434
Allocate(sizeofPool()),
35+
Allocate(sizeofStaticStringPool()),
3536
});
3637
}
3738

extras/tests/JsonDocument/constructor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ TEST_CASE("JsonDocument constructor") {
6262
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
6363
REQUIRE(spyingAllocator.log() == AllocatorLog{
6464
Allocate(sizeofPool()),
65+
Allocate(sizeofStaticStringPool()),
6566
});
6667
}
6768

@@ -85,6 +86,7 @@ TEST_CASE("JsonDocument constructor") {
8586
REQUIRE(doc2.as<std::string>() == "[\"hello\"]");
8687
REQUIRE(spyingAllocator.log() == AllocatorLog{
8788
Allocate(sizeofPool()),
89+
Allocate(sizeofStaticStringPool()),
8890
});
8991
}
9092

0 commit comments

Comments
 (0)