Added string deduplication (closes #1303)

This commit is contained in:
Benoit Blanchon
2020-07-21 20:15:31 +02:00
parent 8ef226bcb8
commit 764ff2cd53
31 changed files with 574 additions and 156 deletions

View File

@ -5,6 +5,7 @@ HEAD
---- ----
* Added comparisons (`>`, `>=`, `==`, `!=`, `<`, and `<=`) between `JsonVariant`s * Added comparisons (`>`, `>=`, `==`, `!=`, `<`, and `<=`) between `JsonVariant`s
* Added string deduplication (issue #1303)
v6.15.2 (2020-05-15) v6.15.2 (2020-05-15)
------- -------

View File

@ -31,6 +31,7 @@ ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things).
* [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/?utm_source=github&utm_medium=readme) * [Consumes roughly 10% less RAM than the "official" Arduino_JSON library](https://arduinojson.org/2019/11/19/arduinojson-vs-arduino_json/?utm_source=github&utm_medium=readme)
* [Fixed memory allocation, no heap fragmentation](https://arduinojson.org/v6/api/jsondocument/?utm_source=github&utm_medium=readme) * [Fixed memory allocation, no heap fragmentation](https://arduinojson.org/v6/api/jsondocument/?utm_source=github&utm_medium=readme)
* [Optionally works without heap memory (zero malloc)](https://arduinojson.org/v6/api/staticjsondocument/?utm_source=github&utm_medium=readme) * [Optionally works without heap memory (zero malloc)](https://arduinojson.org/v6/api/staticjsondocument/?utm_source=github&utm_medium=readme)
* Deduplicates strings
* Versatile * Versatile
* [Supports custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/?utm_source=github&utm_medium=readme) * [Supports custom allocators (to use external RAM chip, for example)](https://arduinojson.org/v6/how-to/use-external-ram-on-esp32/?utm_source=github&utm_medium=readme)
* Supports [Arduino's `String`](https://arduinojson.org/v6/api/config/enable_arduino_string/) and [STL's `std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme) * Supports [Arduino's `String`](https://arduinojson.org/v6/api/config/enable_arduino_string/) and [STL's `std::string`](https://arduinojson.org/v6/api/config/enable_std_string/?utm_source=github&utm_medium=readme)

View File

@ -9,6 +9,9 @@
// Reproduces Arduino's String class // Reproduces Arduino's String class
class String { class String {
public: public:
String() {}
explicit String(const char* s) : _str(s) {}
String& operator+=(const char* rhs) { String& operator+=(const char* rhs) {
_str += rhs; _str += rhs;
return *this; return *this;

View File

@ -239,7 +239,7 @@ TEST_CASE("Filtering") {
10, 10,
DeserializationError::Ok, DeserializationError::Ok,
"[{\"example\":1},{\"example\":3}]", "[{\"example\":1},{\"example\":3}]",
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16 JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8
}, },
{ {
"[',2,3]", "[',2,3]",

View File

@ -74,16 +74,16 @@ TEST_CASE("Invalid JSON string") {
} }
} }
TEST_CASE("Not enough room to duplicate the string") { TEST_CASE("Not enough room to save the key") {
DynamicJsonDocument doc(JSON_OBJECT_SIZE(0)); DynamicJsonDocument doc(JSON_OBJECT_SIZE(1) + 8);
SECTION("Quoted string") { SECTION("Quoted string") {
REQUIRE(deserializeJson(doc, "{\"example\":1}") == REQUIRE(deserializeJson(doc, "{\"accuracy\":1}") ==
DeserializationError::NoMemory); DeserializationError::NoMemory);
} }
SECTION("Non-quoted string") { SECTION("Non-quoted string") {
REQUIRE(deserializeJson(doc, "{example:1}") == REQUIRE(deserializeJson(doc, "{accuracy:1}") ==
DeserializationError::NoMemory); DeserializationError::NoMemory);
} }
} }

View File

@ -212,7 +212,7 @@ TEST_CASE("StaticJsonDocument") {
SECTION("garbageCollect()") { SECTION("garbageCollect()") {
StaticJsonDocument<256> doc; StaticJsonDocument<256> doc;
doc[std::string("example")] = std::string("example"); doc[std::string("example")] = std::string("jukebox");
doc.remove("example"); doc.remove("example");
REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 16); REQUIRE(doc.memoryUsage() == JSON_OBJECT_SIZE(1) + 16);

View File

@ -4,8 +4,8 @@
add_executable(MemoryPoolTests add_executable(MemoryPoolTests
allocVariant.cpp allocVariant.cpp
allocString.cpp
clear.cpp clear.cpp
saveString.cpp
size.cpp size.cpp
StringCopier.cpp StringCopier.cpp
) )

View File

@ -38,8 +38,47 @@ TEST_CASE("StringCopier") {
str.startString(&pool); str.startString(&pool);
str.append('h'); str.append('h');
str.commit(&pool); str.save(&pool);
REQUIRE(1 == pool.size()); REQUIRE(1 == pool.size());
} }
} }
static const char* addStringToPool(MemoryPool* pool, const char* s) {
StringCopier str;
str.startString(pool);
str.append(s);
str.append('\0');
return str.save(pool);
}
TEST_CASE("StringCopier::save() deduplicates strings") {
char buffer[4096];
MemoryPool pool(buffer, 4096);
SECTION("Basic") {
const char* s1 = addStringToPool(&pool, "hello");
const char* s2 = addStringToPool(&pool, "world");
const char* s3 = addStringToPool(&pool, "hello");
REQUIRE(s1 == s3);
REQUIRE(s2 != s3);
REQUIRE(pool.size() == 12);
}
SECTION("Requires terminator") {
const char* s1 = addStringToPool(&pool, "hello world");
const char* s2 = addStringToPool(&pool, "hello");
REQUIRE(s2 != s1);
REQUIRE(pool.size() == 12 + 6);
}
SECTION("Don't overrun") {
const char* s1 = addStringToPool(&pool, "hello world");
const char* s2 = addStringToPool(&pool, "wor");
REQUIRE(s2 != s1);
REQUIRE(pool.size() == 12 + 4);
}
}

View File

@ -1,67 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#include <ArduinoJson/Memory/MemoryPool.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("MemoryPool::allocFrozenString()") {
const size_t poolCapacity = 64;
const size_t longestString = poolCapacity;
char buffer[poolCapacity];
MemoryPool pool(buffer, poolCapacity);
SECTION("Returns different addresses") {
char *a = pool.allocFrozenString(1);
char *b = pool.allocFrozenString(1);
REQUIRE(a != b);
}
SECTION("Returns NULL when full") {
void *p1 = pool.allocFrozenString(longestString);
REQUIRE(p1 != 0);
void *p2 = pool.allocFrozenString(1);
REQUIRE(p2 == 0);
}
SECTION("Returns NULL when pool is too small") {
void *p = pool.allocFrozenString(longestString + 1);
REQUIRE(0 == p);
}
SECTION("Returns NULL when buffer is NULL") {
MemoryPool pool2(0, poolCapacity);
REQUIRE(0 == pool2.allocFrozenString(2));
}
SECTION("Returns NULL when capacity is 0") {
MemoryPool pool2(buffer, 0);
REQUIRE(0 == pool2.allocFrozenString(2));
}
SECTION("Returns same address after clear()") {
void *a = pool.allocFrozenString(1);
pool.clear();
void *b = pool.allocFrozenString(1);
REQUIRE(a == b);
}
SECTION("Can use full capacity when fresh") {
void *a = pool.allocFrozenString(longestString);
REQUIRE(a != 0);
}
SECTION("Can use full capacity after clear") {
pool.allocFrozenString(longestString);
pool.clear();
void *a = pool.allocFrozenString(longestString);
REQUIRE(a != 0);
}
}

View File

@ -3,6 +3,7 @@
// MIT License // MIT License
#include <ArduinoJson/Memory/MemoryPool.hpp> #include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp> #include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE; using namespace ARDUINOJSON_NAMESPACE;
@ -21,8 +22,8 @@ TEST_CASE("MemoryPool::clear()") {
} }
SECTION("Discards allocated strings") { SECTION("Discards allocated strings") {
pool.allocFrozenString(10); pool.saveString(adaptString(const_cast<char *>("123456789")));
REQUIRE(pool.size() > 0); REQUIRE(pool.size() == 10);
pool.clear(); pool.clear();

View File

@ -0,0 +1,81 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Strings/StringAdapters.hpp>
#include <catch.hpp>
using namespace ARDUINOJSON_NAMESPACE;
static const char *saveString(MemoryPool &pool, const char *s) {
return pool.saveString(adaptString(const_cast<char *>(s)));
}
TEST_CASE("MemoryPool::saveString()") {
char buffer[32];
MemoryPool pool(buffer, 32);
SECTION("Duplicates different strings") {
const char *a = saveString(pool, "hello");
const char *b = saveString(pool, "world");
REQUIRE(a != b);
REQUIRE(pool.size() == 6 + 6);
}
SECTION("Deduplicates identical strings") {
const char *a = saveString(pool, "hello");
const char *b = saveString(pool, "hello");
REQUIRE(a == b);
REQUIRE(pool.size() == 6);
}
SECTION("Returns NULL when full") {
REQUIRE(pool.capacity() == 32);
const void *p1 = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
REQUIRE(p1 != 0);
REQUIRE(pool.size() == 32);
const void *p2 = saveString(pool, "b");
REQUIRE(p2 == 0);
}
SECTION("Returns NULL when pool is too small") {
const void *p = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
REQUIRE(0 == p);
}
SECTION("Returns NULL when buffer is NULL") {
MemoryPool pool2(0, 32);
REQUIRE(0 == saveString(pool2, "a"));
}
SECTION("Returns NULL when capacity is 0") {
MemoryPool pool2(buffer, 0);
REQUIRE(0 == saveString(pool2, "a"));
}
SECTION("Returns same address after clear()") {
const void *a = saveString(pool, "hello");
pool.clear();
const void *b = saveString(pool, "world");
REQUIRE(a == b);
}
SECTION("Can use full capacity when fresh") {
const void *a = saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
REQUIRE(a != 0);
}
SECTION("Can use full capacity after clear") {
saveString(pool, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
pool.clear();
const void *a = saveString(pool, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
REQUIRE(a != 0);
}
}

View File

@ -22,24 +22,6 @@ TEST_CASE("MemoryPool::size()") {
REQUIRE(0 == pool.size()); REQUIRE(0 == pool.size());
} }
SECTION("Decreases after freezeString()") {
StringSlot a = pool.allocExpandableString();
pool.freezeString(a, 1);
REQUIRE(pool.size() == 1);
StringSlot b = pool.allocExpandableString();
pool.freezeString(b, 1);
REQUIRE(pool.size() == 2);
}
SECTION("Increases after allocFrozenString()") {
pool.allocFrozenString(1);
REQUIRE(pool.size() == 1);
pool.allocFrozenString(2);
REQUIRE(pool.size() == 3);
}
SECTION("Doesn't grow when memory pool is full") { SECTION("Doesn't grow when memory pool is full") {
const size_t variantCount = sizeof(buffer) / sizeof(VariantSlot); const size_t variantCount = sizeof(buffer) / sizeof(VariantSlot);

View File

@ -6,6 +6,7 @@
#include "progmem_emulation.hpp" #include "progmem_emulation.hpp"
#include "weird_strcmp.hpp" #include "weird_strcmp.hpp"
#include <ArduinoJson/Strings/ArduinoStringAdapter.hpp>
#include <ArduinoJson/Strings/ConstRamStringAdapter.hpp> #include <ArduinoJson/Strings/ConstRamStringAdapter.hpp>
#include <ArduinoJson/Strings/FlashStringAdapter.hpp> #include <ArduinoJson/Strings/FlashStringAdapter.hpp>
#include <ArduinoJson/Strings/SizedRamStringAdapter.hpp> #include <ArduinoJson/Strings/SizedRamStringAdapter.hpp>
@ -114,6 +115,21 @@ TEST_CASE("std::string") {
CHECK(adapter.size() == 5); CHECK(adapter.size() == 5);
} }
TEST_CASE("Arduino String") {
::String str("bravo");
ArduinoStringAdapter adapter = adaptString(str);
CHECK(adapter.compare(NULL) > 0);
CHECK(adapter.compare("alpha") > 0);
CHECK(adapter.compare("bravo") == 0);
CHECK(adapter.compare("charlie") < 0);
CHECK(adapter.equals("bravo"));
CHECK_FALSE(adapter.equals("charlie"));
CHECK(adapter.size() == 5);
}
TEST_CASE("custom_string") { TEST_CASE("custom_string") {
custom_string str("bravo"); custom_string str("bravo");
StlStringAdapter<custom_string> adapter = adaptString(str); StlStringAdapter<custom_string> adapter = adaptString(str);

View File

@ -18,6 +18,8 @@ add_executable(MixedConfigurationTests
enable_nan_0.cpp enable_nan_0.cpp
enable_nan_1.cpp enable_nan_1.cpp
enable_progmem_1.cpp enable_progmem_1.cpp
enable_string_deduplication_0.cpp
enable_string_deduplication_1.cpp
use_double_0.cpp use_double_0.cpp
use_double_1.cpp use_double_1.cpp
use_long_long_0.cpp use_long_long_0.cpp

View File

@ -0,0 +1,125 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#include "progmem_emulation.hpp"
#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
#define ARDUINOJSON_ENABLE_PROGMEM 1
#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 0
#include <ArduinoJson.h>
#include <catch.hpp>
TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
StaticJsonDocument<1024> doc;
SECTION("deserializeJson()") {
SECTION("Deduplicate values") {
deserializeJson(doc, "[\"example\",\"example\"]");
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
}
SECTION("Deduplicate keys") {
deserializeJson(doc, "[{\"example\":1},{\"example\":2}]");
CHECK(doc.memoryUsage() ==
2 * JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2) + 16);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 != key2);
}
}
SECTION("JsonDocument") {
SECTION("values") {
SECTION("std::string") {
doc.add(std::string("example"));
doc.add(std::string("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
}
SECTION("char*") {
char value[] = "example";
doc.add(value);
doc.add(value);
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
}
SECTION("Arduino String") {
doc.add(String("example"));
doc.add(String("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
}
SECTION("Flash string") {
doc.add(F("example"));
doc.add(F("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
}
}
SECTION("keys") {
SECTION("std::string") {
doc[0][std::string("example")] = 1;
doc[1][std::string("example")] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 != key2);
}
SECTION("char*") {
char key[] = "example";
doc[0][key] = 1;
doc[1][key] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 != key2);
}
SECTION("Arduino String") {
doc[0][String("example")] = 1;
doc[1][String("example")] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 != key2);
}
SECTION("Flash string") {
doc[0][F("example")] = 1;
doc[1][F("example")] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 16);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 != key2);
}
}
}
}

View File

@ -0,0 +1,124 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#include "progmem_emulation.hpp"
#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
#define ARDUINOJSON_ENABLE_PROGMEM 1
#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1
#include <ArduinoJson.h>
#include <catch.hpp>
TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
StaticJsonDocument<1024> doc;
SECTION("deserializeJson()") {
SECTION("Deduplicate values") {
deserializeJson(doc, "[\"example\",\"example\"]");
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
}
SECTION("Deduplicate keys") {
deserializeJson(doc, "[{\"example\":1},{\"example\":2}]");
CHECK(doc.memoryUsage() ==
2 * JSON_OBJECT_SIZE(1) + JSON_ARRAY_SIZE(2) + 8);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 == key2);
}
}
SECTION("JsonDocument") {
SECTION("values") {
SECTION("std::string") {
doc.add(std::string("example"));
doc.add(std::string("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
}
SECTION("char*") {
char value[] = "example";
doc.add(value);
doc.add(value);
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
}
SECTION("Arduino String") {
doc.add(String("example"));
doc.add(String("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
}
SECTION("Flash string") {
doc.add(F("example"));
doc.add(F("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
}
}
SECTION("keys") {
SECTION("std::string") {
doc[0][std::string("example")] = 1;
doc[1][std::string("example")] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 == key2);
}
SECTION("char*") {
char key[] = "example";
doc[0][key] = 1;
doc[1][key] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 == key2);
}
SECTION("Arduino String") {
doc[0][String("example")] = 1;
doc[1][String("example")] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 == key2);
}
SECTION("Flash string") {
doc[0][F("example")] = 1;
doc[1][F("example")] = 2;
CHECK(doc.memoryUsage() ==
JSON_ARRAY_SIZE(2) + 2 * JSON_OBJECT_SIZE(1) + 8);
const char* key1 = doc[0].as<JsonObject>().begin()->key().c_str();
const char* key2 = doc[1].as<JsonObject>().begin()->key().c_str();
CHECK(key1 == key2);
}
}
}
}

View File

@ -215,6 +215,10 @@
#define ARDUINOJSON_TAB " " #define ARDUINOJSON_TAB " "
#endif #endif
#ifndef ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
#define ARDUINOJSON_ENABLE_STRING_DEDUPLICATION 1
#endif
#ifndef ARDUINOJSON_STRING_BUFFER_SIZE #ifndef ARDUINOJSON_STRING_BUFFER_SIZE
#define ARDUINOJSON_STRING_BUFFER_SIZE 32 #define ARDUINOJSON_STRING_BUFFER_SIZE 32
#endif #endif

View File

@ -212,8 +212,6 @@ class JsonDeserializer {
// Read each key value pair // Read each key value pair
for (;;) { for (;;) {
_stringStorage.startString(_pool);
// Parse key // Parse key
err = parseKey(); err = parseKey();
if (err) if (err)
@ -233,7 +231,9 @@ class JsonDeserializer {
if (memberFilter.allow()) { if (memberFilter.allow()) {
VariantData *variant = object.getMember(adaptString(key)); VariantData *variant = object.getMember(adaptString(key));
if (!variant) { if (!variant) {
_stringStorage.commit(_pool); // Save key in memory pool.
// This MUST be done before adding the slot.
key = _stringStorage.save(_pool);
// Allocate slot in object // Allocate slot in object
VariantSlot *slot = object.addSlot(_pool); VariantSlot *slot = object.addSlot(_pool);
@ -325,6 +325,7 @@ class JsonDeserializer {
} }
DeserializationError parseKey() { DeserializationError parseKey() {
_stringStorage.startString(_pool);
if (isQuote(current())) { if (isQuote(current())) {
return parseQuotedString(); return parseQuotedString();
} else { } else {
@ -337,8 +338,8 @@ class JsonDeserializer {
DeserializationError err = parseQuotedString(); DeserializationError err = parseQuotedString();
if (err) if (err)
return err; return err;
_stringStorage.commit(_pool); const char *value = _stringStorage.save(_pool);
variant.setOwnedString(make_not_null(_stringStorage.c_str())); variant.setOwnedString(make_not_null(value));
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@ -51,40 +51,43 @@ class MemoryPool {
return allocRight<VariantSlot>(); return allocRight<VariantSlot>();
} }
char* allocFrozenString(size_t n) {
if (!canAlloc(n))
return 0;
char* s = _left;
_left += n;
checkInvariants();
return s;
}
template <typename TAdaptedString> template <typename TAdaptedString>
char* saveString(const TAdaptedString& str) { const char* saveString(const TAdaptedString& str) {
if (str.isNull()) if (str.isNull())
return 0; return 0;
#if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
const char* existingCopy = findString(str.begin());
if (existingCopy)
return existingCopy;
#endif
size_t n = str.size(); size_t n = str.size();
char* dup = allocFrozenString(n + 1);
if (dup) { char* newCopy = allocString(n + 1);
str.copyTo(dup, n); if (newCopy) {
dup[n] = 0; // force null-terminator str.copyTo(newCopy, n);
newCopy[n] = 0; // force null-terminator
} }
return newCopy;
}
void getFreeZone(char** zoneStart, size_t* zoneSize) const {
*zoneStart = _left;
*zoneSize = size_t(_right - _left);
}
const char* saveStringFromFreeZone(size_t len) {
#if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
const char* dup = findString(_left);
if (dup)
return dup; return dup;
} #endif
StringSlot allocExpandableString() { const char* str = _left;
StringSlot s; _left += len;
s.value = _left;
s.size = size_t(_right - _left);
checkInvariants();
return s;
}
void freezeString(StringSlot& s, size_t newSize) {
_left = (s.value + newSize);
s.size = newSize;
checkInvariants(); checkInvariants();
return str;
} }
void clear() { void clear() {
@ -100,18 +103,6 @@ class MemoryPool {
return _begin <= p && p < _end; return _begin <= p && p < _end;
} }
template <typename T>
T* allocRight() {
return reinterpret_cast<T*>(allocRight(sizeof(T)));
}
void* allocRight(size_t bytes) {
if (!canAlloc(bytes))
return 0;
_right -= bytes;
return _right;
}
// Workaround for missing placement new // Workaround for missing placement new
void* operator new(size_t, void* p) { void* operator new(size_t, void* p) {
return p; return p;
@ -163,6 +154,46 @@ class MemoryPool {
ARDUINOJSON_ASSERT(isAligned(_right)); ARDUINOJSON_ASSERT(isAligned(_right));
} }
#if ARDUINOJSON_ENABLE_STRING_DEDUPLICATION
template <typename TIterator>
const char* findString(TIterator str) {
for (char* next = _begin; next < _left; ++next) {
char* begin = next;
// try to match
for (TIterator it = str; *it == *next; ++it) {
if (*next++ == 0)
return begin;
}
// jump to next terminator
while (*next) ++next;
}
return 0;
}
#endif
char* allocString(size_t n) {
if (!canAlloc(n))
return 0;
char* s = _left;
_left += n;
checkInvariants();
return s;
}
template <typename T>
T* allocRight() {
return reinterpret_cast<T*>(allocRight(sizeof(T)));
}
void* allocRight(size_t bytes) {
if (!canAlloc(bytes))
return 0;
_right -= bytes;
return _right;
}
char *_begin, *_left, *_right, *_end; char *_begin, *_left, *_right, *_end;
}; };

View File

@ -248,8 +248,8 @@ class MsgPackDeserializer {
_stringStorage.append('\0'); _stringStorage.append('\0');
if (!_stringStorage.isValid()) if (!_stringStorage.isValid())
return DeserializationError::NoMemory; return DeserializationError::NoMemory;
_stringStorage.commit(_pool);
result = _stringStorage.c_str(); result = _stringStorage.save(_pool);
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@ -16,16 +16,17 @@
#define ARDUINOJSON_CONCAT8(A, B, C, D, E, F, G, H) \ #define ARDUINOJSON_CONCAT8(A, B, C, D, E, F, G, H) \
ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT4(A, B, C, D), \ ARDUINOJSON_CONCAT2(ARDUINOJSON_CONCAT4(A, B, C, D), \
ARDUINOJSON_CONCAT4(E, F, G, H)) ARDUINOJSON_CONCAT4(E, F, G, H))
#define ARDUINOJSON_CONCAT12(A, B, C, D, E, F, G, H, I, J, K, L) \ #define ARDUINOJSON_CONCAT13(A, B, C, D, E, F, G, H, I, J, K, L, M) \
ARDUINOJSON_CONCAT8(A, B, C, D, E, F, G, \ ARDUINOJSON_CONCAT8(A, B, C, D, E, ARDUINOJSON_CONCAT4(F, G, H, I), \
ARDUINOJSON_CONCAT4(H, I, J, ARDUINOJSON_CONCAT2(K, L))) ARDUINOJSON_CONCAT2(J, K), ARDUINOJSON_CONCAT2(L, M))
#define ARDUINOJSON_NAMESPACE \ #define ARDUINOJSON_NAMESPACE \
ARDUINOJSON_CONCAT12( \ ARDUINOJSON_CONCAT13( \
ArduinoJson, ARDUINOJSON_VERSION_MAJOR, ARDUINOJSON_VERSION_MINOR, \ ArduinoJson, ARDUINOJSON_VERSION_MAJOR, ARDUINOJSON_VERSION_MINOR, \
ARDUINOJSON_VERSION_REVISION, _, ARDUINOJSON_USE_LONG_LONG, \ ARDUINOJSON_VERSION_REVISION, _, ARDUINOJSON_USE_LONG_LONG, \
ARDUINOJSON_USE_DOUBLE, ARDUINOJSON_DECODE_UNICODE, \ ARDUINOJSON_USE_DOUBLE, ARDUINOJSON_DECODE_UNICODE, \
ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \ ARDUINOJSON_ENABLE_NAN, ARDUINOJSON_ENABLE_INFINITY, \
ARDUINOJSON_ENABLE_PROGMEM, ARDUINOJSON_ENABLE_COMMENTS) ARDUINOJSON_ENABLE_PROGMEM, ARDUINOJSON_ENABLE_COMMENTS, \
ARDUINOJSON_ENABLE_STRING_DEDUPLICATION)
#endif #endif

View File

@ -11,13 +11,13 @@ namespace ARDUINOJSON_NAMESPACE {
class StringCopier { class StringCopier {
public: public:
void startString(MemoryPool* pool) { void startString(MemoryPool* pool) {
_slot = pool->allocExpandableString(); pool->getFreeZone(&_ptr, &_capacity);
_size = 0; _size = 0;
} }
void commit(MemoryPool* pool) { const char* save(MemoryPool* pool) {
ARDUINOJSON_ASSERT(_slot.value); ARDUINOJSON_ASSERT(_ptr);
pool->freezeString(_slot, _size); return pool->saveStringFromFreeZone(_size);
} }
void append(const char* s) { void append(const char* s) {
@ -29,27 +29,28 @@ class StringCopier {
} }
void append(char c) { void append(char c) {
if (!_slot.value) if (!_ptr)
return; return;
if (_size >= _slot.size) { if (_size >= _capacity) {
_slot.value = 0; _ptr = 0;
return; return;
} }
_slot.value[_size++] = c; _ptr[_size++] = c;
} }
bool isValid() { bool isValid() {
return _slot.value != 0; return _ptr != 0;
} }
const char* c_str() { const char* c_str() {
return _slot.value; return _ptr;
} }
private: private:
char* _ptr;
size_t _size; size_t _size;
StringSlot _slot; size_t _capacity;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -16,7 +16,9 @@ class StringMover {
_startPtr = _writePtr; _startPtr = _writePtr;
} }
void commit(MemoryPool*) const {} const char* save(MemoryPool*) const {
return _startPtr;
}
void append(char c) { void append(char c) {
*_writePtr++ = c; *_writePtr++ = c;

View File

@ -39,6 +39,10 @@ class ArduinoStringAdapter {
return _str->length(); return _str->length();
} }
const char* begin() const {
return _str->c_str();
}
typedef storage_policies::store_by_copy storage_policy; typedef storage_policies::store_by_copy storage_policy;
private: private:

View File

@ -39,6 +39,10 @@ class ConstRamStringAdapter {
return _str; return _str;
} }
const char* begin() const {
return _str;
}
typedef storage_policies::store_by_address storage_policy; typedef storage_policies::store_by_address storage_policy;
protected: protected:

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <ArduinoJson/Polyfills/pgmspace.hpp> #include <ArduinoJson/Polyfills/pgmspace.hpp>
#include <ArduinoJson/Strings/FlashStringIterator.hpp>
#include <ArduinoJson/Strings/IsString.hpp> #include <ArduinoJson/Strings/IsString.hpp>
#include <ArduinoJson/Strings/StoragePolicy.hpp> #include <ArduinoJson/Strings/StoragePolicy.hpp>
@ -42,6 +43,10 @@ class FlashStringAdapter {
return strlen_P(reinterpret_cast<const char*>(_str)); return strlen_P(reinterpret_cast<const char*>(_str));
} }
FlashStringIterator begin() const {
return FlashStringIterator(_str);
}
typedef storage_policies::store_by_copy storage_policy; typedef storage_policies::store_by_copy storage_policy;
private: private:

View File

@ -0,0 +1,44 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2020
// MIT License
#pragma once
namespace ARDUINOJSON_NAMESPACE {
class FlashStringIterator {
public:
explicit FlashStringIterator(const __FlashStringHelper* ptr)
: _ptr(reinterpret_cast<const char*>(ptr)) {}
explicit FlashStringIterator(const char* ptr) : _ptr(ptr) {}
FlashStringIterator operator+(ptrdiff_t d) const {
return FlashStringIterator(_ptr + d);
}
ptrdiff_t operator-(FlashStringIterator other) const {
return _ptr - other._ptr;
}
FlashStringIterator operator++(int) {
return FlashStringIterator(_ptr++);
}
FlashStringIterator operator++() {
return FlashStringIterator(++_ptr);
}
bool operator!=(FlashStringIterator other) const {
return _ptr != other._ptr;
}
char operator*() const {
return char(pgm_read_byte(_ptr));
}
private:
const char* _ptr;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <ArduinoJson/Namespace.hpp> #include <ArduinoJson/Namespace.hpp>
#include <ArduinoJson/Strings/FlashStringIterator.hpp>
#include <ArduinoJson/Strings/IsString.hpp> #include <ArduinoJson/Strings/IsString.hpp>
#include <ArduinoJson/Strings/StoragePolicy.hpp> #include <ArduinoJson/Strings/StoragePolicy.hpp>
@ -41,6 +42,10 @@ class SizedFlashStringAdapter {
return _size; return _size;
} }
FlashStringIterator begin() const {
return FlashStringIterator(_str);
}
typedef storage_policies::store_by_copy storage_policy; typedef storage_policies::store_by_copy storage_policy;
private: private:

View File

@ -36,6 +36,10 @@ class SizedRamStringAdapter {
return _size; return _size;
} }
const char* begin() const {
return _str;
}
typedef storage_policies::store_by_copy storage_policy; typedef storage_policies::store_by_copy storage_policy;
private: private:

View File

@ -41,6 +41,10 @@ class StlStringAdapter {
return _str->size(); return _str->size();
} }
const char* begin() const {
return _str->c_str();
}
typedef storage_policies::store_by_copy storage_policy; typedef storage_policies::store_by_copy storage_policy;
private: private:

View File

@ -192,7 +192,7 @@ class VariantData {
template <typename T> template <typename T>
bool setOwnedRaw(SerializedValue<T> value, MemoryPool *pool) { bool setOwnedRaw(SerializedValue<T> value, MemoryPool *pool) {
char *dup = pool->saveString(adaptString(value.data(), value.size())); const char *dup = pool->saveString(adaptString(value.data(), value.size()));
if (dup) { if (dup) {
setType(VALUE_IS_OWNED_RAW); setType(VALUE_IS_OWNED_RAW);
_content.asRaw.data = dup; _content.asRaw.data = dup;