diff --git a/CHANGELOG.md b/CHANGELOG.md index d95d6bc5..275e729d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ HEAD * Fix support for NUL characters in `deserializeJson()` * Make `ElementProxy` and `MemberProxy` non-copyable +* Change string copy policy: only string literal are stored by pointer > ### BREAKING CHANGES > diff --git a/extras/tests/JsonArray/add.cpp b/extras/tests/JsonArray/add.cpp index 1527670a..f1ca5f20 100644 --- a/extras/tests/JsonArray/add.cpp +++ b/extras/tests/JsonArray/add.cpp @@ -17,31 +17,112 @@ TEST_CASE("JsonArray::add(T)") { SECTION("int") { array.add(123); + REQUIRE(123 == array[0].as()); REQUIRE(array[0].is()); REQUIRE(array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } SECTION("double") { array.add(123.45); + REQUIRE(123.45 == array[0].as()); REQUIRE(array[0].is()); REQUIRE_FALSE(array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } SECTION("bool") { array.add(true); - REQUIRE(true == array[0].as()); + + REQUIRE(array[0].as() == true); REQUIRE(array[0].is()); REQUIRE_FALSE(array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("string literal") { + array.add("hello"); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(array[0].is()); + REQUIRE(array[0].is() == false); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("std::string") { + array.add("hello"_s); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(array[0].is() == true); + REQUIRE(array[0].is() == false); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); } SECTION("const char*") { const char* str = "hello"; array.add(str); - REQUIRE(str == array[0].as()); - REQUIRE(array[0].is()); - REQUIRE_FALSE(array[0].is()); + + REQUIRE(array[0].as() == "hello"); + REQUIRE(array[0].as() != str); + REQUIRE(array[0].is() == true); + REQUIRE(array[0].is() == false); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + + SECTION("serialized(const char*)") { + array.add(serialized("{}")); + + REQUIRE(doc.as() == "[{}]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("{}")), + }); + } + + SECTION("serialized(char*)") { + array.add(serialized(const_cast("{}"))); + + REQUIRE(doc.as() == "[{}]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("{}")), + }); + } + + SECTION("serialized(std::string)") { + array.add(serialized("{}"_s)); + + REQUIRE(doc.as() == "[{}]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("{}")), + }); + } + + SECTION("serialized(std::string)") { + array.add(serialized("\0XX"_s)); + + REQUIRE(doc.as() == "[\0XX]"_s); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString(" XX")), + }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY @@ -52,7 +133,12 @@ TEST_CASE("JsonArray::add(T)") { array.add(vla); - REQUIRE("world"_s == array[0]); + strcpy(vla, "hello"); + REQUIRE(array[0] == "world"_s); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); } #endif @@ -99,61 +185,6 @@ TEST_CASE("JsonArray::add(T)") { REQUIRE(str == array[0]); } - - SECTION("should not duplicate const char*") { - array.add("world"); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - }); - } - - SECTION("should duplicate char*") { - array.add(const_cast("world")); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - - SECTION("should duplicate std::string") { - array.add("world"_s); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - - SECTION("should duplicate serialized(const char*)") { - array.add(serialized("{}")); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("{}")), - }); - } - - SECTION("should duplicate serialized(char*)") { - array.add(serialized(const_cast("{}"))); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("{}")), - }); - } - - SECTION("should duplicate serialized(std::string)") { - array.add(serialized("{}"_s)); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("{}")), - }); - } - - SECTION("should duplicate serialized(std::string)") { - array.add(serialized("\0XX"_s)); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString(" XX")), - }); - } } TEST_CASE("JsonArray::add()") { diff --git a/extras/tests/JsonArray/subscript.cpp b/extras/tests/JsonArray/subscript.cpp index b0332f85..40b17361 100644 --- a/extras/tests/JsonArray/subscript.cpp +++ b/extras/tests/JsonArray/subscript.cpp @@ -59,15 +59,56 @@ TEST_CASE("JsonArray::operator[]") { REQUIRE(false == array[0].is()); } - SECTION("const char*") { - const char* str = "hello"; + SECTION("string literal") { + array[0] = "hello"; - array[0] = str; - REQUIRE(str == array[0].as()); + REQUIRE(array[0].as() == "hello"); REQUIRE(true == array[0].is()); REQUIRE(false == array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } + SECTION("const char*") { + const char* str = "hello"; + array[0] = str; + + REQUIRE(array[0].as() == "hello"); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + + SECTION("std::string") { + array[0] = "hello"_s; + + REQUIRE(array[0].as() == "hello"); + REQUIRE(true == array[0].is()); + REQUIRE(false == array[0].is()); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + +#ifdef HAS_VARIABLE_LENGTH_ARRAY + SECTION("VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "world"); + + array.add("hello"); + array[0] = vla; + + REQUIRE(array[0] == "world"_s); + } +#endif + SECTION("nested array") { JsonDocument doc2; JsonArray arr2 = doc2.to(); @@ -114,58 +155,11 @@ TEST_CASE("JsonArray::operator[]") { REQUIRE(str == array[0]); } - SECTION("should not duplicate const char*") { - array[0] = "world"; - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - }); - } - - SECTION("should duplicate char*") { - array[0] = const_cast("world"); - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - - SECTION("should duplicate std::string") { - array[0] = "world"_s; - REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofPool()), - Allocate(sizeofString("world")), - }); - } - SECTION("array[0].to()") { JsonObject obj = array[0].to(); REQUIRE(obj.isNull() == false); } -#ifdef HAS_VARIABLE_LENGTH_ARRAY - SECTION("set(VLA)") { - size_t i = 16; - char vla[i]; - strcpy(vla, "world"); - - array.add("hello"); - array[0].set(vla); - - REQUIRE("world"_s == array[0]); - } - - SECTION("operator=(VLA)") { - size_t i = 16; - char vla[i]; - strcpy(vla, "world"); - - array.add("hello"); - array[0] = vla; - - REQUIRE("world"_s == array[0]); - } -#endif - SECTION("Use a JsonVariant as index") { array[0] = 1; array[1] = 2; diff --git a/extras/tests/JsonDocument/ElementProxy.cpp b/extras/tests/JsonDocument/ElementProxy.cpp index 7de4d6ab..387dc884 100644 --- a/extras/tests/JsonDocument/ElementProxy.cpp +++ b/extras/tests/JsonDocument/ElementProxy.cpp @@ -5,37 +5,60 @@ #include #include +#include "Allocators.hpp" #include "Literals.hpp" using ElementProxy = ArduinoJson::detail::ElementProxy; TEST_CASE("ElementProxy::add()") { - JsonDocument doc; + SpyingAllocator spy; + JsonDocument doc(&spy); doc.add(); const ElementProxy& ep = doc[0]; - SECTION("add(int)") { + SECTION("integer") { ep.add(42); REQUIRE(doc.as() == "[[42]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } - SECTION("add(const char*)") { + SECTION("string literal") { ep.add("world"); REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } - SECTION("add(char[])") { + SECTION("const char*") { + const char* s = "world"; + ep.add(s); + + REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + + SECTION("char[]") { char s[] = "world"; ep.add(s); strcpy(s, "!!!!!"); REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY - SECTION("set(vla)") { + SECTION("VLA") { size_t i = 8; char vla[i]; strcpy(vla, "world"); @@ -43,6 +66,10 @@ TEST_CASE("ElementProxy::add()") { ep.add(vla); REQUIRE(doc.as() == "[[\"world\"]]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } #endif } diff --git a/extras/tests/JsonDocument/MemberProxy.cpp b/extras/tests/JsonDocument/MemberProxy.cpp index 79f314c0..4eb7468c 100644 --- a/extras/tests/JsonDocument/MemberProxy.cpp +++ b/extras/tests/JsonDocument/MemberProxy.cpp @@ -15,23 +15,53 @@ using ArduinoJson::detail::sizeofArray; using ArduinoJson::detail::sizeofObject; TEST_CASE("MemberProxy::add()") { - JsonDocument doc; + SpyingAllocator spy; + JsonDocument doc(&spy); const auto& mp = doc["hello"]; - SECTION("add(int)") { + SECTION("integer") { mp.add(42); REQUIRE(doc.as() == "{\"hello\":[42]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); } - SECTION("add(const char*)") { + SECTION("string literal") { mp.add("world"); REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("const char*") { + const char* temp = "world"; + mp.add(temp); + + REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); + } + + SECTION("char[]") { + char temp[] = "world"; + mp.add(temp); + + REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + + }); } #ifdef HAS_VARIABLE_LENGTH_ARRAY - SECTION("add(vla)") { + SECTION("VLA") { size_t i = 16; char vla[i]; strcpy(vla, "world"); @@ -39,6 +69,10 @@ TEST_CASE("MemberProxy::add()") { mp.add(vla); REQUIRE(doc.as() == "{\"hello\":[\"world\"]}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("world")), + }); } #endif } diff --git a/extras/tests/JsonDocument/add.cpp b/extras/tests/JsonDocument/add.cpp index 31aa6c2f..8a1c48e3 100644 --- a/extras/tests/JsonDocument/add.cpp +++ b/extras/tests/JsonDocument/add.cpp @@ -26,7 +26,7 @@ TEST_CASE("JsonDocument::add(T)") { }); } - SECTION("const char*") { + SECTION("string literal") { doc.add("hello"); REQUIRE(doc.as() == "[\"hello\"]"); @@ -35,6 +35,17 @@ TEST_CASE("JsonDocument::add(T)") { }); } + SECTION("const char*") { + const char* value = "hello"; + doc.add(value); + + REQUIRE(doc.as() == "[\"hello\"]"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + SECTION("std::string") { doc.add("example"_s); doc.add("example"_s); diff --git a/extras/tests/JsonDocument/remove.cpp b/extras/tests/JsonDocument/remove.cpp index afabf4eb..1656a4d2 100644 --- a/extras/tests/JsonDocument/remove.cpp +++ b/extras/tests/JsonDocument/remove.cpp @@ -20,11 +20,21 @@ TEST_CASE("JsonDocument::remove()") { REQUIRE(doc.as() == "[1,3]"); } + SECTION("string literal") { + doc["a"] = 1; + doc["a\0b"_s] = 2; + doc["b"] = 3; + + doc.remove("a\0b"); + + REQUIRE(doc.as() == "{\"a\":1,\"b\":3}"); + } + SECTION("remove(const char *)") { doc["a"] = 1; doc["b"] = 2; - doc.remove("a"); + doc.remove(static_cast("a")); REQUIRE(doc.as() == "{\"b\":2}"); } diff --git a/extras/tests/JsonDocument/set.cpp b/extras/tests/JsonDocument/set.cpp index ce9de42a..1205acf5 100644 --- a/extras/tests/JsonDocument/set.cpp +++ b/extras/tests/JsonDocument/set.cpp @@ -11,6 +11,21 @@ TEST_CASE("JsonDocument::set()") { SpyingAllocator spy; JsonDocument doc(&spy); + SECTION("nullptr") { + doc.set(nullptr); + + REQUIRE(doc.isNull()); + REQUIRE(spy.log() == AllocatorLog{}); + } + + SECTION("integer&") { + int toto = 42; + doc.set(toto); + + REQUIRE(doc.as() == "42"); + REQUIRE(spy.log() == AllocatorLog{}); + } + SECTION("integer") { doc.set(42); @@ -18,13 +33,23 @@ TEST_CASE("JsonDocument::set()") { REQUIRE(spy.log() == AllocatorLog{}); } - SECTION("const char*") { + SECTION("string literal") { doc.set("example"); REQUIRE(doc.as() == "example"_s); REQUIRE(spy.log() == AllocatorLog{}); } + SECTION("const char*") { + const char* value = "example"; + doc.set(value); + + REQUIRE(doc.as() == "example"_s); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofString("example")), + }); + } + SECTION("std::string") { doc.set("example"_s); diff --git a/extras/tests/JsonDocument/subscript.cpp b/extras/tests/JsonDocument/subscript.cpp index 250ce1dd..2150d15c 100644 --- a/extras/tests/JsonDocument/subscript.cpp +++ b/extras/tests/JsonDocument/subscript.cpp @@ -5,6 +5,7 @@ #include #include +#include "Allocators.hpp" #include "Literals.hpp" TEST_CASE("JsonDocument::operator[]") { @@ -16,8 +17,16 @@ TEST_CASE("JsonDocument::operator[]") { doc["abc\0d"_s] = "ABCD"; SECTION("const char*") { + const char* key = "abc"; + REQUIRE(doc[key] == "ABC"); + REQUIRE(cdoc[key] == "ABC"); + } + + SECTION("string literal") { REQUIRE(doc["abc"] == "ABC"); REQUIRE(cdoc["abc"] == "ABC"); + REQUIRE(doc["abc\0d"] == "ABCD"); + REQUIRE(cdoc["abc\0d"] == "ABCD"); } SECTION("std::string") { @@ -94,3 +103,65 @@ TEST_CASE("JsonDocument automatically promotes to array") { REQUIRE(doc.as() == "[null,null,2]"); } + +TEST_CASE("JsonDocument::operator[] key storage") { + SpyingAllocator spy; + JsonDocument doc(&spy); + + SECTION("string literal") { + doc["hello"] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + }); + } + + SECTION("const char*") { + const char* key = "hello"; + doc[key] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + + SECTION("char[]") { + char key[] = "hello"; + doc[key] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } + + SECTION("std::string") { + doc["hello"_s] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } +#if defined(HAS_VARIABLE_LENGTH_ARRAY) && \ + !defined(SUBSCRIPT_CONFLICTS_WITH_BUILTIN_OPERATOR) + SECTION("VLA") { + size_t i = 16; + char vla[i]; + strcpy(vla, "hello"); + + doc[vla] = 0; + + REQUIRE(doc.as() == "{\"hello\":0}"); + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofPool()), + Allocate(sizeofString("hello")), + }); + } +#endif +} diff --git a/extras/tests/JsonVariant/set.cpp b/extras/tests/JsonVariant/set.cpp index b10f9505..8e5c1a29 100644 --- a/extras/tests/JsonVariant/set.cpp +++ b/extras/tests/JsonVariant/set.cpp @@ -17,6 +17,15 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { JsonDocument doc(&spy); JsonVariant variant = doc.to(); + SECTION("string literal") { + bool result = variant.set("hello\0world"); + + REQUIRE(result == true); + CHECK(variant == + "hello"_s); // linked string cannot contain '\0' at the moment + CHECK(spy.log() == AllocatorLog{}); + } + SECTION("const char*") { char str[16]; @@ -25,8 +34,10 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { strcpy(str, "world"); REQUIRE(result == true); - REQUIRE(variant == "world"); // stores by pointer - REQUIRE(spy.log() == AllocatorLog{}); + REQUIRE(variant == "hello"); // stores by copy + REQUIRE(spy.log() == AllocatorLog{ + Allocate(sizeofString("hello")), + }); } SECTION("(const char*)0") { @@ -34,6 +45,7 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { REQUIRE(result == true); REQUIRE(variant.isNull()); + REQUIRE(variant.as() == nullptr); REQUIRE(spy.log() == AllocatorLog{}); } @@ -105,16 +117,14 @@ TEST_CASE("JsonVariant::set() when there is enough memory") { #endif SECTION("std::string") { - std::string str; - - str = "hello"; + std::string str = "hello\0world"_s; bool result = variant.set(str); str.replace(0, 5, "world"); REQUIRE(result == true); - REQUIRE(variant == "hello"); // stores by copy + REQUIRE(variant == "hello\0world"_s); // stores by copy REQUIRE(spy.log() == AllocatorLog{ - Allocate(sizeofString("hello")), + Allocate(sizeofString("hello?world")), }); } diff --git a/extras/tests/JsonVariant/types.cpp b/extras/tests/JsonVariant/types.cpp index eb0ee628..b04c91f1 100644 --- a/extras/tests/JsonVariant/types.cpp +++ b/extras/tests/JsonVariant/types.cpp @@ -7,14 +7,8 @@ #include #include -template -void checkValue(T expected) { - JsonDocument doc; - JsonVariant variant = doc.to(); - - variant.set(expected); - REQUIRE(expected == variant.as()); -} +#include "Allocators.hpp" +#include "Literals.hpp" template void checkReference(T& expected) { @@ -39,27 +33,29 @@ void checkNumericType() { } TEST_CASE("JsonVariant set()/get()") { + SpyingAllocator spy; + JsonDocument doc(&spy); + JsonVariant variant = doc.to(); + #if ARDUINOJSON_USE_LONG_LONG SECTION("SizeOfJsonInteger") { REQUIRE(8 == sizeof(JsonInteger)); } #endif - SECTION("Null") { - checkValue(NULL); - } - SECTION("const char*") { - checkValue("hello"); - } - SECTION("std::string") { - checkValue("hello"); - } + // /!\ Most test were moved to `JsonVariant/set.cpp` + // TODO: move the remaining tests too SECTION("False") { - checkValue(false); + variant.set(false); + REQUIRE(variant.as() == false); + REQUIRE(spy.log() == AllocatorLog{}); } + SECTION("True") { - checkValue(true); + variant.set(true); + REQUIRE(variant.as() == true); + REQUIRE(spy.log() == AllocatorLog{}); } SECTION("Double") { @@ -129,10 +125,12 @@ TEST_CASE("JsonVariant set()/get()") { #endif SECTION("CanStoreObject") { - JsonDocument doc; - JsonObject object = doc.to(); + JsonDocument doc2; + JsonObject object = doc2.to(); - checkValue(object); + variant.set(object); + REQUIRE(variant.is()); + REQUIRE(variant.as() == object); } } diff --git a/extras/tests/JsonVariantConst/subscript.cpp b/extras/tests/JsonVariantConst/subscript.cpp index 735506ae..a207c531 100644 --- a/extras/tests/JsonVariantConst/subscript.cpp +++ b/extras/tests/JsonVariantConst/subscript.cpp @@ -54,13 +54,22 @@ TEST_CASE("JsonVariantConst::operator[]") { object["abc"_s] = "ABC"; object["abc\0d"_s] = "ABCD"; - SECTION("supports const char*") { + SECTION("string literal") { REQUIRE(var["ab"] == "AB"_s); REQUIRE(var["abc"] == "ABC"_s); + REQUIRE(var["abc\0d"] == "ABCD"_s); REQUIRE(var["def"].isNull()); REQUIRE(var[0].isNull()); } + SECTION("const char*") { + REQUIRE(var[static_cast("ab")] == "AB"_s); + REQUIRE(var[static_cast("abc")] == "ABC"_s); + REQUIRE(var[static_cast("abc\0d")] == "ABC"_s); + REQUIRE(var[static_cast("def")].isNull()); + REQUIRE(var[static_cast(0)].isNull()); + } + SECTION("supports std::string") { REQUIRE(var["ab"_s] == "AB"_s); REQUIRE(var["abc"_s] == "ABC"_s); diff --git a/extras/tests/Misc/StringAdapters.cpp b/extras/tests/Misc/StringAdapters.cpp index b33ab990..dbf5f06c 100644 --- a/extras/tests/Misc/StringAdapters.cpp +++ b/extras/tests/Misc/StringAdapters.cpp @@ -16,20 +16,29 @@ using ArduinoJson::JsonString; using namespace ArduinoJson::detail; TEST_CASE("adaptString()") { + SECTION("string literal") { + auto s = adaptString("bravo\0alpha"); + + CHECK(s.isNull() == false); + CHECK(s.size() == 11); + CHECK(s.isLinked() == true); + } + SECTION("null const char*") { auto s = adaptString(static_cast(0)); CHECK(s.isNull() == true); CHECK(s.size() == 0); - CHECK(s.isLinked() == true); } SECTION("non-null const char*") { - auto s = adaptString("bravo"); + const char* p = "bravo"; + auto s = adaptString(p); CHECK(s.isNull() == false); CHECK(s.size() == 5); - CHECK(s.isLinked() == true); + CHECK(s.isLinked() == false); + CHECK(s.data() == p); } SECTION("null const char* + size") { diff --git a/extras/tests/MsgPackSerializer/serializeVariant.cpp b/extras/tests/MsgPackSerializer/serializeVariant.cpp index 43ca3ff4..505b56ce 100644 --- a/extras/tests/MsgPackSerializer/serializeVariant.cpp +++ b/extras/tests/MsgPackSerializer/serializeVariant.cpp @@ -139,7 +139,8 @@ TEST_CASE("serialize MsgPack value") { SECTION("str 32") { std::string shortest(65536, '?'); - checkVariant(shortest.c_str(), "\xDB\x00\x01\x00\x00"_s + shortest); + checkVariant(JsonString(shortest.c_str(), true), // force store by pointer + "\xDB\x00\x01\x00\x00"_s + shortest); } SECTION("serialized(const char*)") { diff --git a/src/ArduinoJson/Array/JsonArray.hpp b/src/ArduinoJson/Array/JsonArray.hpp index e9cbca71..565cff26 100644 --- a/src/ArduinoJson/Array/JsonArray.hpp +++ b/src/ArduinoJson/Array/JsonArray.hpp @@ -66,7 +66,8 @@ class JsonArray : public detail::VariantOperators { // Appends a value to the array. // https://arduinojson.org/v7/api/jsonarray/add/ - template + template ::value>> bool add(T* value) const { return detail::ArrayData::addValue(data_, value, resources_); } diff --git a/src/ArduinoJson/Document/JsonDocument.hpp b/src/ArduinoJson/Document/JsonDocument.hpp index 9916e296..40393080 100644 --- a/src/ArduinoJson/Document/JsonDocument.hpp +++ b/src/ArduinoJson/Document/JsonDocument.hpp @@ -144,7 +144,8 @@ class JsonDocument : public detail::VariantOperators { // Replaces the root with the specified value. // https://arduinojson.org/v7/api/jsondocument/set/ - template + template ::value>> bool set(TChar* src) { return to().set(src); } @@ -197,7 +198,7 @@ class JsonDocument : public detail::VariantOperators { // https://arduinojson.org/v7/api/jsondocument/subscript/ template detail::enable_if_t< - detail::IsString::value, + detail::IsString::value && !detail::is_const::value, detail::MemberProxy>> operator[](TChar* key) { return {*this, detail::adaptString(key)}; @@ -215,7 +216,9 @@ class JsonDocument : public detail::VariantOperators { // Gets a root object's member. // https://arduinojson.org/v7/api/jsondocument/subscript/ template - detail::enable_if_t::value, JsonVariantConst> + detail::enable_if_t::value && + !detail::is_const::value, + JsonVariantConst> operator[](TChar* key) const { return JsonVariantConst( data_.getMember(detail::adaptString(key), &resources_), &resources_); @@ -273,7 +276,8 @@ class JsonDocument : public detail::VariantOperators { // Appends a value to the root array. // https://arduinojson.org/v7/api/jsondocument/add/ - template + template ::value>> bool add(TChar* value) { return data_.addValue(value, &resources_); } @@ -289,7 +293,9 @@ class JsonDocument : public detail::VariantOperators { // Removes a member of the root object. // https://arduinojson.org/v7/api/jsondocument/remove/ template - detail::enable_if_t::value> remove(TChar* key) { + detail::enable_if_t::value && + !detail::is_const::value> + remove(TChar* key) { detail::VariantData::removeMember(getData(), detail::adaptString(key), getResourceManager()); } diff --git a/src/ArduinoJson/Object/JsonObject.hpp b/src/ArduinoJson/Object/JsonObject.hpp index afcb8d75..c90b2955 100644 --- a/src/ArduinoJson/Object/JsonObject.hpp +++ b/src/ArduinoJson/Object/JsonObject.hpp @@ -113,7 +113,7 @@ class JsonObject : public detail::VariantOperators { // https://arduinojson.org/v7/api/jsonobject/subscript/ template detail::enable_if_t< - detail::IsString::value, + detail::IsString::value && !detail::is_const::value, detail::MemberProxy>> operator[](TChar* key) const { return {*this, detail::adaptString(key)}; @@ -175,8 +175,9 @@ class JsonObject : public detail::VariantOperators { // https://arduinojson.org/v7/api/jsonobject/containskey/ template ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") - detail::enable_if_t::value, bool> containsKey( - TChar* key) const { + detail::enable_if_t::value && + !detail::is_const::value, + bool> containsKey(TChar* key) const { return detail::ObjectData::getMember(data_, detail::adaptString(key), resources_) != 0; } diff --git a/src/ArduinoJson/Object/JsonObjectConst.hpp b/src/ArduinoJson/Object/JsonObjectConst.hpp index 6b0de202..91d9dca5 100644 --- a/src/ArduinoJson/Object/JsonObjectConst.hpp +++ b/src/ArduinoJson/Object/JsonObjectConst.hpp @@ -109,7 +109,9 @@ class JsonObjectConst : public detail::VariantOperators { // Gets the member with specified key. // https://arduinojson.org/v7/api/jsonobjectconst/subscript/ template - detail::enable_if_t::value, JsonVariantConst> + detail::enable_if_t::value && + !detail::is_const::value, + JsonVariantConst> operator[](TChar* key) const { return JsonVariantConst(detail::ObjectData::getMember( data_, detail::adaptString(key), resources_), diff --git a/src/ArduinoJson/Object/MemberProxy.hpp b/src/ArduinoJson/Object/MemberProxy.hpp index 2d297585..ffd89939 100644 --- a/src/ArduinoJson/Object/MemberProxy.hpp +++ b/src/ArduinoJson/Object/MemberProxy.hpp @@ -39,7 +39,7 @@ class MemberProxy return *this; } - template + template ::value>> MemberProxy& operator=(T* src) { this->set(src); return *this; diff --git a/src/ArduinoJson/Strings/Adapters/RamString.hpp b/src/ArduinoJson/Strings/Adapters/RamString.hpp index e506d6a7..b191b245 100644 --- a/src/ArduinoJson/Strings/Adapters/RamString.hpp +++ b/src/ArduinoJson/Strings/Adapters/RamString.hpp @@ -76,6 +76,15 @@ struct StringAdapter::value>> { } }; +template +struct StringAdapter { + using AdaptedString = RamString; + + static AdaptedString adapt(const char (&p)[N]) { + return RamString(p, N - 1, true); + } +}; + template struct StringAdapter::value>> { using AdaptedString = RamString; @@ -86,15 +95,6 @@ struct StringAdapter::value>> { } }; -template <> -struct StringAdapter { - using AdaptedString = RamString; - - static AdaptedString adapt(const char* p) { - return AdaptedString(p, p ? ::strlen(p) : 0, true); - } -}; - template struct SizedStringAdapter::value>> { using AdaptedString = RamString; diff --git a/src/ArduinoJson/Strings/IsString.hpp b/src/ArduinoJson/Strings/IsString.hpp index 45ef7995..a71655db 100644 --- a/src/ArduinoJson/Strings/IsString.hpp +++ b/src/ArduinoJson/Strings/IsString.hpp @@ -13,7 +13,7 @@ template struct IsString : false_type {}; template -struct IsString::AdaptedString>> +struct IsString::AdaptedString>> : true_type {}; ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Strings/StringAdapter.hpp b/src/ArduinoJson/Strings/StringAdapter.hpp index 9cf2e6fd..50324ffb 100644 --- a/src/ArduinoJson/Strings/StringAdapter.hpp +++ b/src/ArduinoJson/Strings/StringAdapter.hpp @@ -4,8 +4,17 @@ #pragma once +#include + ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE +// a meta function that tells if the type is a string literal (const char[N]) +template +struct IsStringLiteral : false_type {}; + +template +struct IsStringLiteral : true_type {}; + template struct StringAdapter; @@ -13,18 +22,25 @@ template struct SizedStringAdapter; template -typename StringAdapter::AdaptedString adaptString(const TString& s) { - return StringAdapter::adapt(s); +using StringAdapterFor = + StringAdapter::value, TString, + remove_cv_t>>>; + +template +using AdaptedString = typename StringAdapterFor::AdaptedString; + +template +AdaptedString adaptString(TString&& s) { + return StringAdapterFor::adapt(detail::forward(s)); } -template -typename StringAdapter::AdaptedString adaptString(TChar* p) { +template ::value>> +AdaptedString adaptString(TChar* p) { return StringAdapter::adapt(p); } template -typename SizedStringAdapter::AdaptedString adaptString(TChar* p, - size_t n) { +AdaptedString adaptString(TChar* p, size_t n) { return SizedStringAdapter::adapt(p, n); } diff --git a/src/ArduinoJson/Strings/StringAdapters.hpp b/src/ArduinoJson/Strings/StringAdapters.hpp index a558defd..5da554ce 100644 --- a/src/ArduinoJson/Strings/StringAdapters.hpp +++ b/src/ArduinoJson/Strings/StringAdapters.hpp @@ -70,8 +70,4 @@ static void stringGetChars(TAdaptedString s, char* p, size_t n) { } } -template -using AdaptedString = - typename StringAdapter>::AdaptedString; - ARDUINOJSON_END_PRIVATE_NAMESPACE diff --git a/src/ArduinoJson/Variant/ConverterImpl.hpp b/src/ArduinoJson/Variant/ConverterImpl.hpp index cd939595..1709ceaa 100644 --- a/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -31,7 +31,7 @@ struct Converter { // clang-format on } - static T fromJson(JsonVariantConst src) { + static detail::decay_t fromJson(JsonVariantConst src) { static_assert(!detail::is_same::value, "type 'char*' is not supported, use 'const char*' instead"); diff --git a/src/ArduinoJson/Variant/JsonVariantConst.hpp b/src/ArduinoJson/Variant/JsonVariantConst.hpp index 4389bb8c..f87b56dd 100644 --- a/src/ArduinoJson/Variant/JsonVariantConst.hpp +++ b/src/ArduinoJson/Variant/JsonVariantConst.hpp @@ -120,7 +120,9 @@ class JsonVariantConst : public detail::VariantTag, // Gets object's member with specified key. // https://arduinojson.org/v7/api/jsonvariantconst/subscript/ template - detail::enable_if_t::value, JsonVariantConst> + detail::enable_if_t::value && + !detail::is_const::value, + JsonVariantConst> operator[](TChar* key) const { return JsonVariantConst(detail::VariantData::getMember( data_, detail::adaptString(key), resources_), @@ -153,8 +155,9 @@ class JsonVariantConst : public detail::VariantTag, // https://arduinojson.org/v7/api/jsonvariantconst/containskey/ template ARDUINOJSON_DEPRECATED("use obj[\"key\"].is() instead") - detail::enable_if_t::value, bool> containsKey( - TChar* key) const { + detail::enable_if_t::value && + !detail::is_const::value, + bool> containsKey(TChar* key) const { return detail::VariantData::getMember(getData(), detail::adaptString(key), resources_) != 0; } diff --git a/src/ArduinoJson/Variant/VariantRefBase.hpp b/src/ArduinoJson/Variant/VariantRefBase.hpp index 5a4c59be..2dc86d13 100644 --- a/src/ArduinoJson/Variant/VariantRefBase.hpp +++ b/src/ArduinoJson/Variant/VariantRefBase.hpp @@ -77,12 +77,15 @@ class VariantRefBase : public VariantTag { // https://arduinojson.org/v7/api/jsonvariant/set/ template bool set(const T& value) const { - return doSet>>(value); + using TypeForConverter = conditional_t::value, T, + remove_cv_t>>; + return doSet>(value); } // Copies the specified value. // https://arduinojson.org/v7/api/jsonvariant/set/ - template + template ::value>> bool set(T* value) const { return doSet>(value); } @@ -123,7 +126,7 @@ class VariantRefBase : public VariantTag { // Appends a value to the array. // https://arduinojson.org/v7/api/jsonvariant/add/ - template + template ::value>> bool add(T* value) const { return detail::VariantData::addValue(getOrCreateData(), value, getResourceManager()); @@ -195,7 +198,7 @@ class VariantRefBase : public VariantTag { // Gets or sets an object member. // https://arduinojson.org/v7/api/jsonvariant/subscript/ template - FORCE_INLINE enable_if_t::value, + FORCE_INLINE enable_if_t::value && !is_const::value, MemberProxy>> operator[](TChar* key) const; diff --git a/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp b/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp index 1a0685f3..4f22ffd4 100644 --- a/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp +++ b/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp @@ -124,7 +124,7 @@ inline ElementProxy VariantRefBase::operator[]( template template -inline enable_if_t::value, +inline enable_if_t::value && !is_const::value, MemberProxy>> VariantRefBase::operator[](TChar* key) const { return {derived(), adaptString(key)};