Browse Source

Add `JsonVariant::link()` (resolves #1343)

Benoit Blanchon 3 years ago
parent
commit
3d6c328a4f
35 changed files with 649 additions and 51 deletions
  1. 1 0
      CHANGELOG.md
  2. 8 0
      extras/tests/Cpp11/nullptr.cpp
  3. 8 0
      extras/tests/JsonDocument/ElementProxy.cpp
  4. 7 0
      extras/tests/JsonDocument/MemberProxy.cpp
  5. 17 0
      extras/tests/JsonDocument/size.cpp
  6. 1 0
      extras/tests/JsonVariant/CMakeLists.txt
  7. 10 0
      extras/tests/JsonVariant/add.cpp
  8. 93 0
      extras/tests/JsonVariant/as.cpp
  9. 11 0
      extras/tests/JsonVariant/clear.cpp
  10. 29 0
      extras/tests/JsonVariant/compare.cpp
  11. 11 2
      extras/tests/JsonVariant/containsKey.cpp
  12. 10 0
      extras/tests/JsonVariant/copy.cpp
  13. 44 0
      extras/tests/JsonVariant/createNested.cpp
  14. 110 0
      extras/tests/JsonVariant/is.cpp
  15. 24 11
      extras/tests/JsonVariant/isnull.cpp
  16. 77 0
      extras/tests/JsonVariant/link.cpp
  17. 8 0
      extras/tests/JsonVariant/memoryUsage.cpp
  18. 8 0
      extras/tests/JsonVariant/nesting.cpp
  19. 8 0
      extras/tests/JsonVariant/or.cpp
  20. 20 0
      extras/tests/JsonVariant/remove.cpp
  21. 9 0
      extras/tests/JsonVariant/size.cpp
  22. 54 0
      extras/tests/JsonVariant/subscript.cpp
  23. 1 1
      src/ArduinoJson/Array/ArrayRef.hpp
  24. 4 0
      src/ArduinoJson/Array/ElementProxy.hpp
  25. 1 1
      src/ArduinoJson/Document/JsonDocument.hpp
  26. 2 2
      src/ArduinoJson/Json/JsonSerializer.hpp
  27. 2 2
      src/ArduinoJson/Json/PrettyJsonSerializer.hpp
  28. 2 2
      src/ArduinoJson/MsgPack/MsgPackSerializer.hpp
  29. 4 0
      src/ArduinoJson/Object/MemberProxy.hpp
  30. 1 1
      src/ArduinoJson/Object/ObjectRef.hpp
  31. 13 13
      src/ArduinoJson/Variant/ConverterImpl.hpp
  32. 3 0
      src/ArduinoJson/Variant/VariantContent.hpp
  33. 20 2
      src/ArduinoJson/Variant/VariantData.hpp
  34. 6 6
      src/ArduinoJson/Variant/VariantFunctions.hpp
  35. 22 8
      src/ArduinoJson/Variant/VariantRef.hpp

+ 1 - 0
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)

+ 8 - 0
extras/tests/Cpp11/nullptr.cpp

@@ -43,5 +43,13 @@ TEST_CASE("nullptr") {
 
     variant.clear();
     REQUIRE(variant.is<std::nullptr_t>() == true);
+
+    StaticJsonDocument<128> doc2;
+    doc2["hello"] = "world";
+    variant.link(doc2);
+    REQUIRE(variant.is<std::nullptr_t>() == false);
+
+    doc2.clear();
+    REQUIRE(variant.is<std::nullptr_t>() == true);
   }
 }

+ 8 - 0
extras/tests/JsonDocument/ElementProxy.cpp

@@ -245,3 +245,11 @@ TEST_CASE("ElementProxy cast to JsonVariant") {
 
   CHECK(doc.as<std::string>() == "[\"toto\"]");
 }
+
+TEST_CASE("ElementProxy::link()") {
+  StaticJsonDocument<1024> doc1, doc2;
+  doc1[0].link(doc2);
+  doc2["hello"] = "world";
+
+  CHECK(doc1.as<std::string>() == "[{\"hello\":\"world\"}]");
+}

+ 7 - 0
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<std::string>() == "{\"obj\":{\"hello\":\"world\"}}");
+}

