Store static string in a dedicated pool

This commit is contained in:
Benoit Blanchon
2025-02-24 17:48:37 +01:00
parent 907bb43481
commit 08b2400592
25 changed files with 133 additions and 63 deletions

View File

@ -54,7 +54,7 @@ TEST_CASE("BasicJsonDocument") {
doc["hello"] = "world"; doc["hello"] = "world";
auto copy = doc; auto copy = doc;
REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}"); REQUIRE(copy.as<std::string>() == "{\"hello\":\"world\"}");
REQUIRE(allocatorLog == "AA"); REQUIRE(allocatorLog == "AAAA");
} }
SECTION("capacity") { SECTION("capacity") {

View File

@ -275,6 +275,12 @@ inline size_t sizeofPool(
return MemoryPool<VariantData>::slotsToBytes(n); return MemoryPool<VariantData>::slotsToBytes(n);
} }
inline size_t sizeofStaticStringPool(
ArduinoJson::detail::SlotCount n = ARDUINOJSON_POOL_CAPACITY) {
using namespace ArduinoJson::detail;
return MemoryPool<const char*>::slotsToBytes(n);
}
inline size_t sizeofStringBuffer(size_t iteration = 1) { inline size_t sizeofStringBuffer(size_t iteration = 1) {
// returns 31, 63, 127, 255, etc. // returns 31, 63, 127, 255, etc.
auto capacity = ArduinoJson::detail::StringBuilder::initialCapacity; auto capacity = ArduinoJson::detail::StringBuilder::initialCapacity;

View File

@ -56,6 +56,7 @@ TEST_CASE("JsonArray::add(T)") {
REQUIRE(array[0].is<int>() == false); REQUIRE(array[0].is<int>() == false);
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }

View File

@ -104,6 +104,8 @@ TEST_CASE("deserializeJson(MemberProxy)") {
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
REQUIRE(spy.log() == AllocatorLog{}); REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
} }
} }

View File

@ -825,7 +825,9 @@ TEST_CASE("shrink filter") {
deserializeJson(doc, "{}", DeserializationOption::Filter(filter)); deserializeJson(doc, "{}", DeserializationOption::Filter(filter));
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() ==
Reallocate(sizeofPool(), sizeofObject(1)), AllocatorLog{
}); Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
} }

View File

@ -31,6 +31,7 @@ TEST_CASE("ElementProxy::add()") {
REQUIRE(doc.as<std::string>() == "[[\"world\"]]"); REQUIRE(doc.as<std::string>() == "[[\"world\"]]");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }

View File

@ -25,6 +25,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":[42]}");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }
@ -34,6 +35,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }
@ -44,6 +46,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")), Allocate(sizeofString("world")),
}); });
} }
@ -55,8 +58,8 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")), Allocate(sizeofString("world")),
}); });
} }
@ -71,6 +74,7 @@ TEST_CASE("MemberProxy::add()") {
REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":[\"world\"]}");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")), Allocate(sizeofString("world")),
}); });
} }
@ -399,7 +403,7 @@ TEST_CASE("MemberProxy under memory constraints") {
} }
SECTION("value slot allocation fails") { SECTION("value slot allocation fails") {
timebomb.setCountdown(1); timebomb.setCountdown(2);
// fill the pool entirely, but leave one slot for the key // fill the pool entirely, but leave one slot for the key
doc["foo"][ARDUINOJSON_POOL_CAPACITY - 4] = 1; doc["foo"][ARDUINOJSON_POOL_CAPACITY - 4] = 1;
@ -412,6 +416,7 @@ TEST_CASE("MemberProxy under memory constraints") {
REQUIRE(doc.overflowed() == true); REQUIRE(doc.overflowed() == true);
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
AllocateFail(sizeofPool()), AllocateFail(sizeofPool()),
}); });
} }

View File

@ -32,6 +32,7 @@ TEST_CASE("JsonDocument::add(T)") {
REQUIRE(doc.as<std::string>() == "[\"hello\"]"); REQUIRE(doc.as<std::string>() == "[\"hello\"]");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }

View File

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

View File

