From 4167b114341661f21b84e8af75cd51ccf7033122 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Wed, 23 Jan 2019 10:47:20 +0100 Subject: [PATCH] Create or assign a JsonDocument from a JsonArray/JsonObject/JsonVariant --- CHANGELOG.md | 3 + .../Document/DynamicJsonDocument.hpp | 29 ++- src/ArduinoJson/Document/JsonDocument.hpp | 11 + .../Document/StaticJsonDocument.hpp | 26 ++- src/ArduinoJson/Variant/VariantAs.hpp | 8 + src/ArduinoJson/Variant/VariantAsImpl.hpp | 13 ++ src/ArduinoJson/Variant/VariantData.hpp | 10 +- src/ArduinoJson/Variant/VariantFunctions.hpp | 4 + test/JsonDocument/DynamicJsonDocument.cpp | 128 ++++++++--- test/JsonDocument/StaticJsonDocument.cpp | 200 ++++++++++++++---- 10 files changed, 334 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254e9b87..e259b28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ HEAD * Replaced `JsonDocument::nestingLimit` with an additional parameter to `deserializeJson()` and `deserializeMsgPack()` * Fixed uninitialized variant in `JsonDocument` +* Fixed `StaticJsonDocument` copy constructor and copy assignment +* The copy constructor of `DynamicJsonDocument` chooses the capacity according to the memory usage of the source, not from the capacity of the source. +* Added the ability to create/assign a `StaticJsonDocument`/`DynamicJsonDocument` from a `JsonArray`/`JsonObject`/`JsonVariant` * Added `JsonDocument::isNull()` > ### BREAKING CHANGES diff --git a/src/ArduinoJson/Document/DynamicJsonDocument.hpp b/src/ArduinoJson/Document/DynamicJsonDocument.hpp index 06cc4cd1..19b8c976 100644 --- a/src/ArduinoJson/Document/DynamicJsonDocument.hpp +++ b/src/ArduinoJson/Document/DynamicJsonDocument.hpp @@ -13,16 +13,24 @@ namespace ARDUINOJSON_NAMESPACE { class DynamicJsonDocument : public JsonDocument { public: explicit DynamicJsonDocument(size_t capa) - : JsonDocument(allocPool(addPadding(capa))) {} + : JsonDocument(allocPool(capa)) {} DynamicJsonDocument(const DynamicJsonDocument& src) - : JsonDocument(allocPool(src.capacity())) { - copy(src); + : JsonDocument(allocPool(src.memoryUsage())) { + set(src); } - DynamicJsonDocument(const JsonDocument& src) - : JsonDocument(allocPool(src.capacity())) { - copy(src); + template + DynamicJsonDocument(const T& src, + typename enable_if::value>::type* = 0) + : JsonDocument(allocPool(src.memoryUsage())) { + set(src); + } + + // disambiguate + DynamicJsonDocument(VariantRef src) + : JsonDocument(allocPool(src.memoryUsage())) { + set(src); } ~DynamicJsonDocument() { @@ -31,19 +39,20 @@ class DynamicJsonDocument : public JsonDocument { DynamicJsonDocument& operator=(const DynamicJsonDocument& src) { reallocPoolIfTooSmall(src.memoryUsage()); - copy(src); + set(src); return *this; } template - DynamicJsonDocument& operator=(const JsonDocument& src) { + DynamicJsonDocument& operator=(const T& src) { reallocPoolIfTooSmall(src.memoryUsage()); - copy(src); + set(src); return *this; } private: - MemoryPool allocPool(size_t capa) { + MemoryPool allocPool(size_t requiredSize) { + size_t capa = addPadding(requiredSize); return MemoryPool(reinterpret_cast(malloc(capa)), capa); } diff --git a/src/ArduinoJson/Document/JsonDocument.hpp b/src/ArduinoJson/Document/JsonDocument.hpp index e13493f5..a1ed8138 100644 --- a/src/ArduinoJson/Document/JsonDocument.hpp +++ b/src/ArduinoJson/Document/JsonDocument.hpp @@ -5,6 +5,7 @@ #pragma once #include "../Memory/MemoryPool.hpp" +#include "../Object/ObjectRef.hpp" #include "../Variant/VariantRef.hpp" #include "../Variant/VariantTo.hpp" @@ -53,6 +54,16 @@ class JsonDocument : public Visitable { return _pool.capacity(); } + bool set(const JsonDocument& src) { + return to().set(src.as()); + } + + template + typename enable_if::value, bool>::type set( + const T& src) { + return to().set(src); + } + template typename VariantTo::type to() { clear(); diff --git a/src/ArduinoJson/Document/StaticJsonDocument.hpp b/src/ArduinoJson/Document/StaticJsonDocument.hpp index 2f1fb5f4..a7a58296 100644 --- a/src/ArduinoJson/Document/StaticJsonDocument.hpp +++ b/src/ArduinoJson/Document/StaticJsonDocument.hpp @@ -16,13 +16,31 @@ class StaticJsonDocument : public JsonDocument { public: StaticJsonDocument() : JsonDocument(_buffer, ACTUAL_CAPACITY) {} - StaticJsonDocument(const JsonDocument& src) + StaticJsonDocument(const StaticJsonDocument& src) : JsonDocument(_buffer, ACTUAL_CAPACITY) { - copy(src); + set(src); } - StaticJsonDocument operator=(const JsonDocument& src) { - copy(src); + template + StaticJsonDocument(const T& src, + typename enable_if::value>::type* = 0) + : JsonDocument(_buffer, ACTUAL_CAPACITY) { + set(src); + } + + // disambiguate + StaticJsonDocument(VariantRef src) : JsonDocument(_buffer, ACTUAL_CAPACITY) { + set(src); + } + + StaticJsonDocument operator=(const StaticJsonDocument& src) { + set(src); + return *this; + } + + template + StaticJsonDocument operator=(const T& src) { + set(src); return *this; } diff --git a/src/ArduinoJson/Variant/VariantAs.hpp b/src/ArduinoJson/Variant/VariantAs.hpp index 2212e714..0e655ec0 100644 --- a/src/ArduinoJson/Variant/VariantAs.hpp +++ b/src/ArduinoJson/Variant/VariantAs.hpp @@ -77,6 +77,14 @@ variantAs(const VariantData* _data) { return _data != 0 ? _data->asString() : 0; } +template +inline typename enable_if::value, T>::type variantAs( + const VariantData* _data); + +template +inline typename enable_if::value, T>::type variantAs( + const VariantData* _data); + template inline typename enable_if::value, T>::type variantAs(const VariantData* _data); diff --git a/src/ArduinoJson/Variant/VariantAsImpl.hpp b/src/ArduinoJson/Variant/VariantAsImpl.hpp index 8836a9fd..6303d351 100644 --- a/src/ArduinoJson/Variant/VariantAsImpl.hpp +++ b/src/ArduinoJson/Variant/VariantAsImpl.hpp @@ -5,10 +5,23 @@ #pragma once #include "../Serialization/DynamicStringWriter.hpp" +#include "VariantFunctions.hpp" #include "VariantRef.hpp" namespace ARDUINOJSON_NAMESPACE { +template +inline typename enable_if::value, T>::type variantAs( + const VariantData* _data) { + return ArrayConstRef(variantAsArray(_data)); +} + +template +inline typename enable_if::value, T>::type variantAs( + const VariantData* _data) { + return ObjectConstRef(variantAsObject(_data)); +} + template inline typename enable_if::value, T>::type variantAs(const VariantData* _data) { diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index 60a2cebb..c99309fd 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -67,10 +67,7 @@ class VariantData { } CollectionData *asArray() { - if (type() == VALUE_IS_ARRAY) - return &_content.asCollection; - else - return 0; + return type() == VALUE_IS_ARRAY ? &_content.asCollection : 0; } const CollectionData *asArray() const { @@ -78,10 +75,7 @@ class VariantData { } CollectionData *asObject() { - if (type() == VALUE_IS_OBJECT) - return &_content.asCollection; - else - return 0; + return type() == VALUE_IS_OBJECT ? &_content.asCollection : 0; } const CollectionData *asObject() const { diff --git a/src/ArduinoJson/Variant/VariantFunctions.hpp b/src/ArduinoJson/Variant/VariantFunctions.hpp index 3c965b66..d3d0c976 100644 --- a/src/ArduinoJson/Variant/VariantFunctions.hpp +++ b/src/ArduinoJson/Variant/VariantFunctions.hpp @@ -16,6 +16,10 @@ inline void variantAccept(const VariantData *var, Visitor &visitor) { visitor.visitNull(); } +inline const CollectionData *variantAsArray(const VariantData *var) { + return var != 0 ? var->asArray() : 0; +} + inline const CollectionData *variantAsObject(const VariantData *var) { return var != 0 ? var->asObject() : 0; } diff --git a/test/JsonDocument/DynamicJsonDocument.cpp b/test/JsonDocument/DynamicJsonDocument.cpp index 1d6da0fd..85c16474 100644 --- a/test/JsonDocument/DynamicJsonDocument.cpp +++ b/test/JsonDocument/DynamicJsonDocument.cpp @@ -5,6 +5,14 @@ #include #include +using ARDUINOJSON_NAMESPACE::addPadding; + +static void REQUIRE_JSON(JsonDocument& doc, const std::string& expected) { + std::string json; + serializeJson(doc, json); + REQUIRE(json == expected); +} + TEST_CASE("DynamicJsonDocument") { DynamicJsonDocument doc(4096); @@ -74,20 +82,62 @@ TEST_CASE("DynamicJsonDocument") { } } -TEST_CASE("DynamicJsonDocument copies") { +TEST_CASE("DynamicJsonDocument constructor") { SECTION("Copy constructor") { DynamicJsonDocument doc1(1234); deserializeJson(doc1, "{\"hello\":\"world\"}"); DynamicJsonDocument doc2 = doc1; - std::string json; - serializeJson(doc2, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); - REQUIRE(doc2.capacity() == doc1.capacity()); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); } + SECTION("Construct from StaticJsonDocument") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + DynamicJsonDocument doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } + + SECTION("Construct from JsonObject") { + StaticJsonDocument<200> doc1; + JsonObject obj = doc1.to(); + obj["hello"] = "world"; + + DynamicJsonDocument doc2 = obj; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } + + SECTION("Construct from JsonArray") { + StaticJsonDocument<200> doc1; + JsonArray arr = doc1.to(); + arr.add("hello"); + + DynamicJsonDocument doc2 = arr; + + REQUIRE_JSON(doc2, "[\"hello\"]"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } + + SECTION("Construct from JsonVariant") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "42"); + + DynamicJsonDocument doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + REQUIRE(doc2.capacity() == addPadding(doc1.memoryUsage())); + } +} + +TEST_CASE("DynamicJsonDocument assignment") { SECTION("Copy assignment preserves the buffer when capacity is sufficient") { DynamicJsonDocument doc1(1234); deserializeJson(doc1, "{\"hello\":\"world\"}"); @@ -95,9 +145,7 @@ TEST_CASE("DynamicJsonDocument copies") { DynamicJsonDocument doc2(doc1.capacity()); doc2 = doc1; - std::string json; - serializeJson(doc2, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); REQUIRE(doc2.capacity() == doc1.capacity()); } @@ -110,34 +158,52 @@ TEST_CASE("DynamicJsonDocument copies") { doc2 = doc1; REQUIRE(doc2.capacity() >= doc1.memoryUsage()); - std::string json; - serializeJson(doc2, json); - REQUIRE(json == "{\"hello\":\"world\"}"); - } - - SECTION("Construct from StaticJsonDocument") { - StaticJsonDocument<200> sdoc; - deserializeJson(sdoc, "{\"hello\":\"world\"}"); - - DynamicJsonDocument ddoc = sdoc; - - std::string json; - serializeJson(ddoc, json); - REQUIRE(json == "{\"hello\":\"world\"}"); - REQUIRE(ddoc.capacity() == sdoc.capacity()); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); } SECTION("Assign from StaticJsonDocument") { - DynamicJsonDocument ddoc(4096); - ddoc.to().set(666); + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + DynamicJsonDocument doc2(4096); + doc2.to().set(666); - StaticJsonDocument<200> sdoc; - deserializeJson(sdoc, "{\"hello\":\"world\"}"); + doc2 = doc1; - ddoc = sdoc; + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } - std::string json; - serializeJson(ddoc, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + SECTION("Assign from JsonObject") { + StaticJsonDocument<200> doc1; + JsonObject obj = doc1.to(); + obj["hello"] = "world"; + + DynamicJsonDocument doc2(4096); + doc2 = obj; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + REQUIRE(doc2.capacity() == 4096); + } + + SECTION("Assign from JsonArray") { + StaticJsonDocument<200> doc1; + JsonArray arr = doc1.to(); + arr.add("hello"); + + DynamicJsonDocument doc2(4096); + doc2 = arr; + + REQUIRE_JSON(doc2, "[\"hello\"]"); + REQUIRE(doc2.capacity() == 4096); + } + + SECTION("Assign from JsonVariant") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "42"); + + DynamicJsonDocument doc2(4096); + doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + REQUIRE(doc2.capacity() == 4096); } } diff --git a/test/JsonDocument/StaticJsonDocument.cpp b/test/JsonDocument/StaticJsonDocument.cpp index 3ed12bc1..ae3e3d5d 100644 --- a/test/JsonDocument/StaticJsonDocument.cpp +++ b/test/JsonDocument/StaticJsonDocument.cpp @@ -5,6 +5,12 @@ #include #include +static void REQUIRE_JSON(JsonDocument& doc, const std::string& expected) { + std::string json; + serializeJson(doc, json); + REQUIRE(json == expected); +} + TEST_CASE("StaticJsonDocument") { SECTION("capacity()") { SECTION("matches template argument") { @@ -36,67 +42,171 @@ TEST_CASE("StaticJsonDocument") { doc1 = doc2; - std::string json; - serializeJson(doc1, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); } - SECTION("Copy constructor") { - StaticJsonDocument<200> doc1; - deserializeJson(doc1, "{\"hello\":\"world\"}"); + SECTION("Contructor") { + SECTION("Copy constructor") { + StaticJsonDocument<200> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); - StaticJsonDocument<200> doc2 = doc1; + StaticJsonDocument<200> doc2 = doc1; - std::string json; - serializeJson(doc2, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from StaticJsonDocument of different size") { + StaticJsonDocument<300> doc1; + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from DynamicJsonDocument") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1; + + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from JsonObject") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + StaticJsonDocument<200> doc2 = doc1.as(); + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Construct from JsonArray") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "[\"hello\",\"world\"]"); + + StaticJsonDocument<200> doc2 = doc1.as(); + + deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]"); + REQUIRE_JSON(doc2, "[\"hello\",\"world\"]"); + } + + SECTION("Construct from JsonVariant") { + DynamicJsonDocument doc1(4096); + deserializeJson(doc1, "42"); + + StaticJsonDocument<200> doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + } } - SECTION("Assign from StaticJsonDocument of different capacity") { - StaticJsonDocument<200> doc1; - StaticJsonDocument<300> doc2; - doc1.to().set(666); - deserializeJson(doc2, "{\"hello\":\"world\"}"); + SECTION("Assignment") { + SECTION("Copy assignment") { + StaticJsonDocument<200> doc1, doc2; + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); - doc1 = doc2; + doc2 = doc1; - std::string json; - serializeJson(doc1, json); - REQUIRE(json == "{\"hello\":\"world\"}"); - } + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } - SECTION("Assign from DynamicJsonDocument") { - StaticJsonDocument<200> doc1; - DynamicJsonDocument doc2(4096); - doc1.to().set(666); - deserializeJson(doc2, "{\"hello\":\"world\"}"); + SECTION("Assign from StaticJsonDocument of different capacity") { + StaticJsonDocument<200> doc1; + StaticJsonDocument<300> doc2; + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); - doc1 = doc2; + doc2 = doc1; - std::string json; - serializeJson(doc1, json); - REQUIRE(json == "{\"hello\":\"world\"}"); - } + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } - SECTION("Construct from StaticJsonDocument of different size") { - StaticJsonDocument<300> doc2; - deserializeJson(doc2, "{\"hello\":\"world\"}"); + SECTION("Assign from DynamicJsonDocument") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); - StaticJsonDocument<200> doc1 = doc2; + doc2 = doc1; - std::string json; - serializeJson(doc1, json); - REQUIRE(json == "{\"hello\":\"world\"}"); - } + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } - SECTION("Construct from DynamicJsonDocument") { - DynamicJsonDocument doc2(4096); - deserializeJson(doc2, "{\"hello\":\"world\"}"); + SECTION("Assign from JsonArray") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "[\"hello\",\"world\"]"); - StaticJsonDocument<200> doc1 = doc2; + doc2 = doc1.as(); - std::string json; - serializeJson(doc1, json); - REQUIRE(json == "{\"hello\":\"world\"}"); + deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]"); + REQUIRE_JSON(doc2, "[\"hello\",\"world\"]"); + } + + SECTION("Assign from JsonArrayConst") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "[\"hello\",\"world\"]"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "[\"HELLO\",\"WORLD\"]"); + REQUIRE_JSON(doc2, "[\"hello\",\"world\"]"); + } + + SECTION("Assign from JsonObject") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from JsonObjectConst") { + StaticJsonDocument<200> doc1; + DynamicJsonDocument doc2(4096); + doc1.to().set(666); + deserializeJson(doc1, "{\"hello\":\"world\"}"); + + doc2 = doc1.as(); + + deserializeJson(doc1, "{\"HELLO\":\"WORLD\"}"); + REQUIRE_JSON(doc2, "{\"hello\":\"world\"}"); + } + + SECTION("Assign from JsonVariant") { + DynamicJsonDocument doc1(4096); + doc1.to().set(666); + deserializeJson(doc1, "42"); + + StaticJsonDocument<200> doc2; + doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + } + + SECTION("Assign from JsonVariantConst") { + DynamicJsonDocument doc1(4096); + doc1.to().set(666); + deserializeJson(doc1, "42"); + + StaticJsonDocument<200> doc2; + doc2 = doc1.as(); + + REQUIRE_JSON(doc2, "42"); + } } }