diff --git a/CHANGELOG.md b/CHANGELOG.md index f7a3fca9..d2d0d4c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ArduinoJson: change log HEAD ---- +* Add `JsonVariant::link()` (issue #1343) * Fix `9.22337e+18 is outside the range of representable values of type 'long'` v6.19.4 (2022-04-05) diff --git a/extras/tests/Cpp11/nullptr.cpp b/extras/tests/Cpp11/nullptr.cpp index 813b9cc2..ae0eb634 100644 --- a/extras/tests/Cpp11/nullptr.cpp +++ b/extras/tests/Cpp11/nullptr.cpp @@ -43,5 +43,13 @@ TEST_CASE("nullptr") { variant.clear(); REQUIRE(variant.is() == true); + + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + REQUIRE(variant.is() == false); + + doc2.clear(); + REQUIRE(variant.is() == true); } } diff --git a/extras/tests/JsonDocument/ElementProxy.cpp b/extras/tests/JsonDocument/ElementProxy.cpp index 0fe3c471..e54560c1 100644 --- a/extras/tests/JsonDocument/ElementProxy.cpp +++ b/extras/tests/JsonDocument/ElementProxy.cpp @@ -245,3 +245,11 @@ TEST_CASE("ElementProxy cast to JsonVariant") { CHECK(doc.as() == "[\"toto\"]"); } + +TEST_CASE("ElementProxy::link()") { + StaticJsonDocument<1024> doc1, doc2; + doc1[0].link(doc2); + doc2["hello"] = "world"; + + CHECK(doc1.as() == "[{\"hello\":\"world\"}]"); +} diff --git a/extras/tests/JsonDocument/MemberProxy.cpp b/extras/tests/JsonDocument/MemberProxy.cpp index 087f5392..a4de1257 100644 --- a/extras/tests/JsonDocument/MemberProxy.cpp +++ b/extras/tests/JsonDocument/MemberProxy.cpp @@ -318,3 +318,10 @@ TEST_CASE("MemberProxy::createNestedObject(key)") { CHECK(doc["status"]["weather"]["temp"] == 42); } +TEST_CASE("MemberProxy::link()") { + StaticJsonDocument<1024> doc1, doc2; + doc1["obj"].link(doc2); + doc2["hello"] = "world"; + + CHECK(doc1.as() == "{\"obj\":{\"hello\":\"world\"}}"); +} diff --git a/extras/tests/JsonDocument/size.cpp b/extras/tests/JsonDocument/size.cpp index 4fbb6e23..683cc733 100644 --- a/extras/tests/JsonDocument/size.cpp +++ b/extras/tests/JsonDocument/size.cpp @@ -25,4 +25,21 @@ TEST_CASE("JsonDocument::size()") { REQUIRE(doc.size() == 2); } + + SECTION("linked array") { + StaticJsonDocument<128> doc2; + doc2.add(1); + doc2.add(2); + doc.as().link(doc2); + + REQUIRE(doc.size() == 2); + } + + SECTION("linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + doc.as().link(doc2); + + REQUIRE(doc.size() == 1); + } } diff --git a/extras/tests/JsonVariant/CMakeLists.txt b/extras/tests/JsonVariant/CMakeLists.txt index 78f810e1..35cf3818 100644 --- a/extras/tests/JsonVariant/CMakeLists.txt +++ b/extras/tests/JsonVariant/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(JsonVariantTests createNested.cpp is.cpp isnull.cpp + link.cpp memoryUsage.cpp misc.cpp nesting.cpp diff --git a/extras/tests/JsonVariant/add.cpp b/extras/tests/JsonVariant/add.cpp index 526e914d..fec99c5a 100644 --- a/extras/tests/JsonVariant/add.cpp +++ b/extras/tests/JsonVariant/add.cpp @@ -43,4 +43,14 @@ TEST_CASE("JsonVariant::add()") { REQUIRE(var.as() == "{\"val\":123}"); } + + SECTION("add to linked array") { + StaticJsonDocument<1024> doc2; + doc2.add(42); + var.link(doc2); + + var.add(666); // no-op + + CHECK(var.as() == "[42]"); + } } diff --git a/extras/tests/JsonVariant/as.cpp b/extras/tests/JsonVariant/as.cpp index c9956778..43c30185 100644 --- a/extras/tests/JsonVariant/as.cpp +++ b/extras/tests/JsonVariant/as.cpp @@ -267,4 +267,97 @@ TEST_CASE("JsonVariant::as()") { REQUIRE(variant.as() == ONE); } + + SECTION("linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + + SECTION("as()") { + CHECK(variant.as() == "{\"hello\":\"world\"}"); + } + + SECTION("as()") { + JsonArray a = variant.as(); + CHECK(a.isNull() == true); + } + + SECTION("as()") { + JsonObject o = variant.as(); + CHECK(o.isNull() == true); + } + + SECTION("as()") { + JsonObjectConst o = variant.as(); + CHECK(o.isNull() == false); + CHECK(o.size() == 1); + CHECK(o["hello"] == "world"); + } + } + + SECTION("linked array") { + StaticJsonDocument<128> doc2; + doc2.add("hello"); + doc2.add("world"); + variant.link(doc2); + + SECTION("as()") { + CHECK(variant.as() == "[\"hello\",\"world\"]"); + } + + SECTION("as()") { + JsonArray a = variant.as(); + CHECK(a.isNull() == true); + } + + SECTION("as()") { + JsonArrayConst a = variant.as(); + CHECK(a.isNull() == false); + CHECK(a.size() == 2); + CHECK(a[0] == "hello"); + CHECK(a[1] == "world"); + } + + SECTION("as()") { + JsonObject o = variant.as(); + CHECK(o.isNull() == true); + } + } + + SECTION("linked int") { + StaticJsonDocument<128> doc2; + doc2.set(42); + variant.link(doc2); + + CHECK(variant.as() == 42); + CHECK(variant.as() == 42.0); + } + + SECTION("linked double") { + StaticJsonDocument<128> doc2; + doc2.set(42.0); + variant.link(doc2); + + CHECK(variant.as() == 42); + CHECK(variant.as() == 42.0); + } + + SECTION("linked string") { + StaticJsonDocument<128> doc2; + doc2.set("hello"); + variant.link(doc2); + + CHECK(variant.as() == "hello"); + } + + SECTION("linked bool") { + StaticJsonDocument<128> doc2; + variant.link(doc2); + + doc2.set(true); + CHECK(variant.as() == true); + + doc2.set(false); + CHECK(variant.as() == false); + } } diff --git a/extras/tests/JsonVariant/clear.cpp b/extras/tests/JsonVariant/clear.cpp index 2b40e324..173a7dea 100644 --- a/extras/tests/JsonVariant/clear.cpp +++ b/extras/tests/JsonVariant/clear.cpp @@ -23,4 +23,15 @@ TEST_CASE("JsonVariant::clear()") { REQUIRE(var.isNull() == true); } + + SECTION("doesn't alter linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + var.link(doc2); + + var.clear(); + + CHECK(var.isNull() == true); + CHECK(doc2.as() == "{\"hello\":\"world\"}"); + } } diff --git a/extras/tests/JsonVariant/compare.cpp b/extras/tests/JsonVariant/compare.cpp index 63adbc26..c6211a55 100644 --- a/extras/tests/JsonVariant/compare.cpp +++ b/extras/tests/JsonVariant/compare.cpp @@ -34,6 +34,20 @@ TEST_CASE("Compare JsonVariant with value") { CHECK_FALSE(a < b); CHECK_FALSE(a > b); } + + SECTION("linked 42 vs 42") { + StaticJsonDocument<128> doc2; + doc2.set(42); + a.link(doc2); + int b = 42; + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } } TEST_CASE("Compare JsonVariant with JsonVariant") { @@ -313,4 +327,19 @@ TEST_CASE("Compare JsonVariant with JsonVariant") { CHECK_FALSE(a > b); CHECK_FALSE(a >= b); } + + SECTION("linked 42 vs link 42") { + StaticJsonDocument<128> doc2, doc3; + doc2.set(42); + doc3.set(42); + a.link(doc2); + b.link(doc3); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } } diff --git a/extras/tests/JsonVariant/containsKey.cpp b/extras/tests/JsonVariant/containsKey.cpp index 51bf500f..739e33ec 100644 --- a/extras/tests/JsonVariant/containsKey.cpp +++ b/extras/tests/JsonVariant/containsKey.cpp @@ -12,19 +12,28 @@ TEST_CASE("JsonVariant::containsKey()") { DynamicJsonDocument doc(4096); JsonVariant var = doc.to(); - SECTION("containsKey(const char*) returns true") { + SECTION("containsKey(const char*)") { var["hello"] = "world"; REQUIRE(var.containsKey("hello") == true); REQUIRE(var.containsKey("world") == false); } - SECTION("containsKey(std::string) returns true") { + SECTION("containsKey(std::string)") { var["hello"] = "world"; REQUIRE(var.containsKey(std::string("hello")) == true); REQUIRE(var.containsKey(std::string("world")) == false); } + + SECTION("linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + var.link(doc2); + + CHECK(var.containsKey("hello") == true); + CHECK(var.containsKey("world") == false); + } } TEST_CASE("JsonVariantConst::containsKey()") { diff --git a/extras/tests/JsonVariant/copy.cpp b/extras/tests/JsonVariant/copy.cpp index 7784f0ca..b8f96297 100644 --- a/extras/tests/JsonVariant/copy.cpp +++ b/extras/tests/JsonVariant/copy.cpp @@ -84,6 +84,16 @@ TEST_CASE("JsonVariant::set(JsonVariant)") { REQUIRE(doc2.memoryUsage() == JSON_STRING_SIZE(7)); } + SECTION("stores linked object by pointer") { + StaticJsonDocument<128> doc3; + doc3["hello"] = "world"; + var1.link(doc3); + var2.set(var1); + + REQUIRE(doc1.memoryUsage() == 0); + REQUIRE(doc2.memoryUsage() == 0); + } + SECTION("destination is unbound") { JsonVariant unboundVariant; diff --git a/extras/tests/JsonVariant/createNested.cpp b/extras/tests/JsonVariant/createNested.cpp index efd75015..0636762e 100644 --- a/extras/tests/JsonVariant/createNested.cpp +++ b/extras/tests/JsonVariant/createNested.cpp @@ -18,6 +18,17 @@ TEST_CASE("JsonVariant::createNestedObject()") { REQUIRE(variant[0]["value"] == 42); REQUIRE(obj.isNull() == false); } + + SECTION("does nothing on linked array") { + StaticJsonDocument<128> doc2; + doc2[0] = 42; + variant.link(doc2); + + variant.createNestedObject(); + + CHECK(variant.size() == 1); + CHECK(variant[0] == 42); + } } TEST_CASE("JsonVariant::createNestedArray()") { @@ -30,6 +41,17 @@ TEST_CASE("JsonVariant::createNestedArray()") { REQUIRE(variant.is() == true); REQUIRE(arr.isNull() == false); } + + SECTION("does nothing on linked array") { + StaticJsonDocument<128> doc2; + doc2[0] = 42; + variant.link(doc2); + + variant.createNestedArray(); + + CHECK(variant.size() == 1); + CHECK(variant[0] == 42); + } } TEST_CASE("JsonVariant::createNestedObject(key)") { @@ -43,6 +65,17 @@ TEST_CASE("JsonVariant::createNestedObject(key)") { REQUIRE(variant.is() == true); REQUIRE(variant["weather"]["temp"] == 42); } + + SECTION("does nothing on linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + + variant.createNestedObject("weather"); + + CHECK(variant.size() == 1); + CHECK(variant["hello"] == "world"); + } } TEST_CASE("JsonVariant::createNestedArray(key)") { @@ -55,4 +88,15 @@ TEST_CASE("JsonVariant::createNestedArray(key)") { REQUIRE(variant.is() == true); REQUIRE(arr.isNull() == false); } + + SECTION("does nothing on linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + + variant.createNestedArray("items"); + + CHECK(variant.size() == 1); + CHECK(variant["hello"] == "world"); + } } diff --git a/extras/tests/JsonVariant/is.cpp b/extras/tests/JsonVariant/is.cpp index 6d04f0af..0cc63e3d 100644 --- a/extras/tests/JsonVariant/is.cpp +++ b/extras/tests/JsonVariant/is.cpp @@ -144,6 +144,24 @@ TEST_CASE("JsonVariant::is()") { CHECK(variant.is() == false); } + SECTION("linked array") { + StaticJsonDocument<1024> doc2; + doc2[0] = "world"; + variant.link(doc2); + + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + } + SECTION("JsonObject") { variant.to(); @@ -161,6 +179,44 @@ TEST_CASE("JsonVariant::is()") { CHECK(variant.is() == true); CHECK(variant.is() == true); } + + SECTION("linked object") { + StaticJsonDocument<1024> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + } + + SECTION("linked int") { + StaticJsonDocument<1024> doc2; + doc2.set(42); + variant.link(doc2); + + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == true); + CHECK(variant.is() == true); + CHECK(variant.is() == false); + CHECK(variant.is() == false); + CHECK(variant.is() == true); + } } TEST_CASE("JsonVariantConst::is()") { @@ -316,4 +372,58 @@ TEST_CASE("JsonVariantConst::is()") { CHECK(cvariant.is() == false); CHECK(cvariant.is() == false); } + + SECTION("linked array") { + StaticJsonDocument<1024> doc2; + doc2[0] = "world"; + variant.link(doc2); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("linked object") { + StaticJsonDocument<1024> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + } + + SECTION("linked int") { + StaticJsonDocument<1024> doc2; + doc2.set(42); + variant.link(doc2); + + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == true); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == false); + CHECK(cvariant.is() == true); + } } diff --git a/extras/tests/JsonVariant/isnull.cpp b/extras/tests/JsonVariant/isnull.cpp index 7e29039f..ec5e0a34 100644 --- a/extras/tests/JsonVariant/isnull.cpp +++ b/extras/tests/JsonVariant/isnull.cpp @@ -9,17 +9,17 @@ TEST_CASE("JsonVariant::isNull()") { DynamicJsonDocument doc(4096); JsonVariant variant = doc.to(); - SECTION("return true when Undefined") { + SECTION("returns true when Undefined") { REQUIRE(variant.isNull() == true); } - SECTION("return false when Integer") { + SECTION("returns false when Integer") { variant.set(42); REQUIRE(variant.isNull() == false); } - SECTION("return false when EmptyArray") { + SECTION("returns false when EmptyArray") { DynamicJsonDocument doc2(4096); JsonArray array = doc2.to(); @@ -27,7 +27,7 @@ TEST_CASE("JsonVariant::isNull()") { REQUIRE(variant.isNull() == false); } - SECTION("return false when EmptyObject") { + SECTION("returns false when EmptyObject") { DynamicJsonDocument doc2(4096); JsonObject obj = doc2.to(); @@ -35,41 +35,54 @@ TEST_CASE("JsonVariant::isNull()") { REQUIRE(variant.isNull() == false); } - SECTION("return true after set(JsonArray())") { + SECTION("returns true after set(JsonArray())") { variant.set(JsonArray()); REQUIRE(variant.isNull() == true); } - SECTION("return true after set(JsonObject())") { + SECTION("returns true after set(JsonObject())") { variant.set(JsonObject()); REQUIRE(variant.isNull() == true); } - SECTION("return false after set('hello')") { + SECTION("returns false after set('hello')") { variant.set("hello"); REQUIRE(variant.isNull() == false); } - SECTION("return true after set((char*)0)") { + SECTION("returns true after set((char*)0)") { variant.set(static_cast(0)); REQUIRE(variant.isNull() == true); } - SECTION("return true after set((const char*)0)") { + SECTION("returns true after set((const char*)0)") { variant.set(static_cast(0)); REQUIRE(variant.isNull() == true); } - SECTION("return true after set(serialized((char*)0))") { + SECTION("returns true after set(serialized((char*)0))") { variant.set(serialized(static_cast(0))); REQUIRE(variant.isNull() == true); } - SECTION("return true after set(serialized((const char*)0))") { + SECTION("returns true after set(serialized((const char*)0))") { variant.set(serialized(static_cast(0))); REQUIRE(variant.isNull() == true); } + SECTION("returns true for a linked null") { + StaticJsonDocument<128> doc2; + variant.link(doc2); + CHECK(variant.isNull() == true); + } + + SECTION("returns false for a linked array") { + StaticJsonDocument<128> doc2; + doc2[0] = 42; + variant.link(doc2); + CHECK(variant.isNull() == false); + } + SECTION("works with JsonVariantConst") { variant.set(42); diff --git a/extras/tests/JsonVariant/link.cpp b/extras/tests/JsonVariant/link.cpp new file mode 100644 index 00000000..28ef2ecd --- /dev/null +++ b/extras/tests/JsonVariant/link.cpp @@ -0,0 +1,77 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::link()") { + StaticJsonDocument<1024> doc1, doc2; + JsonVariant variant = doc1.to(); + + SECTION("JsonVariant::link(JsonDocument&)") { + doc2["hello"] = "world"; + + variant.link(doc2); + + CHECK(variant.as() == "{\"hello\":\"world\"}"); + CHECK(variant.memoryUsage() == 0); + + // altering the linked document should change the result + doc2["hello"] = "WORLD!"; + + CHECK(variant.as() == "{\"hello\":\"WORLD!\"}"); + } + + SECTION("JsonVariant::link(MemberProxy)") { + doc2["obj"]["hello"] = "world"; + + variant.link(doc2["obj"]); + + CHECK(variant.as() == "{\"hello\":\"world\"}"); + CHECK(variant.memoryUsage() == 0); + + // altering the linked document should change the result + doc2["obj"]["hello"] = "WORLD!"; + + CHECK(variant.as() == "{\"hello\":\"WORLD!\"}"); + } + + SECTION("JsonVariant::link(ElementProxy)") { + doc2[0]["hello"] = "world"; + + variant.link(doc2[0]); + + CHECK(variant.as() == "{\"hello\":\"world\"}"); + CHECK(variant.memoryUsage() == 0); + + // altering the linked document should change the result + doc2[0]["hello"] = "WORLD!"; + + CHECK(variant.as() == "{\"hello\":\"WORLD!\"}"); + } + + SECTION("target is unbound") { + JsonVariant unbound; + variant["hello"] = "world"; + + variant.link(unbound); + + CHECK(variant.isUnbound() == false); + CHECK(variant.isNull() == true); + CHECK(variant.memoryUsage() == 0); + CHECK(variant.size() == 0); + } + + SECTION("variant is unbound") { + JsonVariant unbound; + doc2["hello"] = "world"; + + unbound.link(doc2); + + CHECK(unbound.isUnbound() == true); + CHECK(unbound.isNull() == true); + CHECK(unbound.memoryUsage() == 0); + CHECK(unbound.size() == 0); + } +} diff --git a/extras/tests/JsonVariant/memoryUsage.cpp b/extras/tests/JsonVariant/memoryUsage.cpp index 59587dbb..5c18b906 100644 --- a/extras/tests/JsonVariant/memoryUsage.cpp +++ b/extras/tests/JsonVariant/memoryUsage.cpp @@ -38,4 +38,12 @@ TEST_CASE("JsonVariant::memoryUsage()") { REQUIRE(var.memoryUsage() == 6); REQUIRE(var.memoryUsage() == doc.memoryUsage()); } + + SECTION("ignore size of linked document") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + var.link(doc2); + CHECK(var.memoryUsage() == 0); + CHECK(var.memoryUsage() == doc.memoryUsage()); + } } diff --git a/extras/tests/JsonVariant/nesting.cpp b/extras/tests/JsonVariant/nesting.cpp index 3e936fbe..e7602e51 100644 --- a/extras/tests/JsonVariant/nesting.cpp +++ b/extras/tests/JsonVariant/nesting.cpp @@ -28,4 +28,12 @@ TEST_CASE("JsonVariant::nesting()") { var.to(); REQUIRE(var.nesting() == 1); } + + SECTION("returns depth of linked array") { + StaticJsonDocument<128> doc2; + doc2[0][0] = 42; + var.link(doc2); + + CHECK(var.nesting() == 2); + } } diff --git a/extras/tests/JsonVariant/or.cpp b/extras/tests/JsonVariant/or.cpp index 7d1c190b..41bd2f79 100644 --- a/extras/tests/JsonVariant/or.cpp +++ b/extras/tests/JsonVariant/or.cpp @@ -156,4 +156,12 @@ TEST_CASE("JsonVariant::operator|()") { int result = variant | 42; REQUIRE(result == 42); } + + SECTION("linked int | int") { + StaticJsonDocument<128> doc2; + doc2.set(42); + variant.link(doc2); + int result = variant | 666; + CHECK(result == 42); + } } diff --git a/extras/tests/JsonVariant/remove.cpp b/extras/tests/JsonVariant/remove.cpp index c744e19b..adaa0761 100644 --- a/extras/tests/JsonVariant/remove.cpp +++ b/extras/tests/JsonVariant/remove.cpp @@ -39,4 +39,24 @@ TEST_CASE("JsonVariant::remove()") { REQUIRE(var.as() == "{\"a\":1}"); } + + SECTION("linked array") { + StaticJsonDocument<128> doc2; + doc2[0] = 42; + var.link(doc2); + + var.remove(0); + + CHECK(var.as() == "[42]"); + } + + SECTION("linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + var.link(doc2); + + var.remove("hello"); + + CHECK(var.as() == "{\"hello\":\"world\"}"); + } } diff --git a/extras/tests/JsonVariant/size.cpp b/extras/tests/JsonVariant/size.cpp index a08aaead..9db3c538 100644 --- a/extras/tests/JsonVariant/size.cpp +++ b/extras/tests/JsonVariant/size.cpp @@ -41,4 +41,13 @@ TEST_CASE("JsonVariant::size()") { CHECK(variant.size() == 1); } + + SECTION("linked array") { + StaticJsonDocument<1024> doc2; + doc2.add(1); + doc2.add(2); + variant.link(doc2); + + CHECK(variant.size() == 2); + } } diff --git a/extras/tests/JsonVariant/subscript.cpp b/extras/tests/JsonVariant/subscript.cpp index f466cd27..a1bba08c 100644 --- a/extras/tests/JsonVariant/subscript.cpp +++ b/extras/tests/JsonVariant/subscript.cpp @@ -129,6 +129,44 @@ TEST_CASE("JsonVariant::operator[]") { REQUIRE(std::string("world") == variant[vla]); } #endif + + SECTION("get value from linked object") { + StaticJsonDocument<1024> doc2; + doc2["hello"] = "world"; + var.link(doc2); + + CHECK(var["hello"].as() == "world"); + } + + SECTION("set value to linked object") { + StaticJsonDocument<1024> doc2; + doc2["hello"] = "world"; + var.link(doc2); + + var["tutu"] = "toto"; // no-op + + CHECK(doc.as() == "{\"hello\":\"world\"}"); + CHECK(doc2.as() == "{\"hello\":\"world\"}"); + } + + SECTION("get value from linked array") { + StaticJsonDocument<1024> doc2; + doc2.add(42); + var.link(doc2); + + CHECK(var[0].as() == 42); + } + + SECTION("set value to linked array") { + StaticJsonDocument<1024> doc2; + doc2.add(42); + var.link(doc2); + + var[0] = 666; // no-op + + CHECK(doc.as() == "[42]"); + CHECK(doc2.as() == "[42]"); + } } TEST_CASE("JsonVariantConst::operator[]") { @@ -201,4 +239,20 @@ TEST_CASE("JsonVariantConst::operator[]") { REQUIRE(var.is() == false); REQUIRE(value == 0); } + + SECTION("get value from linked object") { + StaticJsonDocument<1024> doc2; + doc2["hello"] = "world"; + var.link(doc2); + + CHECK(cvar["hello"].as() == "world"); + } + + SECTION("get value from linked array") { + StaticJsonDocument<1024> doc2; + doc2.add(42); + var.link(doc2); + + CHECK(cvar[0].as() == 42); + } } diff --git a/src/ArduinoJson/Array/ArrayRef.hpp b/src/ArduinoJson/Array/ArrayRef.hpp index e890bc95..e5962a49 100644 --- a/src/ArduinoJson/Array/ArrayRef.hpp +++ b/src/ArduinoJson/Array/ArrayRef.hpp @@ -211,7 +211,7 @@ struct Converter { static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isArray(); + return data && data->resolve()->isArray(); } }; diff --git a/src/ArduinoJson/Array/ElementProxy.hpp b/src/ArduinoJson/Array/ElementProxy.hpp index fe2d71cc..490d17ee 100644 --- a/src/ArduinoJson/Array/ElementProxy.hpp +++ b/src/ArduinoJson/Array/ElementProxy.hpp @@ -109,6 +109,10 @@ class ElementProxy : public VariantOperators >, return getOrAddUpstreamElement().template to(); } + FORCE_INLINE void link(VariantConstRef value) const { + getOrAddUpstreamElement().link(value); + } + // Replaces the value // // bool set(const TValue&) diff --git a/src/ArduinoJson/Document/JsonDocument.hpp b/src/ArduinoJson/Document/JsonDocument.hpp index d1d7cb63..9f0892a3 100644 --- a/src/ArduinoJson/Document/JsonDocument.hpp +++ b/src/ArduinoJson/Document/JsonDocument.hpp @@ -68,7 +68,7 @@ class JsonDocument : public Visitable, } size_t size() const { - return _data.size(); + return _data.resolve()->size(); } bool set(const JsonDocument& src) { diff --git a/src/ArduinoJson/Json/JsonSerializer.hpp b/src/ArduinoJson/Json/JsonSerializer.hpp index db5803c9..7c6f6424 100644 --- a/src/ArduinoJson/Json/JsonSerializer.hpp +++ b/src/ArduinoJson/Json/JsonSerializer.hpp @@ -25,7 +25,7 @@ class JsonSerializer : public Visitor { VariantSlot *slot = array.head(); while (slot != 0) { - slot->data()->accept(*this); + slot->data()->resolve()->accept(*this); slot = slot->next(); if (slot == 0) @@ -46,7 +46,7 @@ class JsonSerializer : public Visitor { while (slot != 0) { _formatter.writeString(slot->key()); write(':'); - slot->data()->accept(*this); + slot->data()->resolve()->accept(*this); slot = slot->next(); if (slot == 0) diff --git a/src/ArduinoJson/Json/PrettyJsonSerializer.hpp b/src/ArduinoJson/Json/PrettyJsonSerializer.hpp index cbd66540..3f7fd262 100644 --- a/src/ArduinoJson/Json/PrettyJsonSerializer.hpp +++ b/src/ArduinoJson/Json/PrettyJsonSerializer.hpp @@ -25,7 +25,7 @@ class PrettyJsonSerializer : public JsonSerializer { _nesting++; while (slot != 0) { indent(); - slot->data()->accept(*this); + slot->data()->resolve()->accept(*this); slot = slot->next(); base::write(slot ? ",\r\n" : "\r\n"); @@ -48,7 +48,7 @@ class PrettyJsonSerializer : public JsonSerializer { indent(); base::visitString(slot->key()); base::write(": "); - slot->data()->accept(*this); + slot->data()->resolve()->accept(*this); slot = slot->next(); base::write(slot ? ",\r\n" : "\r\n"); diff --git a/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp b/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp index a1053f0a..8cf99801 100644 --- a/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp +++ b/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp @@ -56,7 +56,7 @@ class MsgPackSerializer : public Visitor { writeInteger(uint32_t(n)); } for (VariantSlot* slot = array.head(); slot; slot = slot->next()) { - slot->data()->accept(*this); + slot->data()->resolve()->accept(*this); } return bytesWritten(); } @@ -74,7 +74,7 @@ class MsgPackSerializer : public Visitor { } for (VariantSlot* slot = object.head(); slot; slot = slot->next()) { visitString(slot->key()); - slot->data()->accept(*this); + slot->data()->resolve()->accept(*this); } return bytesWritten(); } diff --git a/src/ArduinoJson/Object/MemberProxy.hpp b/src/ArduinoJson/Object/MemberProxy.hpp index b0cbd9d2..c7f93e94 100644 --- a/src/ArduinoJson/Object/MemberProxy.hpp +++ b/src/ArduinoJson/Object/MemberProxy.hpp @@ -135,6 +135,10 @@ class MemberProxy : public VariantOperators >, getUpstreamMember().remove(key); } + FORCE_INLINE void link(VariantConstRef value) { + getOrAddUpstreamMember().link(value); + } + template FORCE_INLINE typename VariantTo::type to() { return getOrAddUpstreamMember().template to(); diff --git a/src/ArduinoJson/Object/ObjectRef.hpp b/src/ArduinoJson/Object/ObjectRef.hpp index 934452c1..3ef65c87 100644 --- a/src/ArduinoJson/Object/ObjectRef.hpp +++ b/src/ArduinoJson/Object/ObjectRef.hpp @@ -278,7 +278,7 @@ struct Converter { static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isObject(); + return data && data->resolve()->isObject(); } }; diff --git a/src/ArduinoJson/Variant/ConverterImpl.hpp b/src/ArduinoJson/Variant/ConverterImpl.hpp index e0222592..a510aa23 100644 --- a/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -48,12 +48,12 @@ struct Converter< static T fromJson(VariantConstRef src) { ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T); const VariantData* data = getData(src); - return data ? data->asIntegral() : T(); + return data ? data->resolve()->asIntegral() : T(); } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isInteger(); + return data && data->resolve()->isInteger(); } }; @@ -65,12 +65,12 @@ struct Converter::value>::type> { static T fromJson(VariantConstRef src) { const VariantData* data = getData(src); - return data ? static_cast(data->asIntegral()) : T(); + return data ? static_cast(data->resolve()->asIntegral()) : T(); } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isInteger(); + return data && data->resolve()->isInteger(); } }; @@ -84,12 +84,12 @@ struct Converter { static bool fromJson(VariantConstRef src) { const VariantData* data = getData(src); - return data ? data->asBoolean() : false; + return data ? data->resolve()->asBoolean() : false; } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isBoolean(); + return data && data->resolve()->isBoolean(); } }; @@ -103,12 +103,12 @@ struct Converter::value>::type> { static T fromJson(VariantConstRef src) { const VariantData* data = getData(src); - return data ? data->asFloat() : false; + return data ? data->resolve()->asFloat() : 0; } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isFloat(); + return data && data->resolve()->isFloat(); } }; @@ -121,12 +121,12 @@ struct Converter { static const char* fromJson(VariantConstRef src) { const VariantData* data = getData(src); - return data ? data->asString().c_str() : 0; + return data ? data->resolve()->asString().c_str() : 0; } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isString(); + return data && data->resolve()->isString(); } }; @@ -139,12 +139,12 @@ struct Converter { static String fromJson(VariantConstRef src) { const VariantData* data = getData(src); - return data ? data->asString() : 0; + return data ? data->resolve()->asString() : 0; } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data && data->isString(); + return data && data->resolve()->isString(); } }; @@ -192,7 +192,7 @@ struct Converter { } static bool checkJson(VariantConstRef src) { const VariantData* data = getData(src); - return data == 0 || data->isNull(); + return data == 0 || data->resolve()->isNull(); } }; diff --git a/src/ArduinoJson/Variant/VariantContent.hpp b/src/ArduinoJson/Variant/VariantContent.hpp index dbe833e5..3ede76ef 100644 --- a/src/ArduinoJson/Variant/VariantContent.hpp +++ b/src/ArduinoJson/Variant/VariantContent.hpp @@ -31,6 +31,8 @@ enum { VALUE_IS_SIGNED_INTEGER = 0x0A, VALUE_IS_FLOAT = 0x0C, + VALUE_IS_POINTER = 0x10, + COLLECTION_MASK = 0x60, VALUE_IS_OBJECT = 0x20, VALUE_IS_ARRAY = 0x40, @@ -49,6 +51,7 @@ union VariantContent { UInt asUnsignedInteger; Integer asSignedInteger; CollectionData asCollection; + const class VariantData *asPointer; struct { const char *data; size_t size; diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index 162eab0c..e99400fd 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -83,6 +83,12 @@ class VariantData { bool asBoolean() const; + const VariantData *resolve() const { + if (isPointer()) + return _content.asPointer->resolve(); + return this; + } + CollectionData *asArray() { return isArray() ? &_content.asCollection : 0; } @@ -117,6 +123,10 @@ class VariantData { return (_flags & COLLECTION_MASK) != 0; } + bool isPointer() const { + return type() == VALUE_IS_POINTER; + } + template bool isInteger() const { switch (type()) { @@ -212,6 +222,12 @@ class VariantData { setType(VALUE_IS_NULL); } + void setPointer(const VariantData *p) { + ARDUINOJSON_ASSERT(p); + setType(VALUE_IS_POINTER); + _content.asPointer = p; + } + void setString(String s) { ARDUINOJSON_ASSERT(s); if (s.isLinked()) @@ -262,7 +278,8 @@ class VariantData { } VariantData *getElement(size_t index) const { - return isArray() ? _content.asCollection.getElement(index) : 0; + const CollectionData *col = asArray(); + return col ? col->getElement(index) : 0; } VariantData *getOrAddElement(size_t index, MemoryPool *pool) { @@ -275,7 +292,8 @@ class VariantData { template VariantData *getMember(TAdaptedString key) const { - return isObject() ? _content.asCollection.getMember(key) : 0; + const CollectionData *col = asObject(); + return col ? col->getMember(key) : 0; } template diff --git a/src/ArduinoJson/Variant/VariantFunctions.hpp b/src/ArduinoJson/Variant/VariantFunctions.hpp index 51ffd64d..c6b64f8a 100644 --- a/src/ArduinoJson/Variant/VariantFunctions.hpp +++ b/src/ArduinoJson/Variant/VariantFunctions.hpp @@ -15,17 +15,17 @@ template inline typename TVisitor::result_type variantAccept(const VariantData *var, TVisitor &visitor) { if (var != 0) - return var->accept(visitor); + return var->resolve()->accept(visitor); else return visitor.visitNull(); } inline const CollectionData *variantAsArray(const VariantData *var) { - return var != 0 ? var->asArray() : 0; + return var != 0 ? var->resolve()->asArray() : 0; } inline const CollectionData *variantAsObject(const VariantData *var) { - return var != 0 ? var->asObject() : 0; + return var != 0 ? var->resolve()->asObject() : 0; } inline CollectionData *variantAsObject(VariantData *var) { @@ -56,7 +56,7 @@ inline bool variantSetString(VariantData *var, TAdaptedString value, } inline size_t variantSize(const VariantData *var) { - return var != 0 ? var->size() : 0; + return var != 0 ? var->resolve()->size() : 0; } inline CollectionData *variantToArray(VariantData *var) { @@ -102,14 +102,14 @@ NO_INLINE VariantData *variantGetOrAddMember(VariantData *var, } inline bool variantIsNull(const VariantData *var) { - return var == 0 || var->isNull(); + return var == 0 || var->resolve()->isNull(); } inline size_t variantNesting(const VariantData *var) { if (!var) return 0; - const CollectionData *collection = var->asCollection(); + const CollectionData *collection = var->resolve()->asCollection(); if (!collection) return 0; diff --git a/src/ArduinoJson/Variant/VariantRef.hpp b/src/ArduinoJson/Variant/VariantRef.hpp index a26e62a3..7a6c4522 100644 --- a/src/ArduinoJson/Variant/VariantRef.hpp +++ b/src/ArduinoJson/Variant/VariantRef.hpp @@ -124,7 +124,8 @@ class VariantConstRef : public VariantRefBase, } FORCE_INLINE VariantConstRef getElementConst(size_t index) const { - return VariantConstRef(_data != 0 ? _data->getElement(index) : 0); + return VariantConstRef(_data != 0 ? _data->resolve()->getElement(index) + : 0); } FORCE_INLINE VariantConstRef operator[](size_t index) const { @@ -135,8 +136,8 @@ class VariantConstRef : public VariantRefBase, // getMemberConst(const String&) const template FORCE_INLINE VariantConstRef getMemberConst(const TString &key) const { - return VariantConstRef( - objectGetMember(variantAsObject(_data), adaptString(key))); + return VariantConstRef(_data ? _data->resolve()->getMember(adaptString(key)) + : 0); } // getMemberConst(char*) const @@ -144,8 +145,8 @@ class VariantConstRef : public VariantRefBase, // getMemberConst(const __FlashStringHelper*) const template FORCE_INLINE VariantConstRef getMemberConst(TChar *key) const { - const CollectionData *obj = variantAsObject(_data); - return VariantConstRef(obj ? obj->getMember(adaptString(key)) : 0); + return VariantConstRef(_data ? _data->resolve()->getMember(adaptString(key)) + : 0); } // operator[](const std::string&) const @@ -293,7 +294,8 @@ class VariantRef : public VariantRefBase, } FORCE_INLINE VariantConstRef getElementConst(size_t index) const { - return VariantConstRef(_data != 0 ? _data->getElement(index) : 0); + return VariantConstRef(_data != 0 ? _data->resolve()->getElement(index) + : 0); } FORCE_INLINE VariantRef getOrAddElement(size_t index) const { @@ -321,7 +323,8 @@ class VariantRef : public VariantRefBase, // getMemberConst(const __FlashStringHelper*) const template FORCE_INLINE VariantConstRef getMemberConst(TChar *key) const { - return VariantConstRef(_data ? _data->getMember(adaptString(key)) : 0); + return VariantConstRef(_data ? _data->resolve()->getMember(adaptString(key)) + : 0); } // getMemberConst(const std::string&) const @@ -330,7 +333,8 @@ class VariantRef : public VariantRefBase, FORCE_INLINE typename enable_if::value, VariantConstRef>::type getMemberConst(const TString &key) const { - return VariantConstRef(_data ? _data->getMember(adaptString(key)) : 0); + return VariantConstRef(_data ? _data->resolve()->getMember(adaptString(key)) + : 0); } // getOrAddMember(char*) const @@ -370,6 +374,16 @@ class VariantRef : public VariantRefBase, _data->remove(adaptString(key)); } + inline void link(VariantConstRef target) { + if (!_data) + return; + const VariantData *targetData = getData(target); + if (targetData) + _data->setPointer(targetData); + else + _data->setNull(); + } + private: MemoryPool *_pool;