@ -37,7 +37,9 @@ TEST_CASE("JsonDocument::set()") {
doc.set("example"); doc.set("example");
REQUIRE(doc.as<const char*>() == "example"_s); REQUIRE(doc.as<const char*>() == "example"_s);
REQUIRE(spy.log() == AllocatorLog{}); REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
} }
SECTION("const char*") { SECTION("const char*") {

View File

@ -75,7 +75,11 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "hello"); REQUIRE(doc.as<std::string>() == "hello");
REQUIRE(spyingAllocator.log() == AllocatorLog{}); REQUIRE(spyingAllocator.log() ==
AllocatorLog{
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
});
} }
SECTION("owned string") { SECTION("owned string") {
@ -110,7 +114,9 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofObject(1)), Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
}); });
} }
@ -137,7 +143,9 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofArray(1)), Reallocate(sizeofPool(), sizeofArray(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(1)),
}); });
} }
@ -164,20 +172,23 @@ TEST_CASE("JsonDocument::shrinkToFit()") {
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Reallocate(sizeofPool(), sizeofObject(1)), Reallocate(sizeofPool(), sizeofObject(1)),
Reallocate(sizeofStaticStringPool(), sizeofStaticStringPool(2)),
}); });
} }
SECTION("owned string in object") { SECTION("owned string in object") {
doc["key"] = "abcdefg"_s; doc["key"_s] = "value"_s;
doc.shrinkToFit(); doc.shrinkToFit();
REQUIRE(doc.as<std::string>() == "{\"key\":\"abcdefg\"}"); REQUIRE(doc.as<std::string>() == "{\"key\":\"value\"}");
REQUIRE(spyingAllocator.log() == REQUIRE(spyingAllocator.log() ==
AllocatorLog{ AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofString("abcdefg")), Allocate(sizeofString("key")),
Allocate(sizeofString("value")),
Reallocate(sizeofPool(), sizeofPool(2)), Reallocate(sizeofPool(), sizeofPool(2)),
}); });
} }

View File

@ -112,6 +112,7 @@ TEST_CASE("JsonDocument::operator[] key storage") {
REQUIRE(doc.as<std::string>() == "{\"hello\":0}"); REQUIRE(doc.as<std::string>() == "{\"hello\":0}");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }

View File

@ -26,25 +26,12 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(obj2["hello"] == "world"_s); REQUIRE(obj2["hello"] == "world"_s);
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }
SECTION("copy local string value") { SECTION("copy local string key and value") {
obj1["hello"] = "world"_s; obj1["hello"_s] = "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";
spy.clearLog(); spy.clearLog();
bool success = obj2.set(obj1); bool success = obj2.set(obj1);
@ -54,6 +41,7 @@ TEST_CASE("JsonObject::set()") {
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofString("hello")), Allocate(sizeofString("hello")),
Allocate(sizeofString("world")),
}); });
} }
@ -110,7 +98,7 @@ TEST_CASE("JsonObject::set()") {
} }
SECTION("copy fails in the middle of an array") { SECTION("copy fails in the middle of an array") {
TimebombAllocator timebomb(1); TimebombAllocator timebomb(2);
JsonDocument doc3(&timebomb); JsonDocument doc3(&timebomb);
JsonObject obj3 = doc3.to<JsonObject>(); JsonObject obj3 = doc3.to<JsonObject>();

View File

@ -102,21 +102,25 @@ TEST_CASE("JsonObject::operator[]") {
REQUIRE(42 == obj[key]); REQUIRE(42 == obj[key]);
} }
SECTION("should not duplicate const char*") { SECTION("string literals") {
obj["hello"] = "world"; obj["hello"] = "world";
REQUIRE(spy.log() == AllocatorLog{Allocate(sizeofPool())}); REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
});
} }
SECTION("should duplicate char* value") { SECTION("should duplicate char* value") {
obj["hello"] = const_cast<char*>("world"); obj["hello"] = const_cast<char*>("world");
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")), Allocate(sizeofString("world")),
}); });
} }
SECTION("should duplicate char* key") { SECTION("should duplicate char* key") {
obj[const_cast<char*>("hello")] = "world"; obj[const_cast<char*>("hello")] = 42;
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofString("hello")), Allocate(sizeofString("hello")),
@ -136,12 +140,13 @@ TEST_CASE("JsonObject::operator[]") {
obj["hello"] = "world"_s; obj["hello"] = "world"_s;
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
Allocate(sizeofString("world")), Allocate(sizeofString("world")),
}); });
} }
SECTION("should duplicate std::string key") { SECTION("should duplicate std::string key") {
obj["hello"_s] = "world"; obj["hello"_s] = 42;
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofString("hello")), Allocate(sizeofString("hello")),
@ -158,7 +163,7 @@ TEST_CASE("JsonObject::operator[]") {
} }
SECTION("should duplicate a non-static JsonString key") { SECTION("should duplicate a non-static JsonString key") {
obj[JsonString("hello", false)] = "world"; obj[JsonString("hello", false)] = 42;
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofString("hello")), Allocate(sizeofString("hello")),
@ -166,9 +171,10 @@ TEST_CASE("JsonObject::operator[]") {
} }
SECTION("should not duplicate a static JsonString key") { SECTION("should not duplicate a static JsonString key") {
obj[JsonString("hello", true)] = "world"; obj[JsonString("hello", true)] = 42;
REQUIRE(spy.log() == AllocatorLog{ REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofPool()), Allocate(sizeofPool()),
Allocate(sizeofStaticStringPool()),
}); });
} }

