Skip to content

Commit d8f3058

Browse files
committed
Store the strings in the heap
1 parent 7c0fa7c commit d8f3058

27 files changed

+423
-366
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ HEAD
1111
* Remove `JSON_ARRAY_SIZE()`, `JSON_OBJECT_SIZE()`, and `JSON_STRING_SIZE()`
1212
* Remove `ARDUINOJSON_ENABLE_STRING_DEDUPLICATION` (string deduplication cannot be enabled anymore)
1313
* Remove `JsonDocument::capacity()`
14+
* Store the strings in the heap

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
3131
* [Twice smaller than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
3232
* [Almost 10% faster than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
3333
* [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/)
34-
* [Fixed memory allocation, no heap fragmentation](https://arduinojson.org/v6/api/jsondocument/)
3534
* [Deduplicates strings](https://arduinojson.org/news/2020/08/01/version-6-16-0/)
3635
* Versatile
3736
* Supports [custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/)

extras/tests/JsonDeserializer/string.cpp

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <ArduinoJson.h>
77
#include <catch.hpp>
88

9+
#include "Allocators.hpp"
10+
911
using ArduinoJson::detail::sizeofArray;
1012
using ArduinoJson::detail::sizeofObject;
1113
using ArduinoJson::detail::sizeofString;
@@ -96,42 +98,67 @@ TEST_CASE("Invalid JSON string") {
9698
}
9799
}
98100

99-
TEST_CASE("Not enough room to save the key") {
100-
JsonDocument doc(sizeofObject(1) + sizeofString(7));
101+
TEST_CASE("Allocation of the key fails") {
102+
TimebombAllocator timebombAllocator(1);
103+
SpyingAllocator spyingAllocator(&timebombAllocator);
104+
JsonDocument doc(1024, &spyingAllocator);
101105

102-
SECTION("Quoted string") {
106+
SECTION("Quoted string, first member") {
103107
REQUIRE(deserializeJson(doc, "{\"example\":1}") ==
104-
DeserializationError::Ok);
105-
REQUIRE(deserializeJson(doc, "{\"accuracy\":1}") ==
106108
DeserializationError::NoMemory);
109+
REQUIRE(spyingAllocator.log() ==
110+
AllocatorLog() << AllocatorLog::Allocate(1024)
111+
<< AllocatorLog::AllocateFail(sizeofString(31)));
112+
}
113+
114+
SECTION("Quoted string, second member") {
115+
timebombAllocator.setCountdown(2);
107116
REQUIRE(deserializeJson(doc, "{\"hello\":1,\"world\"}") ==
108-
DeserializationError::NoMemory); // fails in the second string
117+
DeserializationError::NoMemory);
118+
REQUIRE(spyingAllocator.log() ==
119+
AllocatorLog() << AllocatorLog::Allocate(1024)
120+
<< AllocatorLog::Allocate(sizeofString(31))
121+
<< AllocatorLog::Reallocate(sizeofString(31),
122+
sizeofString(5))
123+
<< AllocatorLog::AllocateFail(sizeofString(31)));
109124
}
110125

111-
SECTION("Non-quoted string") {
112-
REQUIRE(deserializeJson(doc, "{example:1}") == DeserializationError::Ok);
113-
REQUIRE(deserializeJson(doc, "{accuracy:1}") ==
126+
SECTION("Non-Quoted string, first member") {
127+
REQUIRE(deserializeJson(doc, "{example:1}") ==
114128
DeserializationError::NoMemory);
129+
REQUIRE(spyingAllocator.log() ==
130+
AllocatorLog() << AllocatorLog::Allocate(1024)
131+
<< AllocatorLog::AllocateFail(sizeofString(31)));
132+
}
133+
134+
SECTION("Non-Quoted string, second member") {
135+
timebombAllocator.setCountdown(2);
115136
REQUIRE(deserializeJson(doc, "{hello:1,world}") ==
116-
DeserializationError::NoMemory); // fails in the second string
137+
DeserializationError::NoMemory);
138+
REQUIRE(spyingAllocator.log() ==
139+
AllocatorLog() << AllocatorLog::Allocate(1024)
140+
<< AllocatorLog::Allocate(sizeofString(31))
141+
<< AllocatorLog::Reallocate(sizeofString(31),
142+
sizeofString(5))
143+
<< AllocatorLog::AllocateFail(sizeofString(31)));
117144
}
118145
}
119146

120-
TEST_CASE("Empty memory pool") {
121-
// NOLINTNEXTLINE(clang-analyzer-optin.portability.UnixAPI)
122-
JsonDocument doc(0);
147+
TEST_CASE("String allocation fails") {
148+
SpyingAllocator spyingAllocator(FailingAllocator::instance());
149+
JsonDocument doc(0, &spyingAllocator);
123150

124151
SECTION("Input is const char*") {
125152
REQUIRE(deserializeJson(doc, "\"hello\"") ==
126153
DeserializationError::NoMemory);
127-
REQUIRE(deserializeJson(doc, "\"\"") == DeserializationError::NoMemory);
154+
REQUIRE(spyingAllocator.log() ==
155+
AllocatorLog() << AllocatorLog::AllocateFail(sizeofString(31)));
128156
}
129157

130158
SECTION("Input is const char*") {
131159
char hello[] = "\"hello\"";
132160
REQUIRE(deserializeJson(doc, hello) == DeserializationError::Ok);
133-
char empty[] = "\"hello\"";
134-
REQUIRE(deserializeJson(doc, empty) == DeserializationError::Ok);
161+
REQUIRE(spyingAllocator.log() == AllocatorLog());
135162
}
136163
}
137164

extras/tests/JsonDocument/assignment.cpp

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,63 +9,59 @@
99

1010
using ArduinoJson::detail::sizeofArray;
1111
using ArduinoJson::detail::sizeofObject;
12+
using ArduinoJson::detail::sizeofString;
1213

1314
TEST_CASE("JsonDocument assignment") {
1415
SpyingAllocator spyingAllocator;
1516

1617
SECTION("Copy assignment same capacity") {
17-
{
18-
JsonDocument doc1(1024, &spyingAllocator);
19-
deserializeJson(doc1, "{\"hello\":\"world\"}");
20-
JsonDocument doc2(1024, &spyingAllocator);
18+
JsonDocument doc1(1024, &spyingAllocator);
19+
deserializeJson(doc1, "{\"hello\":\"world\"}");
20+
JsonDocument doc2(1024, &spyingAllocator);
21+
spyingAllocator.clearLog();
2122

22-
doc2 = doc1;
23+
doc2 = doc1;
2324

24-
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
25-
}
26-
REQUIRE(spyingAllocator.log() == AllocatorLog()
27-
<< AllocatorLog::Allocate(1024)
28-
<< AllocatorLog::Allocate(1024)
29-
<< AllocatorLog::Deallocate(1024)
30-
<< AllocatorLog::Deallocate(1024));
25+
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
26+
27+
REQUIRE(spyingAllocator.log() ==
28+
AllocatorLog() << AllocatorLog::Allocate(sizeofString(5)) // hello
29+
<< AllocatorLog::Allocate(sizeofString(5)) // world
30+
);
3131
}
3232

3333
SECTION("Copy assignment reallocates when capacity is smaller") {
34-
{
35-
JsonDocument doc1(4096, &spyingAllocator);
36-
deserializeJson(doc1, "{\"hello\":\"world\"}");
37-
JsonDocument doc2(8, &spyingAllocator);
34+
JsonDocument doc1(4096, &spyingAllocator);
35+
deserializeJson(doc1, "{\"hello\":\"world\"}");
36+
JsonDocument doc2(8, &spyingAllocator);
37+
spyingAllocator.clearLog();
3838

39-
doc2 = doc1;
39+
doc2 = doc1;
4040

41-
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
42-
}
43-
REQUIRE(spyingAllocator.log() == AllocatorLog()
44-
<< AllocatorLog::Allocate(4096)
45-
<< AllocatorLog::Allocate(8)
46-
<< AllocatorLog::Deallocate(8)
47-
<< AllocatorLog::Allocate(4096)
48-
<< AllocatorLog::Deallocate(4096)
49-
<< AllocatorLog::Deallocate(4096));
41+
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
42+
REQUIRE(spyingAllocator.log() ==
43+
AllocatorLog() << AllocatorLog::Deallocate(8)
44+
<< AllocatorLog::Allocate(4096)
45+
<< AllocatorLog::Allocate(sizeofString(5)) // hello
46+
<< AllocatorLog::Allocate(sizeofString(5)) // world
47+
);
5048
}
5149

5250
SECTION("Copy assignment reallocates when capacity is larger") {
53-
{
54-
JsonDocument doc1(1024, &spyingAllocator);
55-
deserializeJson(doc1, "{\"hello\":\"world\"}");
56-
JsonDocument doc2(4096, &spyingAllocator);
51+
JsonDocument doc1(1024, &spyingAllocator);
52+
deserializeJson(doc1, "{\"hello\":\"world\"}");
53+
JsonDocument doc2(4096, &spyingAllocator);
54+
spyingAllocator.clearLog();
5755

58-
doc2 = doc1;
56+
doc2 = doc1;
5957

60-
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
61-
}
62-
REQUIRE(spyingAllocator.log() == AllocatorLog()
63-
<< AllocatorLog::Allocate(1024)
64-
<< AllocatorLog::Allocate(4096)
65-
<< AllocatorLog::Deallocate(4096)
66-
<< AllocatorLog::Allocate(1024)
67-
<< AllocatorLog::Deallocate(1024)
68-
<< AllocatorLog::Deallocate(1024));
58+
REQUIRE(doc2.as<std::string>() == "{\"hello\":\"world\"}");
59+
REQUIRE(spyingAllocator.log() ==
60+
AllocatorLog() << AllocatorLog::Deallocate(4096)
61+
<< AllocatorLog::Allocate(1024)
62+
<< AllocatorLog::Allocate(sizeofString(5)) // hello
63+
<< AllocatorLog::Allocate(sizeofString(5)) // world
64+
);
6965
}
7066

7167
SECTION("Move assign") {
@@ -79,11 +75,13 @@ TEST_CASE("JsonDocument assignment") {
7975
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
8076
REQUIRE(doc1.as<std::string>() == "null");
8177
}
82-
REQUIRE(spyingAllocator.log() == AllocatorLog()
83-
<< AllocatorLog::Allocate(4096)
84-
<< AllocatorLog::Allocate(8)
85-
<< AllocatorLog::Deallocate(8)
86-
<< AllocatorLog::Deallocate(4096));
78+
REQUIRE(spyingAllocator.log() ==
79+
AllocatorLog() << AllocatorLog::Allocate(4096)
80+
<< AllocatorLog::Allocate(sizeofString(31))
81+
<< AllocatorLog::Allocate(8)
82+
<< AllocatorLog::Deallocate(8)
83+
<< AllocatorLog::Deallocate(sizeofString(31))
84+
<< AllocatorLog::Deallocate(4096));
8785
}
8886

8987
SECTION("Assign from JsonObject") {

extras/tests/JsonDocument/constructor.cpp

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "Allocators.hpp"
99

1010
using ArduinoJson::detail::addPadding;
11+
using ArduinoJson::detail::sizeofString;
1112

1213
TEST_CASE("JsonDocument constructor") {
1314
SpyingAllocator spyingAllocator;
@@ -29,11 +30,15 @@ TEST_CASE("JsonDocument constructor") {
2930
REQUIRE(doc1.as<std::string>() == "The size of this string is 32!!");
3031
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
3132
}
32-
REQUIRE(spyingAllocator.log() == AllocatorLog()
33-
<< AllocatorLog::Allocate(4096)
34-
<< AllocatorLog::Allocate(4096)
35-
<< AllocatorLog::Deallocate(4096)
36-
<< AllocatorLog::Deallocate(4096));
33+
REQUIRE(spyingAllocator.log() ==
34+
AllocatorLog() << AllocatorLog::Allocate(4096)
35+
<< AllocatorLog::Allocate(sizeofString(31))
36+
<< AllocatorLog::Allocate(4096)
37+
<< AllocatorLog::Allocate(sizeofString(31))
38+
<< AllocatorLog::Deallocate(sizeofString(31))
39+
<< AllocatorLog::Deallocate(4096)
40+
<< AllocatorLog::Deallocate(sizeofString(31))
41+
<< AllocatorLog::Deallocate(4096));
3742
}
3843

3944
SECTION("JsonDocument(JsonDocument&&)") {
@@ -46,9 +51,11 @@ TEST_CASE("JsonDocument constructor") {
4651
REQUIRE(doc2.as<std::string>() == "The size of this string is 32!!");
4752
REQUIRE(doc1.as<std::string>() == "null");
4853
}
49-
REQUIRE(spyingAllocator.log() == AllocatorLog()
50-
<< AllocatorLog::Allocate(4096)
51-
<< AllocatorLog::Deallocate(4096));
54+
REQUIRE(spyingAllocator.log() ==
55+
AllocatorLog() << AllocatorLog::Allocate(4096)
56+
<< AllocatorLog::Allocate(sizeofString(31))
57+
<< AllocatorLog::Deallocate(sizeofString(31))
58+
<< AllocatorLog::Deallocate(4096));
5259
}
5360

5461
SECTION("JsonDocument(JsonObject)") {
@@ -82,7 +89,9 @@ TEST_CASE("JsonDocument constructor") {
8289
JsonDocument doc2(doc1.as<JsonVariant>(), &spyingAllocator);
8390

8491
REQUIRE(doc2.as<std::string>() == "hello");
85-
REQUIRE(spyingAllocator.log() == AllocatorLog() << AllocatorLog::Allocate(
86-
addPadding(doc1.memoryUsage())));
92+
REQUIRE(
93+
spyingAllocator.log() ==
94+
AllocatorLog() << AllocatorLog::Allocate(addPadding(doc1.memoryUsage()))
95+
<< AllocatorLog::Allocate(sizeofString(5)));
8796
}
8897
}

extras/tests/JsonDocument/garbageCollect.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,27 @@ TEST_CASE("JsonDocument::garbageCollect()") {
2121
deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}");
2222
REQUIRE(doc.memoryUsage() == sizeofObject(2) + 2 * sizeofString(7));
2323
doc.remove("blanket");
24+
spyingAllocator.clearLog();
2425

2526
bool result = doc.garbageCollect();
2627

2728
REQUIRE(result == true);
2829
REQUIRE(doc.memoryUsage() == sizeofObject(1) + sizeofString(7));
2930
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
30-
REQUIRE(spyingAllocator.log() == AllocatorLog()
31-
<< AllocatorLog::Allocate(4096)
32-
<< AllocatorLog::Allocate(4096)
33-
<< AllocatorLog::Deallocate(4096));
31+
REQUIRE(spyingAllocator.log() ==
32+
AllocatorLog() << AllocatorLog::Allocate(4096)
33+
<< AllocatorLog::Allocate(sizeofString(7))
34+
<< AllocatorLog::Deallocate(sizeofString(7))
35+
<< AllocatorLog::Deallocate(sizeofString(7))
36+
<< AllocatorLog::Deallocate(4096));
3437
}
3538

3639
SECTION("when allocation fails") {
3740
deserializeJson(doc, "{\"blanket\":1,\"dancing\":2}");
3841
REQUIRE(doc.memoryUsage() == sizeofObject(2) + 2 * sizeofString(7));
3942
doc.remove("blanket");
4043
controllableAllocator.disable();
44+
spyingAllocator.clearLog();
4145

4246
bool result = doc.garbageCollect();
4347

@@ -46,7 +50,6 @@ TEST_CASE("JsonDocument::garbageCollect()") {
4650
REQUIRE(doc.as<std::string>() == "{\"dancing\":2}");
4751

4852
REQUIRE(spyingAllocator.log() == AllocatorLog()
49-
<< AllocatorLog::Allocate(4096)
5053
<< AllocatorLog::AllocateFail(4096));
5154
}
5255
}

extras/tests/JsonDocument/overflowed.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
#include <ArduinoJson.h>
66
#include <catch.hpp>
77

8+
#include "Allocators.hpp"
9+
810
using ArduinoJson::detail::sizeofArray;
9-
using ArduinoJson::detail::sizeofString;
1011

1112
TEST_CASE("JsonDocument::overflowed()") {
1213
SECTION("returns false on a fresh object") {
@@ -27,13 +28,15 @@ TEST_CASE("JsonDocument::overflowed()") {
2728
}
2829

2930
SECTION("returns true after a failed string copy") {
30-
JsonDocument doc(sizeofArray(1));
31+
ControllableAllocator allocator;
32+
JsonDocument doc(sizeofArray(1), &allocator);
33+
allocator.disable();
3134
doc.add(std::string("example"));
3235
CHECK(doc.overflowed() == true);
3336
}
3437

3538
SECTION("returns false after a successful string copy") {
36-
JsonDocument doc(sizeofArray(1) + sizeofString(7));
39+
JsonDocument doc(sizeofArray(1));
3740
doc.add(std::string("example"));
3841
CHECK(doc.overflowed() == false);
3942
}
@@ -46,12 +49,12 @@ TEST_CASE("JsonDocument::overflowed()") {
4649

4750
SECTION("returns true after a failed deserialization") {
4851
JsonDocument doc(sizeofArray(1));
49-
deserializeJson(doc, "[\"example\"]");
52+
deserializeJson(doc, "[1, 2]");
5053
CHECK(doc.overflowed() == true);
5154
}
5255

5356
SECTION("returns false after a successful deserialization") {
54-
JsonDocument doc(sizeofArray(1) + sizeofString(7));
57+
JsonDocument doc(sizeofArray(1));
5558
deserializeJson(doc, "[\"example\"]");
5659
CHECK(doc.overflowed() == false);
5760
}

0 commit comments

Comments
 (0)