+ 17 - 0
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<JsonVariant>().link(doc2);
+
+    REQUIRE(doc.size() == 2);
+  }
+
+  SECTION("linked object") {
+    StaticJsonDocument<128> doc2;
+    doc2["hello"] = "world";
+    doc.as<JsonVariant>().link(doc2);
+
+    REQUIRE(doc.size() == 1);
+  }
 }

+ 1 - 0
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

+ 10 - 0
extras/tests/JsonVariant/add.cpp

@@ -43,4 +43,14 @@ TEST_CASE("JsonVariant::add()") {
 
     REQUIRE(var.as<std::string>() == "{\"val\":123}");
   }
+
+  SECTION("add to linked array") {
+    StaticJsonDocument<1024> doc2;
+    doc2.add(42);
+    var.link(doc2);
+
+    var.add(666);  // no-op
+
+    CHECK(var.as<std::string>() == "[42]");
+  }
 }

+ 93 - 0
extras/tests/JsonVariant/as.cpp

@@ -267,4 +267,97 @@ TEST_CASE("JsonVariant::as()") {
 
     REQUIRE(variant.as<MY_ENUM>() == ONE);
   }
+
+  SECTION("linked object") {
+    StaticJsonDocument<128> doc2;
+    doc2["hello"] = "world";
+    variant.link(doc2);
+
+    SECTION("as<std::string>()") {
+      CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
+    }
+
+    SECTION("as<JsonArray>()") {
+      JsonArray a = variant.as<JsonArray>();
+      CHECK(a.isNull() == true);
+    }
+
+    SECTION("as<JsonObject>()") {
+      JsonObject o = variant.as<JsonObject>();
+      CHECK(o.isNull() == true);
+    }
+
+    SECTION("as<JsonObjectConst>()") {
+      JsonObjectConst o = variant.as<JsonObjectConst>();
+      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<std::string>()") {
+      CHECK(variant.as<std::string>() == "[\"hello\",\"world\"]");
+    }
+
+    SECTION("as<JsonArray>()") {
+      JsonArray a = variant.as<JsonArray>();
+      CHECK(a.isNull() == true);
+    }
+
+    SECTION("as<JsonArrayConst>()") {
+      JsonArrayConst a = variant.as<JsonArrayConst>();
+      CHECK(a.isNull() == false);
+      CHECK(a.size() == 2);
+      CHECK(a[0] == "hello");
+      CHECK(a[1] == "world");
+    }
+
+    SECTION("as<JsonObject>()") {
+      JsonObject o = variant.as<JsonObject>();
+      CHECK(o.isNull() == true);
+    }
+  }
+
+  SECTION("linked int") {
+    StaticJsonDocument<128> doc2;
+    doc2.set(42);
+    variant.link(doc2);
+
+    CHECK(variant.as<int>() == 42);
+    CHECK(variant.as<double>() == 42.0);
+  }
+
+  SECTION("linked double") {
+    StaticJsonDocument<128> doc2;
+    doc2.set(42.0);
+    variant.link(doc2);
+
+    CHECK(variant.as<int>() == 42);
+    CHECK(variant.as<double>() == 42.0);
+  }
+
+  SECTION("linked string") {
+    StaticJsonDocument<128> doc2;
+    doc2.set("hello");
+    variant.link(doc2);
+
+    CHECK(variant.as<std::string>() == "hello");
+  }
+
+  SECTION("linked bool") {
+    StaticJsonDocument<128> doc2;
+    variant.link(doc2);
+
+    doc2.set(true);
+    CHECK(variant.as<bool>() == true);
+
+    doc2.set(false);
+    CHECK(variant.as<bool>() == false);
+  }
 }

+ 11 - 0
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<std::string>() == "{\"hello\":\"world\"}");
+  }
 }

+ 29 - 0
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);
+  }
 }

+ 11 - 2
extras/tests/JsonVariant/containsKey.cpp

@@ -12,19 +12,28 @@ TEST_CASE("JsonVariant::containsKey()") {
   DynamicJsonDocument doc(4096);
   JsonVariant var = doc.to<JsonVariant>();
 
-  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()") {

+ 10 - 0
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;
 

+ 44 - 0
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<JsonArray>() == 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<JsonObject>() == 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<JsonObject>() == 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");
+  }
 }