View File

@ -38,13 +38,15 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
REQUIRE(var1.as<std::string>() == "{\"value\":[42]}"); REQUIRE(var1.as<std::string>() == "{\"value\":[42]}");
} }
SECTION("stores const char* by reference") { SECTION("stores string literals by pointer") {
var1.set("hello!!"); var1.set("hello!!");
spyingAllocator.clearLog(); spyingAllocator.clearLog();
var2.set(var1); var2.set(var1);
REQUIRE(spyingAllocator.log() == AllocatorLog{}); REQUIRE(spyingAllocator.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
} }
SECTION("stores char* by copy") { SECTION("stores char* by copy") {

View File

@ -23,7 +23,9 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true); REQUIRE(result == true);
CHECK(variant == CHECK(variant ==
"hello"_s); // linked string cannot contain '\0' at the moment "hello"_s); // linked string cannot contain '\0' at the moment
CHECK(spy.log() == AllocatorLog{}); CHECK(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
} }
SECTION("const char*") { SECTION("const char*") {
@ -137,7 +139,9 @@ TEST_CASE("JsonVariant::set() when there is enough memory") {
REQUIRE(result == true); REQUIRE(result == true);
REQUIRE(variant == "world"); // stores by pointer REQUIRE(variant == "world"); // stores by pointer
REQUIRE(spy.log() == AllocatorLog{}); REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
} }
SECTION("non-static JsonString") { SECTION("non-static JsonString") {

View File

@ -104,6 +104,8 @@ TEST_CASE("deserializeMsgPack(MemberProxy)") {
REQUIRE(err == DeserializationError::Ok); REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}"); REQUIRE(doc.as<std::string>() == "{\"hello\":\"world\",\"value\":[42]}");
REQUIRE(spy.log() == AllocatorLog{}); REQUIRE(spy.log() == AllocatorLog{
Allocate(sizeofStaticStringPool()),
});
} }
} }

View File

@ -34,6 +34,11 @@ class Slot {
return ptr_; return ptr_;
} }
T& operator*() const {
ARDUINOJSON_ASSERT(ptr_ != nullptr);
return *ptr_;
}
T* operator->() const { T* operator->() const {
ARDUINOJSON_ASSERT(ptr_ != nullptr); ARDUINOJSON_ASSERT(ptr_ != nullptr);
return ptr_; return ptr_;

View File

@ -34,6 +34,7 @@ class ResourceManager {
~ResourceManager() { ~ResourceManager() {
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
variantPools_.clear(allocator_); variantPools_.clear(allocator_);
staticStringsPools_.clear(allocator_);
} }
ResourceManager(const ResourceManager&) = delete; ResourceManager(const ResourceManager&) = delete;
@ -42,6 +43,7 @@ class ResourceManager {
friend void swap(ResourceManager& a, ResourceManager& b) { friend void swap(ResourceManager& a, ResourceManager& b) {
swap(a.stringPool_, b.stringPool_); swap(a.stringPool_, b.stringPool_);
swap(a.variantPools_, b.variantPools_); swap(a.variantPools_, b.variantPools_);
swap(a.staticStringsPools_, b.staticStringsPools_);
swap_(a.allocator_, b.allocator_); swap_(a.allocator_, b.allocator_);
swap_(a.overflowed_, b.overflowed_); swap_(a.overflowed_, b.overflowed_);
} }
@ -111,14 +113,28 @@ class ResourceManager {
stringPool_.dereference(s, allocator_); stringPool_.dereference(s, allocator_);
} }
SlotId saveStaticString(const char* s) {
auto slot = staticStringsPools_.allocSlot(allocator_);
if (!slot)
return NULL_SLOT;
*slot = s;
return slot.id();
}
const char* getStaticString(SlotId id) const {
return *staticStringsPools_.getSlot(id);
}
void clear() { void clear() {
variantPools_.clear(allocator_); variantPools_.clear(allocator_);
overflowed_ = false; overflowed_ = false;
stringPool_.clear(allocator_); stringPool_.clear(allocator_);
staticStringsPools_.clear(allocator_);
} }
void shrinkToFit() { void shrinkToFit() {
variantPools_.shrinkToFit(allocator_); variantPools_.shrinkToFit(allocator_);
staticStringsPools_.shrinkToFit(allocator_);
} }
private: private:
@ -126,6 +142,7 @@ class ResourceManager {
bool overflowed_; bool overflowed_;
StringPool stringPool_; StringPool stringPool_;
MemoryPoolList<SlotData> variantPools_; MemoryPoolList<SlotData> variantPools_;
MemoryPoolList<const char*> staticStringsPools_;
}; };
ARDUINOJSON_END_PRIVATE_NAMESPACE ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

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

View File

@ -36,7 +36,7 @@ inline ObjectData::iterator ObjectData::findKey(
return iterator(); return iterator();
bool isKey = true; bool isKey = true;
for (auto it = createIterator(resources); !it.done(); it.next(resources)) { 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; return it;
isKey = !isKey; isKey = !isKey;
} }

View File

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

View File

@ -53,13 +53,10 @@ union VariantContent {
bool asBoolean; bool asBoolean;
uint32_t asUint32; uint32_t asUint32;
int32_t asInt32; int32_t asInt32;
#if ARDUINOJSON_USE_EXTENSIONS
SlotId asSlotId; SlotId asSlotId;
#endif
ArrayData asArray; ArrayData asArray;
ObjectData asObject; ObjectData asObject;
CollectionData asCollection; CollectionData asCollection;
const char* asLinkedString;
struct StringNode* asOwnedString; struct StringNode* asOwnedString;
}; };

View File

@ -64,7 +64,7 @@ class VariantData {
return visit.visit(content_.asObject); return visit.visit(content_.asObject);
case VariantType::LinkedString: case VariantType::LinkedString:
return visit.visit(JsonString(content_.asLinkedString, true)); return visit.visit(JsonString(asLinkedString(resources), true));
case VariantType::OwnedString: case VariantType::OwnedString:
return visit.visit(JsonString(content_.asOwnedString->data, return visit.visit(JsonString(content_.asOwnedString->data,
@ -200,7 +200,7 @@ class VariantData {
return static_cast<T>(extension->asInt64); return static_cast<T>(extension->asInt64);
#endif #endif
case VariantType::LinkedString: case VariantType::LinkedString:
str = content_.asLinkedString; str = asLinkedString(resources);
break; break;
case VariantType::OwnedString: case VariantType::OwnedString:
str = content_.asOwnedString->data; str = content_.asOwnedString->data;
@ -242,7 +242,7 @@ class VariantData {
return convertNumber<T>(extension->asInt64); return convertNumber<T>(extension->asInt64);
#endif #endif
case VariantType::LinkedString: case VariantType::LinkedString:
str = content_.asLinkedString; str = asLinkedString(resources);
break; break;
case VariantType::OwnedString: case VariantType::OwnedString:
str = content_.asOwnedString->data; str = content_.asOwnedString->data;
@ -279,10 +279,12 @@ class VariantData {
} }
} }
JsonString asString() const { const char* asLinkedString(const ResourceManager* resources) const;
JsonString asString(const ResourceManager* resources) const {
switch (type_) { switch (type_) {
case VariantType::LinkedString: case VariantType::LinkedString:
return JsonString(content_.asLinkedString, true); return JsonString(asLinkedString(resources), true);
case VariantType::OwnedString: case VariantType::OwnedString:
return JsonString(content_.asOwnedString->data, return JsonString(content_.asOwnedString->data,
content_.asOwnedString->length); content_.asOwnedString->length);
@ -502,12 +504,7 @@ class VariantData {
var->setString(value, resources); var->setString(value, resources);
} }
void setLinkedString(const char* s) { bool setLinkedString(const char* s, ResourceManager* resources);
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
ARDUINOJSON_ASSERT(s);
type_ = VariantType::LinkedString;
content_.asLinkedString = s;
}
void setOwnedString(StringNode* s) { void setOwnedString(StringNode* s) {
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first

View File

@ -18,6 +18,20 @@ inline void VariantData::setRawString(SerializedValue<T> value,
setRawString(dup); 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> template <typename TAdaptedString>
inline bool VariantData::setString(TAdaptedString value, inline bool VariantData::setString(TAdaptedString value,
ResourceManager* resources) { ResourceManager* resources) {
@ -26,10 +40,8 @@ inline bool VariantData::setString(TAdaptedString value,
if (value.isNull()) if (value.isNull())
return false; return false;
if (value.isStatic()) { if (value.isStatic())
setLinkedString(value.data()); return setLinkedString(value.data(), resources);
return true;
}
auto dup = resources->saveString(value); auto dup = resources->saveString(value);
if (dup) { if (dup) {
@ -65,6 +77,12 @@ inline const VariantExtension* VariantData::getExtension(
} }
#endif #endif
inline const char* VariantData::asLinkedString(
const ResourceManager* resources) const {
ARDUINOJSON_ASSERT(type_ == VariantType::LinkedString);
return resources->getStaticString(content_.asSlotId);
}
template <typename T> template <typename T>
enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat( enable_if_t<sizeof(T) == 8, bool> VariantData::setFloat(
T value, ResourceManager* resources) { T value, ResourceManager* resources) {