forked from bblanchon/ArduinoJson
Optimize storage of tiny strings (up to 3 characters)
This commit is contained in:
@ -1,6 +1,11 @@
|
|||||||
ArduinoJson: change log
|
ArduinoJson: change log
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
HEAD
|
||||||
|
----
|
||||||
|
|
||||||
|
* Optimize storage of tiny strings (up to 3 characters)
|
||||||
|
|
||||||
v7.3.1 (2025-02-27)
|
v7.3.1 (2025-02-27)
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -292,22 +292,23 @@ TEST_CASE("deserialize JSON object") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Repeated key") {
|
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(err == DeserializationError::Ok);
|
||||||
REQUIRE(doc.as<std::string>() == "{\"a\":2}");
|
REQUIRE(doc.as<std::string>() == "{\"alfa\":2}");
|
||||||
REQUIRE(spy.log() ==
|
REQUIRE(spy.log() ==
|
||||||
AllocatorLog{
|
AllocatorLog{
|
||||||
Allocate(sizeofStringBuffer()),
|
Allocate(sizeofStringBuffer()),
|
||||||
Allocate(sizeofPool()),
|
Allocate(sizeofPool()),
|
||||||
Reallocate(sizeofStringBuffer(), sizeofString("a")),
|
Reallocate(sizeofStringBuffer(), sizeofString("alfa")),
|
||||||
Allocate(sizeofStringBuffer()),
|
Allocate(sizeofStringBuffer()),
|
||||||
Reallocate(sizeofStringBuffer(), sizeofString("b")),
|
Reallocate(sizeofStringBuffer(), sizeofString("bravo")),
|
||||||
Allocate(sizeofStringBuffer()),
|
Allocate(sizeofStringBuffer()),
|
||||||
Reallocate(sizeofStringBuffer(), sizeofString("c")),
|
Reallocate(sizeofStringBuffer(), sizeofString("charlie")),
|
||||||
Allocate(sizeofStringBuffer()),
|
Allocate(sizeofStringBuffer()),
|
||||||
Deallocate(sizeofString("b")),
|
Deallocate(sizeofString("bravo")),
|
||||||
Deallocate(sizeofString("c")),
|
Deallocate(sizeofString("charlie")),
|
||||||
Deallocate(sizeofStringBuffer()),
|
Deallocate(sizeofStringBuffer()),
|
||||||
Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
|
Reallocate(sizeofPool(), sizeofObject(2) + sizeofObject(1)),
|
||||||
});
|
});
|
||||||
@ -389,11 +390,11 @@ TEST_CASE("deserialize JSON object under memory constraints") {
|
|||||||
|
|
||||||
SECTION("string allocation fails") {
|
SECTION("string allocation fails") {
|
||||||
timebomb.setCountdown(3);
|
timebomb.setCountdown(3);
|
||||||
char input[] = "{\"a\":\"b\"}";
|
char input[] = "{\"alfa\":\"bravo\"}";
|
||||||
|
|
||||||
DeserializationError err = deserializeJson(doc, input);
|
DeserializationError err = deserializeJson(doc, input);
|
||||||
|
|
||||||
REQUIRE(err == DeserializationError::NoMemory);
|
REQUIRE(err == DeserializationError::NoMemory);
|
||||||
REQUIRE(doc.as<std::string>() == "{\"a\":null}");
|
REQUIRE(doc.as<std::string>() == "{\"alfa\":null}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,13 +100,13 @@ TEST_CASE("JsonObject::set()") {
|
|||||||
JsonDocument doc3(&timebomb);
|
JsonDocument doc3(&timebomb);
|
||||||
JsonObject obj3 = doc3.to<JsonObject>();
|
JsonObject obj3 = doc3.to<JsonObject>();
|
||||||
|
|
||||||
obj1["a"_s] = 1;
|
obj1["alpha"_s] = 1;
|
||||||
obj1["b"_s] = 2;
|
obj1["beta"_s] = 2;
|
||||||
|
|
||||||
bool success = obj3.set(obj1);
|
bool success = obj3.set(obj1);
|
||||||
|
|
||||||
REQUIRE(success == false);
|
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") {
|
SECTION("copy fails in the middle of an array") {
|
||||||
|
@ -199,7 +199,7 @@ TEST_CASE("JsonVariant::as()") {
|
|||||||
REQUIRE(variant.as<JsonString>() == "hello");
|
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);
|
variant.set("4.2"_s);
|
||||||
|
|
||||||
REQUIRE(variant.as<bool>() == true);
|
REQUIRE(variant.as<bool>() == true);
|
||||||
@ -211,6 +211,18 @@ TEST_CASE("JsonVariant::as()") {
|
|||||||
REQUIRE(variant.as<JsonString>().isStatic() == false);
|
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\")") {
|
SECTION("set(\"true\")") {
|
||||||
variant.set("true");
|
variant.set("true");
|
||||||
|
|
||||||
|
@ -63,6 +63,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") {
|
SECTION("(char*)0") {
|
||||||
bool result = variant.set(static_cast<char*>(0));
|
bool result = variant.set(static_cast<char*>(0));
|
||||||
|
|
||||||
|
@ -5,8 +5,11 @@
|
|||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <catch.hpp>
|
#include <catch.hpp>
|
||||||
|
|
||||||
|
#include "Allocators.hpp"
|
||||||
|
|
||||||
TEST_CASE("deserialize MsgPack array") {
|
TEST_CASE("deserialize MsgPack array") {
|
||||||
JsonDocument doc;
|
SpyingAllocator spy;
|
||||||
|
JsonDocument doc(&spy);
|
||||||
|
|
||||||
SECTION("fixarray") {
|
SECTION("fixarray") {
|
||||||
SECTION("empty") {
|
SECTION("empty") {
|
||||||
@ -30,6 +33,24 @@ TEST_CASE("deserialize MsgPack array") {
|
|||||||
REQUIRE(array[0] == 1);
|
REQUIRE(array[0] == 1);
|
||||||
REQUIRE(array[1] == 2);
|
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") {
|
SECTION("array 16") {
|
||||||
|
@ -348,13 +348,14 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
|
|||||||
SECTION("{}") {
|
SECTION("{}") {
|
||||||
checkError(0, "\x80", DeserializationError::Ok);
|
checkError(0, "\x80", DeserializationError::Ok);
|
||||||
}
|
}
|
||||||
SECTION("{H:1}") {
|
SECTION("{Hello:1}") {
|
||||||
checkError(1, "\x81\xA1H\x01", DeserializationError::NoMemory);
|
checkError(1, "\x81\xA5Hello\x01", DeserializationError::NoMemory);
|
||||||
checkError(2, "\x81\xA1H\x01", DeserializationError::Ok);
|
checkError(2, "\x81\xA5Hello\x01", DeserializationError::Ok);
|
||||||
}
|
}
|
||||||
SECTION("{H:1,W:2}") {
|
SECTION("{Hello:1,World:2}") {
|
||||||
checkError(2, "\x82\xA1H\x01\xA1W\x02", DeserializationError::NoMemory);
|
checkError(2, "\x82\xA5Hello\x01\xA5World\x02",
|
||||||
checkError(3, "\x82\xA1H\x01\xA1W\x02", DeserializationError::Ok);
|
DeserializationError::NoMemory);
|
||||||
|
checkError(3, "\x82\xA5Hello\x01\xA5World\x02", DeserializationError::Ok);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,14 +363,16 @@ TEST_CASE("deserializeMsgPack() under memory constaints") {
|
|||||||
SECTION("{}") {
|
SECTION("{}") {
|
||||||
checkError(0, "\xDE\x00\x00", DeserializationError::Ok);
|
checkError(0, "\xDE\x00\x00", DeserializationError::Ok);
|
||||||
}
|
}
|
||||||
SECTION("{H:1}") {
|
SECTION("{Hello:1}") {
|
||||||
checkError(1, "\xDE\x00\x01\xA1H\x01", DeserializationError::NoMemory);
|
checkError(1, "\xDE\x00\x01\xA5Hello\x01",
|
||||||
checkError(2, "\xDE\x00\x01\xA1H\x01", DeserializationError::Ok);
|
|
||||||
}
|
|
||||||
SECTION("{H:1,W:2}") {
|
|
||||||
checkError(2, "\xDE\x00\x02\xA1H\x01\xA1W\x02",
|
|
||||||
DeserializationError::NoMemory);
|
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);
|
DeserializationError::NoMemory);
|
||||||
checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
|
checkError(2, "\xDF\x00\x00\x00\x01\xA1H\x01", DeserializationError::Ok);
|
||||||
}
|
}
|
||||||
SECTION("{H:1,W:2}") {
|
SECTION("{Hello:1,World:2}") {
|
||||||
checkError(2, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
|
checkError(2, "\xDF\x00\x00\x00\x02\xA5Hello\x01\xA5World\x02",
|
||||||
DeserializationError::NoMemory);
|
DeserializationError::NoMemory);
|
||||||
checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
|
checkError(3, "\xDF\x00\x00\x00\x02\xA1H\x01\xA1W\x02",
|
||||||
DeserializationError::Ok);
|
DeserializationError::Ok);
|
||||||
|
@ -8,6 +8,7 @@ add_executable(ResourceManagerTests
|
|||||||
saveString.cpp
|
saveString.cpp
|
||||||
shrinkToFit.cpp
|
shrinkToFit.cpp
|
||||||
size.cpp
|
size.cpp
|
||||||
|
StringBuffer.cpp
|
||||||
StringBuilder.cpp
|
StringBuilder.cpp
|
||||||
swap.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/Memory/StringBuffer.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() == "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();
|
||||||
|
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() == "alfa");
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,8 @@
|
|||||||
#include <catch.hpp>
|
#include <catch.hpp>
|
||||||
|
|
||||||
#include "Allocators.hpp"
|
#include "Allocators.hpp"
|
||||||
#include "Literals.hpp"
|
|
||||||
|
|
||||||
|
using namespace ArduinoJson;
|
||||||
using namespace ArduinoJson::detail;
|
using namespace ArduinoJson::detail;
|
||||||
|
|
||||||
TEST_CASE("StringBuilder") {
|
TEST_CASE("StringBuilder") {
|
||||||
@ -22,13 +22,31 @@ TEST_CASE("StringBuilder") {
|
|||||||
str.startString();
|
str.startString();
|
||||||
str.save(&data);
|
str.save(&data);
|
||||||
|
|
||||||
REQUIRE(resources.size() == sizeofString(""));
|
|
||||||
REQUIRE(resources.overflowed() == false);
|
REQUIRE(resources.overflowed() == false);
|
||||||
REQUIRE(spyingAllocator.log() ==
|
REQUIRE(spyingAllocator.log() == AllocatorLog{
|
||||||
AllocatorLog{
|
Allocate(sizeofStringBuffer()),
|
||||||
Allocate(sizeofStringBuffer()),
|
});
|
||||||
Reallocate(sizeofStringBuffer(), sizeofString("")),
|
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() == "url");
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("Short string fits in first allocation") {
|
SECTION("Short string fits in first allocation") {
|
||||||
@ -98,12 +116,12 @@ TEST_CASE("StringBuilder") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* saveString(StringBuilder& builder, const char* s) {
|
static JsonString saveString(StringBuilder& builder, const char* s) {
|
||||||
VariantData data;
|
VariantData data;
|
||||||
builder.startString();
|
builder.startString();
|
||||||
builder.append(s);
|
builder.append(s);
|
||||||
builder.save(&data);
|
builder.save(&data);
|
||||||
return data.asString().c_str();
|
return data.asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("StringBuilder::save() deduplicates strings") {
|
TEST_CASE("StringBuilder::save() deduplicates strings") {
|
||||||
@ -116,9 +134,9 @@ TEST_CASE("StringBuilder::save() deduplicates strings") {
|
|||||||
auto s2 = saveString(builder, "world");
|
auto s2 = saveString(builder, "world");
|
||||||
auto s3 = saveString(builder, "hello");
|
auto s3 = saveString(builder, "hello");
|
||||||
|
|
||||||
REQUIRE(s1 == "hello"_s);
|
REQUIRE(s1 == "hello");
|
||||||
REQUIRE(s2 == "world"_s);
|
REQUIRE(s2 == "world");
|
||||||
REQUIRE(+s1 == +s3); // same address
|
REQUIRE(+s1.c_str() == +s3.c_str()); // same address
|
||||||
|
|
||||||
REQUIRE(spy.log() ==
|
REQUIRE(spy.log() ==
|
||||||
AllocatorLog{
|
AllocatorLog{
|
||||||
@ -134,9 +152,9 @@ TEST_CASE("StringBuilder::save() deduplicates strings") {
|
|||||||
auto s1 = saveString(builder, "hello world");
|
auto s1 = saveString(builder, "hello world");
|
||||||
auto s2 = saveString(builder, "hello");
|
auto s2 = saveString(builder, "hello");
|
||||||
|
|
||||||
REQUIRE(s1 == "hello world"_s);
|
REQUIRE(s1 == "hello world");
|
||||||
REQUIRE(s2 == "hello"_s);
|
REQUIRE(s2 == "hello");
|
||||||
REQUIRE(+s2 != +s1); // different address
|
REQUIRE(+s2.c_str() != +s1.c_str()); // different address
|
||||||
|
|
||||||
REQUIRE(spy.log() ==
|
REQUIRE(spy.log() ==
|
||||||
AllocatorLog{
|
AllocatorLog{
|
||||||
@ -149,18 +167,18 @@ TEST_CASE("StringBuilder::save() deduplicates strings") {
|
|||||||
|
|
||||||
SECTION("Don't overrun") {
|
SECTION("Don't overrun") {
|
||||||
auto s1 = saveString(builder, "hello world");
|
auto s1 = saveString(builder, "hello world");
|
||||||
auto s2 = saveString(builder, "wor");
|
auto s2 = saveString(builder, "worl");
|
||||||
|
|
||||||
REQUIRE(s1 == "hello world"_s);
|
REQUIRE(s1 == "hello world");
|
||||||
REQUIRE(s2 == "wor"_s);
|
REQUIRE(s2 == "worl");
|
||||||
REQUIRE(s2 != s1);
|
REQUIRE(s2.c_str() != s1.c_str()); // different address
|
||||||
|
|
||||||
REQUIRE(spy.log() ==
|
REQUIRE(spy.log() ==
|
||||||
AllocatorLog{
|
AllocatorLog{
|
||||||
Allocate(sizeofStringBuffer()),
|
Allocate(sizeofStringBuffer()),
|
||||||
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
|
Reallocate(sizeofStringBuffer(), sizeofString("hello world")),
|
||||||
Allocate(sizeofStringBuffer()),
|
Allocate(sizeofStringBuffer()),
|
||||||
Reallocate(sizeofStringBuffer(), sizeofString("wor")),
|
Reallocate(sizeofStringBuffer(), sizeofString("worl")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,12 +34,16 @@ class StringBuffer {
|
|||||||
|
|
||||||
JsonString str() const {
|
JsonString str() const {
|
||||||
ARDUINOJSON_ASSERT(node_ != nullptr);
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
|
||||||
return JsonString(node_->data, node_->length);
|
return JsonString(node_->data, node_->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
void save(VariantData* data) {
|
void save(VariantData* data) {
|
||||||
data->setOwnedString(commitStringNode());
|
ARDUINOJSON_ASSERT(node_ != nullptr);
|
||||||
|
const char* s = node_->data;
|
||||||
|
if (isTinyString(s, size_))
|
||||||
|
data->setTinyString(s, static_cast<uint8_t>(size_));
|
||||||
|
else
|
||||||
|
data->setOwnedString(commitStringNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveRaw(VariantData* data) {
|
void saveRaw(VariantData* data) {
|
||||||
|
@ -28,8 +28,15 @@ class StringBuilder {
|
|||||||
void save(VariantData* variant) {
|
void save(VariantData* variant) {
|
||||||
ARDUINOJSON_ASSERT(variant != nullptr);
|
ARDUINOJSON_ASSERT(variant != nullptr);
|
||||||
ARDUINOJSON_ASSERT(node_ != 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(p, static_cast<uint8_t>(size_));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p[size_] = 0;
|
||||||
|
StringNode* node = resources_->getString(adaptString(p, size_));
|
||||||
if (!node) {
|
if (!node) {
|
||||||
node = resources_->resizeString(node_, size_);
|
node = resources_->resizeString(node_, size_);
|
||||||
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
|
ARDUINOJSON_ASSERT(node != nullptr); // realloc to smaller can't fail
|
||||||
|
@ -403,7 +403,7 @@ class MsgPackDeserializer {
|
|||||||
|
|
||||||
JsonString key = stringBuffer_.str();
|
JsonString key = stringBuffer_.str();
|
||||||
TFilter memberFilter = filter[key.c_str()];
|
TFilter memberFilter = filter[key.c_str()];
|
||||||
VariantData* member;
|
VariantData* member = 0;
|
||||||
|
|
||||||
if (memberFilter.allow()) {
|
if (memberFilter.allow()) {
|
||||||
ARDUINOJSON_ASSERT(object != 0);
|
ARDUINOJSON_ASSERT(object != 0);
|
||||||
@ -413,8 +413,6 @@ class MsgPackDeserializer {
|
|||||||
return DeserializationError::NoMemory;
|
return DeserializationError::NoMemory;
|
||||||
|
|
||||||
stringBuffer_.save(keyVariant);
|
stringBuffer_.save(keyVariant);
|
||||||
} else {
|
|
||||||
member = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseVariant(member, memberFilter, nestingLimit.decrement());
|
err = parseVariant(member, memberFilter, nestingLimit.decrement());
|
||||||
|
@ -24,6 +24,7 @@ enum class VariantTypeBits : uint8_t {
|
|||||||
|
|
||||||
enum class VariantType : uint8_t {
|
enum class VariantType : uint8_t {
|
||||||
Null = 0, // 0000 0000
|
Null = 0, // 0000 0000
|
||||||
|
TinyString = 0x02, // 0000 0010
|
||||||
RawString = 0x03, // 0000 0011
|
RawString = 0x03, // 0000 0011
|
||||||
LinkedString = 0x04, // 0000 0100
|
LinkedString = 0x04, // 0000 0100
|
||||||
OwnedString = 0x05, // 0000 0101
|
OwnedString = 0x05, // 0000 0101
|
||||||
@ -46,6 +47,8 @@ inline bool operator&(VariantType type, VariantTypeBits bit) {
|
|||||||
return (uint8_t(type) & uint8_t(bit)) != 0;
|
return (uint8_t(type) & uint8_t(bit)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const size_t tinyStringMaxLength = 3;
|
||||||
|
|
||||||
union VariantContent {
|
union VariantContent {
|
||||||
VariantContent() {}
|
VariantContent() {}
|
||||||
|
|
||||||
@ -61,6 +64,7 @@ union VariantContent {
|
|||||||
CollectionData asCollection;
|
CollectionData asCollection;
|
||||||
const char* asLinkedString;
|
const char* asLinkedString;
|
||||||
struct StringNode* asOwnedString;
|
struct StringNode* asOwnedString;
|
||||||
|
char asTinyString[tinyStringMaxLength + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
#if ARDUINOJSON_USE_EXTENSIONS
|
#if ARDUINOJSON_USE_EXTENSIONS
|
||||||
|
@ -17,6 +17,16 @@ ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
T parseNumber(const char* s);
|
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 {
|
class VariantData {
|
||||||
VariantContent content_; // must be first to allow cast from array to variant
|
VariantContent content_; // must be first to allow cast from array to variant
|
||||||
VariantType type_;
|
VariantType type_;
|
||||||
@ -63,6 +73,9 @@ class VariantData {
|
|||||||
case VariantType::Object:
|
case VariantType::Object:
|
||||||
return visit.visit(content_.asObject);
|
return visit.visit(content_.asObject);
|
||||||
|
|
||||||
|
case VariantType::TinyString:
|
||||||
|
return visit.visit(JsonString(content_.asTinyString));
|
||||||
|
|
||||||
case VariantType::LinkedString:
|
case VariantType::LinkedString:
|
||||||
return visit.visit(JsonString(content_.asLinkedString, true));
|
return visit.visit(JsonString(content_.asLinkedString, true));
|
||||||
|
|
||||||
@ -199,6 +212,9 @@ class VariantData {
|
|||||||
case VariantType::Int64:
|
case VariantType::Int64:
|
||||||
return static_cast<T>(extension->asInt64);
|
return static_cast<T>(extension->asInt64);
|
||||||
#endif
|
#endif
|
||||||
|
case VariantType::TinyString:
|
||||||
|
str = content_.asTinyString;
|
||||||
|
break;
|
||||||
case VariantType::LinkedString:
|
case VariantType::LinkedString:
|
||||||
str = content_.asLinkedString;
|
str = content_.asLinkedString;
|
||||||
break;
|
break;
|
||||||
@ -241,6 +257,9 @@ class VariantData {
|
|||||||
case VariantType::Int64:
|
case VariantType::Int64:
|
||||||
return convertNumber<T>(extension->asInt64);
|
return convertNumber<T>(extension->asInt64);
|
||||||
#endif
|
#endif
|
||||||
|
case VariantType::TinyString:
|
||||||
|
str = content_.asTinyString;
|
||||||
|
break;
|
||||||
case VariantType::LinkedString:
|
case VariantType::LinkedString:
|
||||||
str = content_.asLinkedString;
|
str = content_.asLinkedString;
|
||||||
break;
|
break;
|
||||||
@ -281,6 +300,8 @@ class VariantData {
|
|||||||
|
|
||||||
JsonString asString() const {
|
JsonString asString() const {
|
||||||
switch (type_) {
|
switch (type_) {
|
||||||
|
case VariantType::TinyString:
|
||||||
|
return JsonString(content_.asTinyString);
|
||||||
case VariantType::LinkedString:
|
case VariantType::LinkedString:
|
||||||
return JsonString(content_.asLinkedString, true);
|
return JsonString(content_.asLinkedString, true);
|
||||||
case VariantType::OwnedString:
|
case VariantType::OwnedString:
|
||||||
@ -395,7 +416,8 @@ class VariantData {
|
|||||||
|
|
||||||
bool isString() const {
|
bool isString() const {
|
||||||
return type_ == VariantType::LinkedString ||
|
return type_ == VariantType::LinkedString ||
|
||||||
type_ == VariantType::OwnedString;
|
type_ == VariantType::OwnedString ||
|
||||||
|
type_ == VariantType::TinyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nesting(const ResourceManager* resources) const {
|
size_t nesting(const ResourceManager* resources) const {
|
||||||
@ -504,6 +526,15 @@ class VariantData {
|
|||||||
content_.asLinkedString = s;
|
content_.asLinkedString = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setTinyString(const char* s, uint8_t n) {
|
||||||
|
ARDUINOJSON_ASSERT(type_ == VariantType::Null); // must call clear() first
|
||||||
|
ARDUINOJSON_ASSERT(s);
|
||||||
|
type_ = VariantType::TinyString;
|
||||||
|
for (uint8_t i = 0; i < n; i++)
|
||||||
|
content_.asTinyString[i] = s[i];
|
||||||
|
content_.asTinyString[n] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
ARDUINOJSON_ASSERT(s);
|
ARDUINOJSON_ASSERT(s);
|
||||||
|
@ -31,6 +31,11 @@ inline bool VariantData::setString(TAdaptedString value,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTinyString(value, value.size())) {
|
||||||
|
setTinyString(value.data(), uint8_t(value.size()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto dup = resources->saveString(value);
|
auto dup = resources->saveString(value);
|
||||||
if (dup) {
|
if (dup) {
|
||||||
setOwnedString(dup);
|
setOwnedString(dup);
|
||||||
|
Reference in New Issue
Block a user