+ 110 - 0
extras/tests/JsonVariant/is.cpp

@@ -144,6 +144,24 @@ TEST_CASE("JsonVariant::is<T>()") {
     CHECK(variant.is<MYENUM2>() == false);
   }
 
+  SECTION("linked array") {
+    StaticJsonDocument<1024> doc2;
+    doc2[0] = "world";
+    variant.link(doc2);
+
+    CHECK(variant.is<JsonArray>() == false);
+    CHECK(variant.is<JsonArrayConst>() == true);
+    CHECK(variant.is<JsonVariant>() == true);
+    CHECK(variant.is<JsonVariantConst>() == true);
+    CHECK(variant.is<JsonObject>() == false);
+    CHECK(variant.is<JsonObjectConst>() == false);
+    CHECK(variant.is<int>() == false);
+    CHECK(variant.is<float>() == false);
+    CHECK(variant.is<bool>() == false);
+    CHECK(variant.is<const char *>() == false);
+    CHECK(variant.is<MYENUM2>() == false);
+  }
+
   SECTION("JsonObject") {
     variant.to<JsonObject>();
 
@@ -161,6 +179,44 @@ TEST_CASE("JsonVariant::is<T>()") {
     CHECK(variant.is<JsonVariant>() == true);
     CHECK(variant.is<JsonVariantConst>() == true);
   }
+
+  SECTION("linked object") {
+    StaticJsonDocument<1024> doc2;
+    doc2["hello"] = "world";
+    variant.link(doc2);
+
+    CHECK(variant.is<JsonObject>() == false);
+    CHECK(variant.is<JsonObjectConst>() == true);
+    CHECK(variant.is<JsonVariant>() == true);
+    CHECK(variant.is<JsonVariantConst>() == true);
+    CHECK(variant.is<JsonArray>() == false);
+    CHECK(variant.is<JsonArrayConst>() == false);
+    CHECK(variant.is<int>() == false);
+    CHECK(variant.is<float>() == false);
+    CHECK(variant.is<bool>() == false);
+    CHECK(variant.is<const char *>() == false);
+    CHECK(variant.is<MYENUM2>() == false);
+    CHECK(variant.is<JsonVariant>() == true);
+    CHECK(variant.is<JsonVariantConst>() == true);
+  }
+
+  SECTION("linked int") {
+    StaticJsonDocument<1024> doc2;
+    doc2.set(42);
+    variant.link(doc2);
+
+    CHECK(variant.is<JsonObjectConst>() == false);
+    CHECK(variant.is<JsonVariantConst>() == true);
+    CHECK(variant.is<JsonObject>() == false);
+    CHECK(variant.is<JsonVariant>() == true);
+    CHECK(variant.is<JsonArray>() == false);
+    CHECK(variant.is<JsonArrayConst>() == false);
+    CHECK(variant.is<int>() == true);
+    CHECK(variant.is<float>() == true);
+    CHECK(variant.is<bool>() == false);
+    CHECK(variant.is<const char *>() == false);
+    CHECK(variant.is<MYENUM2>() == true);
+  }
 }
 
 TEST_CASE("JsonVariantConst::is<T>()") {
@@ -316,4 +372,58 @@ TEST_CASE("JsonVariantConst::is<T>()") {
     CHECK(cvariant.is<const char *>() == false);
     CHECK(cvariant.is<MYENUM2>() == false);
   }
+
+  SECTION("linked array") {
+    StaticJsonDocument<1024> doc2;
+    doc2[0] = "world";
+    variant.link(doc2);
+
+    CHECK(cvariant.is<JsonArrayConst>() == true);
+    CHECK(cvariant.is<JsonVariantConst>() == true);
+    CHECK(cvariant.is<JsonArray>() == false);
+    CHECK(cvariant.is<JsonVariant>() == false);
+    CHECK(cvariant.is<JsonObject>() == false);
+    CHECK(cvariant.is<JsonObjectConst>() == false);
+    CHECK(cvariant.is<int>() == false);
+    CHECK(cvariant.is<float>() == false);
+    CHECK(cvariant.is<bool>() == false);
+    CHECK(cvariant.is<const char *>() == false);
+    CHECK(cvariant.is<MYENUM2>() == false);
+  }
+
+  SECTION("linked object") {
+    StaticJsonDocument<1024> doc2;
+    doc2["hello"] = "world";
+    variant.link(doc2);
+
+    CHECK(cvariant.is<JsonObjectConst>() == true);
+    CHECK(cvariant.is<JsonVariantConst>() == true);
+    CHECK(cvariant.is<JsonObject>() == false);
+    CHECK(cvariant.is<JsonVariant>() == false);
+    CHECK(cvariant.is<JsonArray>() == false);
+    CHECK(cvariant.is<JsonArrayConst>() == false);
+    CHECK(cvariant.is<int>() == false);
+    CHECK(cvariant.is<float>() == false);
+    CHECK(cvariant.is<bool>() == false);
+    CHECK(cvariant.is<const char *>() == false);
+    CHECK(cvariant.is<MYENUM2>() == false);
+  }
+
+  SECTION("linked int") {
+    StaticJsonDocument<1024> doc2;
+    doc2.set(42);
+    variant.link(doc2);
+
+    CHECK(cvariant.is<JsonObjectConst>() == false);
+    CHECK(cvariant.is<JsonVariantConst>() == true);
+    CHECK(cvariant.is<JsonObject>() == false);
+    CHECK(cvariant.is<JsonVariant>() == false);
+    CHECK(cvariant.is<JsonArray>() == false);
+    CHECK(cvariant.is<JsonArrayConst>() == false);
+    CHECK(cvariant.is<int>() == true);
+    CHECK(cvariant.is<float>() == true);
+    CHECK(cvariant.is<bool>() == false);
+    CHECK(cvariant.is<const char *>() == false);
+    CHECK(cvariant.is<MYENUM2>() == true);
+  }
 }

