From f5cf7a8a20139ba9862b84c6d957c22c4d62b1ba Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 10 Mar 2022 13:50:01 +0100 Subject: [PATCH] Add `JsonVariant::link()` --- extras/tests/JsonDocument/ElementProxy.cpp | 8 ++ extras/tests/JsonDocument/MemberProxy.cpp | 40 ++++++++ extras/tests/JsonVariant/CMakeLists.txt | 4 +- extras/tests/JsonVariant/add.cpp | 10 ++ extras/tests/JsonVariant/as.cpp | 93 +++++++++++++++++ extras/tests/JsonVariant/clear.cpp | 11 +++ extras/tests/JsonVariant/compare.cpp | 29 ++++++ extras/tests/JsonVariant/containsKey.cpp | 13 ++- extras/tests/JsonVariant/copy.cpp | 10 ++ extras/tests/JsonVariant/createNested.cpp | 48 ++++++--- extras/tests/JsonVariant/is.cpp | 110 +++++++++++++++++++++ extras/tests/JsonVariant/isnull.cpp | 35 ++++--- extras/tests/JsonVariant/link.cpp | 77 +++++++++++++++ extras/tests/JsonVariant/memoryUsage.cpp | 8 ++ extras/tests/JsonVariant/nesting.cpp | 8 ++ extras/tests/JsonVariant/or.cpp | 8 ++ extras/tests/JsonVariant/remove.cpp | 20 ++++ extras/tests/JsonVariant/set.cpp | 25 ++--- extras/tests/JsonVariant/size.cpp | 53 ++++++++++ extras/tests/JsonVariant/subscript.cpp | 54 ++++++++++ src/ArduinoJson/Array/ArrayRef.hpp | 2 +- src/ArduinoJson/Array/ElementProxy.hpp | 4 + src/ArduinoJson/Object/MemberProxy.hpp | 4 + src/ArduinoJson/Object/ObjectRef.hpp | 2 +- src/ArduinoJson/Variant/VariantContent.hpp | 3 + src/ArduinoJson/Variant/VariantData.hpp | 59 +++++++++-- src/ArduinoJson/Variant/VariantImpl.hpp | 17 ++++ src/ArduinoJson/Variant/VariantRef.hpp | 2 + 28 files changed, 705 insertions(+), 52 deletions(-) create mode 100644 extras/tests/JsonVariant/link.cpp create mode 100644 extras/tests/JsonVariant/size.cpp 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 64cd06a0..a4de1257 100644 --- a/extras/tests/JsonDocument/MemberProxy.cpp +++ b/extras/tests/JsonDocument/MemberProxy.cpp @@ -285,3 +285,43 @@ TEST_CASE("MemberProxy cast to JsonVariant") { CHECK(doc.as() == "{\"hello\":\"toto\"}"); } + +TEST_CASE("MemberProxy::createNestedArray()") { + StaticJsonDocument<1024> doc; + JsonArray arr = doc["items"].createNestedArray(); + arr.add(42); + + CHECK(doc["items"][0][0] == 42); +} + +TEST_CASE("MemberProxy::createNestedArray(key)") { + StaticJsonDocument<1024> doc; + JsonArray arr = doc["weather"].createNestedArray("temp"); + arr.add(42); + + CHECK(doc["weather"]["temp"][0] == 42); +} + +TEST_CASE("MemberProxy::createNestedObject()") { + StaticJsonDocument<1024> doc; + JsonObject obj = doc["items"].createNestedObject(); + obj["value"] = 42; + + CHECK(doc["items"][0]["value"] == 42); +} + +TEST_CASE("MemberProxy::createNestedObject(key)") { + StaticJsonDocument<1024> doc; + JsonObject obj = doc["status"].createNestedObject("weather"); + obj["temp"] = 42; + + 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/JsonVariant/CMakeLists.txt b/extras/tests/JsonVariant/CMakeLists.txt index 134c6751..35cf3818 100644 --- a/extras/tests/JsonVariant/CMakeLists.txt +++ b/extras/tests/JsonVariant/CMakeLists.txt @@ -8,11 +8,12 @@ add_executable(JsonVariantTests clear.cpp compare.cpp containsKey.cpp - copy.cpp converters.cpp + copy.cpp createNested.cpp is.cpp isnull.cpp + link.cpp memoryUsage.cpp misc.cpp nesting.cpp @@ -20,6 +21,7 @@ add_executable(JsonVariantTests overflow.cpp remove.cpp set.cpp + size.cpp subscript.cpp types.cpp unbound.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 34c74c47..0636762e 100644 --- a/extras/tests/JsonVariant/createNested.cpp +++ b/extras/tests/JsonVariant/createNested.cpp @@ -19,11 +19,15 @@ TEST_CASE("JsonVariant::createNestedObject()") { REQUIRE(obj.isNull() == false); } - SECTION("works on MemberProxy") { - JsonObject obj = variant["items"].createNestedObject(); - obj["value"] = 42; + SECTION("does nothing on linked array") { + StaticJsonDocument<128> doc2; + doc2[0] = 42; + variant.link(doc2); - REQUIRE(variant["items"][0]["value"] == 42); + variant.createNestedObject(); + + CHECK(variant.size() == 1); + CHECK(variant[0] == 42); } } @@ -38,11 +42,15 @@ TEST_CASE("JsonVariant::createNestedArray()") { REQUIRE(arr.isNull() == false); } - SECTION("works on MemberProxy") { - JsonArray arr = variant["items"].createNestedArray(); - arr.add(42); + SECTION("does nothing on linked array") { + StaticJsonDocument<128> doc2; + doc2[0] = 42; + variant.link(doc2); - REQUIRE(variant["items"][0][0] == 42); + variant.createNestedArray(); + + CHECK(variant.size() == 1); + CHECK(variant[0] == 42); } } @@ -58,11 +66,15 @@ TEST_CASE("JsonVariant::createNestedObject(key)") { REQUIRE(variant["weather"]["temp"] == 42); } - SECTION("works on MemberProxy") { - JsonObject obj = variant["status"].createNestedObject("weather"); - obj["temp"] = 42; + SECTION("does nothing on linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + variant.link(doc2); - REQUIRE(variant["status"]["weather"]["temp"] == 42); + variant.createNestedObject("weather"); + + CHECK(variant.size() == 1); + CHECK(variant["hello"] == "world"); } } @@ -77,10 +89,14 @@ TEST_CASE("JsonVariant::createNestedArray(key)") { REQUIRE(arr.isNull() == false); } - SECTION("works on MemberProxy") { - JsonArray arr = variant["weather"].createNestedArray("temp"); - arr.add(42); + SECTION("does nothing on linked object") { + StaticJsonDocument<128> doc2; + doc2["hello"] = "world"; + variant.link(doc2); - REQUIRE(variant["weather"]["temp"][0] == 42); + 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..c1fd4255 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() == false); + 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() == false); + 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..29e7d1ff 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("return 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/set.cpp b/extras/tests/JsonVariant/set.cpp index 899ef6a5..9cd135ee 100644 --- a/extras/tests/JsonVariant/set.cpp +++ b/extras/tests/JsonVariant/set.cpp @@ -155,18 +155,21 @@ TEST_CASE("JsonVariant::set() with not enough memory") { } } -TEST_CASE("JsonVariant::set(DynamicJsonDocument)") { - DynamicJsonDocument doc1(1024); - doc1["hello"] = "world"; +TEST_CASE("Copy/link from other document") { + StaticJsonDocument<1024> doc1, doc2; + JsonVariant variant = doc1.to(); - DynamicJsonDocument doc2(1024); - JsonVariant v = doc2.to(); + SECTION("JsonVariant::set(JsonDocument&)") { + doc2["hello"] = "world"; - // Should copy the doc - v.set(doc1); - doc1.clear(); + variant.set(doc2); - std::string json; - serializeJson(doc2, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + CHECK(variant.as() == "{\"hello\":\"world\"}"); + CHECK(variant.memoryUsage() == JSON_OBJECT_SIZE(1)); + + // altering the copied document should change the result + doc2["hello"] = "WORLD!"; + + CHECK(variant.as() == "{\"hello\":\"world\"}"); + } } diff --git a/extras/tests/JsonVariant/size.cpp b/extras/tests/JsonVariant/size.cpp new file mode 100644 index 00000000..9db3c538 --- /dev/null +++ b/extras/tests/JsonVariant/size.cpp @@ -0,0 +1,53 @@ +// ArduinoJson - https://arduinojson.org +// Copyright © 2014-2022, Benoit BLANCHON +// MIT License + +#include +#include + +TEST_CASE("JsonVariant::size()") { + DynamicJsonDocument doc(4096); + JsonVariant variant = doc.to(); + + SECTION("unbound reference") { + JsonVariant unbound; + + CHECK(unbound.size() == 0); + } + + SECTION("int") { + variant.set(42); + + CHECK(variant.size() == 0); + } + + SECTION("string") { + variant.set("hello"); + + CHECK(variant.size() == 0); + } + + SECTION("object") { + variant["a"] = 1; + variant["b"] = 2; + + CHECK(variant.size() == 2); + } + + SECTION("linked object") { + StaticJsonDocument<1024> doc2; + doc2["hello"] = "world"; + variant.link(doc2); + + 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 a5df7abc..d8924d31 100644 --- a/src/ArduinoJson/Array/ArrayRef.hpp +++ b/src/ArduinoJson/Array/ArrayRef.hpp @@ -207,7 +207,7 @@ struct Converter { static bool checkJson(VariantRef src) { VariantData* data = getData(src); - return data && data->isArray(); + return data && data->isArrayStrict(); } }; } // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Array/ElementProxy.hpp b/src/ArduinoJson/Array/ElementProxy.hpp index 821cfba3..07566551 100644 --- a/src/ArduinoJson/Array/ElementProxy.hpp +++ b/src/ArduinoJson/Array/ElementProxy.hpp @@ -92,6 +92,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/Object/MemberProxy.hpp b/src/ArduinoJson/Object/MemberProxy.hpp index 9a1bc83f..8275c186 100644 --- a/src/ArduinoJson/Object/MemberProxy.hpp +++ b/src/ArduinoJson/Object/MemberProxy.hpp @@ -117,6 +117,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 6cf9c14d..8ea33dfe 100644 --- a/src/ArduinoJson/Object/ObjectRef.hpp +++ b/src/ArduinoJson/Object/ObjectRef.hpp @@ -276,7 +276,7 @@ struct Converter { static bool checkJson(VariantRef src) { VariantData* data = getData(src); - return data && data->isObject(); + return data && data->isObjectStrict(); } }; } // namespace ARDUINOJSON_NAMESPACE 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 27128706..34ee2dba 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -68,6 +68,10 @@ class VariantData { case VALUE_IS_BOOLEAN: return visitor.visitBoolean(_content.asBoolean != 0); + case VALUE_IS_POINTER: + ARDUINOJSON_ASSERT(_content.asPointer != 0); + return _content.asPointer->accept(visitor); + default: return visitor.visitNull(); } @@ -84,24 +88,35 @@ class VariantData { bool asBoolean() const; CollectionData *asArray() { - return isArray() ? &_content.asCollection : 0; + return isArrayStrict() ? &_content.asCollection : 0; } const CollectionData *asArray() const { + if (isPointer()) + return _content.asPointer->asArray(); return const_cast(this)->asArray(); } CollectionData *asObject() { - return isObject() ? &_content.asCollection : 0; + return isObjectStrict() ? &_content.asCollection : 0; } const CollectionData *asObject() const { + if (isPointer()) + return _content.asPointer->asObject(); return const_cast(this)->asObject(); } bool copyFrom(const VariantData &src, MemoryPool *pool); bool isArray() const { + if (isPointer()) + return _content.asPointer->isArray(); + else + return isArrayStrict(); + } + + bool isArrayStrict() const { return (_flags & VALUE_IS_ARRAY) != 0; } @@ -113,8 +128,14 @@ class VariantData { return (_flags & COLLECTION_MASK) != 0; } + bool isPointer() const { + return type() == VALUE_IS_POINTER; + } + template bool isInteger() const { + if (isPointer()) + return _content.asPointer->isInteger(); switch (type()) { case VALUE_IS_UNSIGNED_INTEGER: return canConvertNumber(_content.asUnsignedInteger); @@ -136,10 +157,18 @@ class VariantData { } bool isObject() const { + if (isPointer()) + return _content.asPointer->isObject(); + return isObjectStrict(); + } + + bool isObjectStrict() const { return (_flags & VALUE_IS_OBJECT) != 0; } bool isNull() const { + if (isPointer()) + return _content.asPointer->isNull(); return type() == VALUE_IS_NULL; } @@ -148,13 +177,13 @@ class VariantData { } void remove(size_t index) { - if (isArray()) + if (isArrayStrict()) _content.asCollection.removeElement(index); } template void remove(TAdaptedString key) { - if (isObject()) + if (isObjectStrict()) _content.asCollection.removeMember(key); } @@ -208,6 +237,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()) @@ -246,36 +281,42 @@ class VariantData { } size_t nesting() const { + if (isPointer()) + return _content.asPointer->nesting(); return isCollection() ? _content.asCollection.nesting() : 0; } size_t size() const { + if (isPointer()) + return _content.asPointer->size(); return isCollection() ? _content.asCollection.size() : 0; } VariantData *addElement(MemoryPool *pool) { if (isNull()) toArray(); - if (!isArray()) + if (!isArrayStrict()) return 0; return _content.asCollection.addElement(pool); } 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) { if (isNull()) toArray(); - if (!isArray()) + if (!isArrayStrict()) return 0; return _content.asCollection.getOrAddElement(index, pool); } template VariantData *getMember(TAdaptedString key) const { - return isObject() ? _content.asCollection.getMember(key) : 0; + const CollectionData *col = asObject(); + return col ? col->getMember(key) : 0; } template @@ -283,7 +324,7 @@ class VariantData { TStoragePolicy storage_policy) { if (isNull()) toObject(); - if (!isObject()) + if (!isObjectStrict()) return 0; return _content.asCollection.getOrAddMember(key, pool, storage_policy); } diff --git a/src/ArduinoJson/Variant/VariantImpl.hpp b/src/ArduinoJson/Variant/VariantImpl.hpp index 48c45fb8..6b4a3492 100644 --- a/src/ArduinoJson/Variant/VariantImpl.hpp +++ b/src/ArduinoJson/Variant/VariantImpl.hpp @@ -29,6 +29,8 @@ inline T VariantData::asIntegral() const { return parseNumber(_content.asString.data); case VALUE_IS_FLOAT: return convertNumber(_content.asFloat); + case VALUE_IS_POINTER: + return _content.asPointer->asIntegral(); default: return 0; } @@ -45,6 +47,8 @@ inline bool VariantData::asBoolean() const { return _content.asFloat != 0; case VALUE_IS_NULL: return false; + case VALUE_IS_POINTER: + return _content.asPointer->asBoolean(); default: return true; } @@ -65,6 +69,8 @@ inline T VariantData::asFloat() const { return parseNumber(_content.asString.data); case VALUE_IS_FLOAT: return static_cast(_content.asFloat); + case VALUE_IS_POINTER: + return _content.asPointer->asFloat(); default: return 0; } @@ -78,6 +84,8 @@ inline String VariantData::asString() const { case VALUE_IS_OWNED_STRING: return String(_content.asString.data, _content.asString.size, String::Copied); + case VALUE_IS_POINTER: + return _content.asPointer->asString(); default: return String(); } @@ -181,4 +189,13 @@ bool CopyStringStoragePolicy::store(TAdaptedString str, MemoryPool *pool, return copy != 0; } +inline void VariantRef::link(VariantConstRef target) { + if (!_data) + return; + if (target._data) + _data->setPointer(target._data); + else + _data->setNull(); +} + } // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Variant/VariantRef.hpp b/src/ArduinoJson/Variant/VariantRef.hpp index 31fd0219..cd78673a 100644 --- a/src/ArduinoJson/Variant/VariantRef.hpp +++ b/src/ArduinoJson/Variant/VariantRef.hpp @@ -198,6 +198,8 @@ class VariantRef : public VariantRefBase, template FORCE_INLINE VariantRef getOrAddMember(const TString &) const; + void link(class VariantConstRef var); + FORCE_INLINE void remove(size_t index) const { if (_data) _data->remove(index);