+ 24 - 11
extras/tests/JsonVariant/isnull.cpp

@@ -9,17 +9,17 @@ TEST_CASE("JsonVariant::isNull()") {
   DynamicJsonDocument doc(4096);
   JsonVariant variant = doc.to<JsonVariant>();
 
-  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<JsonArray>();
 
@@ -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<JsonObject>();
 
@@ -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<char*>(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<const char*>(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<char*>(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<const char*>(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);
 

+ 77 - 0
extras/tests/JsonVariant/link.cpp

@@ -0,0 +1,77 @@
+// ArduinoJson - https://arduinojson.org
+// Copyright © 2014-2022, Benoit BLANCHON
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonVariant::link()") {
+  StaticJsonDocument<1024> doc1, doc2;
+  JsonVariant variant = doc1.to<JsonVariant>();
+
+  SECTION("JsonVariant::link(JsonDocument&)") {
+    doc2["hello"] = "world";
+
+    variant.link(doc2);
+
+    CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
+    CHECK(variant.memoryUsage() == 0);
+
+    // altering the linked document should change the result
+    doc2["hello"] = "WORLD!";
+
+    CHECK(variant.as<std::string>() == "{\"hello\":\"WORLD!\"}");
+  }
+
+  SECTION("JsonVariant::link(MemberProxy)") {
+    doc2["obj"]["hello"] = "world";
+
+    variant.link(doc2["obj"]);
+
+    CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
+    CHECK(variant.memoryUsage() == 0);
+
+    // altering the linked document should change the result
+    doc2["obj"]["hello"] = "WORLD!";
+
+    CHECK(variant.as<std::string>() == "{\"hello\":\"WORLD!\"}");
+  }
+
+  SECTION("JsonVariant::link(ElementProxy)") {
+    doc2[0]["hello"] = "world";
+
+    variant.link(doc2[0]);
+
+    CHECK(variant.as<std::string>() == "{\"hello\":\"world\"}");
+    CHECK(variant.memoryUsage() == 0);
+
+    // altering the linked document should change the result
+    doc2[0]["hello"] = "WORLD!";
+
+    CHECK(variant.as<std::string>() == "{\"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);
+  }
+}

+ 8 - 0
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());
+  }
 }

+ 8 - 0
extras/tests/JsonVariant/nesting.cpp

@@ -28,4 +28,12 @@ TEST_CASE("JsonVariant::nesting()") {
     var.to<JsonArray>();
     REQUIRE(var.nesting() == 1);
   }
+
+  SECTION("returns depth of linked array") {
+    StaticJsonDocument<128> doc2;
+    doc2[0][0] = 42;
+    var.link(doc2);
+
+    CHECK(var.nesting() == 2);
+  }
 }

+ 8 - 0
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);
+  }
 }

+ 20 - 0
extras/tests/JsonVariant/remove.cpp

@@ -39,4 +39,24 @@ TEST_CASE("JsonVariant::remove()") {
 
     REQUIRE(var.as<std::string>() == "{\"a\":1}");
   }
+
+  SECTION("linked array") {
+    StaticJsonDocument<128> doc2;
+    doc2[0] = 42;
+    var.link(doc2);
+
+    var.remove(0);
+
+    CHECK(var.as<std::string>() == "[42]");
+  }
+
+  SECTION("linked object") {
+    StaticJsonDocument<128> doc2;
+    doc2["hello"] = "world";
+    var.link(doc2);
+
+    var.remove("hello");
+
+    CHECK(var.as<std::string>() == "{\"hello\":\"world\"}");
+  }
 }

+ 9 - 0
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);
+  }
 }

+ 54 - 0
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<std::string>() == "world");
+  }
+
+  SECTION("set value to linked object") {
+    StaticJsonDocument<1024> doc2;
+    doc2["hello"] = "world";
+    var.link(doc2);
+
+    var["tutu"] = "toto";  // no-op
+
+    CHECK(doc.as<std::string>() == "{\"hello\":\"world\"}");
+    CHECK(doc2.as<std::string>() == "{\"hello\":\"world\"}");
+  }
+
+  SECTION("get value from linked array") {
+    StaticJsonDocument<1024> doc2;
+    doc2.add(42);
+    var.link(doc2);
+
+    CHECK(var[0].as<int>() == 42);
+  }
+
+  SECTION("set value to linked array") {
+    StaticJsonDocument<1024> doc2;
+    doc2.add(42);
+    var.link(doc2);
+
+    var[0] = 666;  // no-op
+
+    CHECK(doc.as<std::string>() == "[42]");
+    CHECK(doc2.as<std::string>() == "[42]");
+  }
 }
 
 TEST_CASE("JsonVariantConst::operator[]") {
@@ -201,4 +239,20 @@ TEST_CASE("JsonVariantConst::operator[]") {
     REQUIRE(var.is<JsonObject>() == false);
     REQUIRE(value == 0);
   }
+
+  SECTION("get value from linked object") {
+    StaticJsonDocument<1024> doc2;
+    doc2["hello"] = "world";
+    var.link(doc2);
+
+    CHECK(cvar["hello"].as<std::string>() == "world");
+  }
+
+  SECTION("get value from linked array") {
+    StaticJsonDocument<1024> doc2;
+    doc2.add(42);
+    var.link(doc2);
+
+    CHECK(cvar[0].as<int>() == 42);
+  }
 }

+ 1 - 1
src/ArduinoJson/Array/ArrayRef.hpp

@@ -211,7 +211,7 @@ struct Converter<ArrayConstRef> {
 
   static bool checkJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data && data->isArray();
+    return data && data->resolve()->isArray();
   }
 };
 

+ 4 - 0
src/ArduinoJson/Array/ElementProxy.hpp

@@ -109,6 +109,10 @@ class ElementProxy : public VariantOperators<ElementProxy<TArray> >,
     return getOrAddUpstreamElement().template to<T>();
   }
 
+  FORCE_INLINE void link(VariantConstRef value) const {
+    getOrAddUpstreamElement().link(value);
+  }
+
   // Replaces the value
   //
   // bool set(const TValue&)

+ 1 - 1
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) {

+ 2 - 2
src/ArduinoJson/Json/JsonSerializer.hpp

@@ -25,7 +25,7 @@ class JsonSerializer : public Visitor<size_t> {
     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<size_t> {
     while (slot != 0) {
       _formatter.writeString(slot->key());
       write(':');
-      slot->data()->accept(*this);
+      slot->data()->resolve()->accept(*this);
 
       slot = slot->next();
       if (slot == 0)

+ 2 - 2
src/ArduinoJson/Json/PrettyJsonSerializer.hpp

@@ -25,7 +25,7 @@ class PrettyJsonSerializer : public JsonSerializer<TWriter> {
       _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<TWriter> {
         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");

+ 2 - 2
src/ArduinoJson/MsgPack/MsgPackSerializer.hpp

@@ -56,7 +56,7 @@ class MsgPackSerializer : public Visitor<size_t> {
       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<size_t> {
     }
     for (VariantSlot* slot = object.head(); slot; slot = slot->next()) {
       visitString(slot->key());
-      slot->data()->accept(*this);
+      slot->data()->resolve()->accept(*this);
     }
     return bytesWritten();
   }

+ 4 - 0
src/ArduinoJson/Object/MemberProxy.hpp

@@ -135,6 +135,10 @@ class MemberProxy : public VariantOperators<MemberProxy<TObject, TStringRef> >,
     getUpstreamMember().remove(key);
   }
 
+  FORCE_INLINE void link(VariantConstRef value) {
+    getOrAddUpstreamMember().link(value);
+  }
+
   template <typename TValue>
   FORCE_INLINE typename VariantTo<TValue>::type to() {
     return getOrAddUpstreamMember().template to<TValue>();

+ 1 - 1
src/ArduinoJson/Object/ObjectRef.hpp

@@ -278,7 +278,7 @@ struct Converter<ObjectConstRef> {
 
   static bool checkJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data && data->isObject();
+    return data && data->resolve()->isObject();
   }
 };
 

+ 13 - 13
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>() : T();
+    return data ? data->resolve()->asIntegral<T>() : T();
   }
 
   static bool checkJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data && data->isInteger<T>();
+    return data && data->resolve()->isInteger<T>();
   }
 };
 
@@ -65,12 +65,12 @@ struct Converter<T, typename enable_if<is_enum<T>::value>::type> {
 
   static T fromJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data ? static_cast<T>(data->asIntegral<int>()) : T();
+    return data ? static_cast<T>(data->resolve()->asIntegral<int>()) : T();
   }
 
   static bool checkJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data && data->isInteger<int>();
+    return data && data->resolve()->isInteger<int>();
   }
 };
 
@@ -84,12 +84,12 @@ struct Converter<bool> {
 
   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<T, typename enable_if<is_floating_point<T>::value>::type> {
 
   static T fromJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data ? data->asFloat<T>() : false;
+    return data ? data->resolve()->asFloat<T>() : 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<const char*> {
 
   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<String> {
 
   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<decltype(nullptr)> {
   }
   static bool checkJson(VariantConstRef src) {
     const VariantData* data = getData(src);
-    return data == 0 || data->isNull();
+    return data == 0 || data->resolve()->isNull();
   }
 };
 

+ 3 - 0
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;

+ 20 - 2
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 <typename T>
   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 <typename TAdaptedString>
   VariantData *getMember(TAdaptedString key) const {
-    return isObject() ? _content.asCollection.getMember(key) : 0;
+    const CollectionData *col = asObject();
+    return col ? col->getMember(key) : 0;
   }
 
   template <typename TAdaptedString, typename TStoragePolicy>

+ 6 - 6
src/ArduinoJson/Variant/VariantFunctions.hpp

@@ -15,17 +15,17 @@ template <typename TVisitor>
 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;
 

+ 22 - 8
src/ArduinoJson/Variant/VariantRef.hpp

@@ -124,7 +124,8 @@ class VariantConstRef : public VariantRefBase<const VariantData>,
   }
 
   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<const VariantData>,
   // getMemberConst(const String&) const
   template <typename TString>
   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<const VariantData>,
   // getMemberConst(const __FlashStringHelper*) const
   template <typename TChar>
   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<VariantData>,
   }
 
   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<VariantData>,
   // getMemberConst(const __FlashStringHelper*) const
   template <typename TChar>
   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<VariantData>,
   FORCE_INLINE
       typename enable_if<IsString<TString>::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<VariantData>,
       _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;