diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8f70868..dccdf9bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ HEAD ---- * Added support for custom converters (issue #687) +* Added support for `Printable` (issue #1444) * Removed support for `char` values, see below (issue #1498) * `deserializeJson()` leaves `\uXXXX` unchanged instead of returning `NotSupported` * `deserializeMsgPack()` inserts `null` instead of returning `NotSupported` diff --git a/extras/tests/Helpers/Arduino.h b/extras/tests/Helpers/Arduino.h index 9bb9d9837..8467f0741 100644 --- a/extras/tests/Helpers/Arduino.h +++ b/extras/tests/Helpers/Arduino.h @@ -4,5 +4,6 @@ #pragma once +#include "api/Print.h" #include "api/Stream.h" #include "api/String.h" diff --git a/extras/tests/Helpers/api/Print.h b/extras/tests/Helpers/api/Print.h new file mode 100644 index 000000000..f07fbc491 --- /dev/null +++ b/extras/tests/Helpers/api/Print.h @@ -0,0 +1,33 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#pragma once + +#include +#include +#include + +class Print { + public: + virtual ~Print() {} + + virtual size_t write(uint8_t) = 0; + virtual size_t write(const uint8_t *buffer, size_t size) = 0; + + size_t write(const char *str) { + if (!str) + return 0; + return write(reinterpret_cast(str), strlen(str)); + } + + size_t write(const char *buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } +}; + +class Printable { + public: + virtual ~Printable() {} + virtual size_t printTo(Print &p) const = 0; +}; diff --git a/extras/tests/Misc/CMakeLists.txt b/extras/tests/Misc/CMakeLists.txt index 0b6157b1c..7a802ad2d 100644 --- a/extras/tests/Misc/CMakeLists.txt +++ b/extras/tests/Misc/CMakeLists.txt @@ -7,6 +7,7 @@ add_executable(MiscTests conflicts.cpp FloatParts.cpp JsonString.cpp + printable.cpp Readers.cpp StringAdapters.cpp StringWriter.cpp diff --git a/extras/tests/Misc/Readers.cpp b/extras/tests/Misc/Readers.cpp index b3e929483..582c1feb5 100644 --- a/extras/tests/Misc/Readers.cpp +++ b/extras/tests/Misc/Readers.cpp @@ -2,7 +2,7 @@ // Copyright Benoit Blanchon 2014-2021 // MIT License -#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +#include #include #include diff --git a/extras/tests/Misc/printable.cpp b/extras/tests/Misc/printable.cpp new file mode 100644 index 000000000..5143c7549 --- /dev/null +++ b/extras/tests/Misc/printable.cpp @@ -0,0 +1,112 @@ +// ArduinoJson - arduinojson.org +// Copyright Benoit Blanchon 2014-2021 +// MIT License + +#include +#include + +#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1 +#include + +struct PrintOneCharacterAtATime { + static size_t printStringTo(const std::string& s, Print& p) { + size_t result = 0; + for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) { + size_t n = p.write(uint8_t(*it)); + if (n == 0) + break; + result += n; + } + return result; + } +}; + +struct PrintAllAtOnce { + static size_t printStringTo(const std::string& s, Print& p) { + return p.write(s.data(), s.size()); + } +}; + +template +struct PrintableString : public Printable { + PrintableString(const char* s) : _str(s), _total(0) {} + + virtual size_t printTo(Print& p) const { + size_t result = PrintPolicy::printStringTo(_str, p); + _total += result; + return result; + } + + size_t totalBytesWritten() const { + return _total; + } + + private: + std::string _str; + mutable size_t _total; +}; + +TEST_CASE("Printable") { + SECTION("Enough space for the whole string") { + StaticJsonDocument<64> doc; + doc.set(666); + + SECTION("Via Print::write(char)") { + PrintableString printable = "Hello World!"; + CHECK(doc.set(printable) == true); + CHECK(doc.as() == "Hello World!"); + CHECK(printable.totalBytesWritten() == 12); + CHECK(doc.overflowed() == false); + CHECK(doc.memoryUsage() == 13); + } + + SECTION("Via Print::write(const char* size_t)") { + PrintableString printable = "Hello World!"; + CHECK(doc.set(printable) == true); + CHECK(doc.as() == "Hello World!"); + CHECK(printable.totalBytesWritten() == 12); + CHECK(doc.overflowed() == false); + CHECK(doc.memoryUsage() == 13); + } + } + + SECTION("Too small memory pool") { + StaticJsonDocument<8> doc; + + SECTION("Via Print::write(char)") { + PrintableString printable = "Hello World!"; + CHECK(doc.set(printable) == false); + CHECK(doc.isNull()); + CHECK(printable.totalBytesWritten() == 8); + CHECK(doc.overflowed() == true); + CHECK(doc.memoryUsage() == 0); + } + + SECTION("Via Print::write(const char* size_t)") { + PrintableString printable = "Hello World!"; + CHECK(doc.set(printable) == false); + CHECK(doc.isNull()); + CHECK(printable.totalBytesWritten() == 0); + CHECK(doc.overflowed() == true); + CHECK(doc.memoryUsage() == 0); + } + } + + SECTION("Null variant") { + JsonVariant var; + PrintableString printable = "Hello World!"; + CHECK(var.set(printable) == false); + CHECK(var.isNull()); + CHECK(printable.totalBytesWritten() == 0); + } + + SECTION("String deduplication") { + StaticJsonDocument<128> doc; + doc.add(PrintableString("Hello World!")); + doc.add(PrintableString("Hello World!")); + REQUIRE(doc.size() == 2); + CHECK(doc[0] == "Hello World!"); + CHECK(doc[1] == "Hello World!"); + CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 13); + } +} diff --git a/src/ArduinoJson/Variant/ConverterImpl.hpp b/src/ArduinoJson/Variant/ConverterImpl.hpp index f3bc44981..f191176c1 100644 --- a/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -206,4 +206,67 @@ struct Converter { #endif +#if ARDUINOJSON_ENABLE_ARDUINO_STREAM + +class MemoryPoolPrint : public Print { + public: + MemoryPoolPrint(MemoryPool* pool) : _pool(pool), _size(0) { + pool->getFreeZone(&_string, &_capacity); + } + + const char* c_str() { + if (_size >= _capacity) + return 0; + + _string[_size++] = 0; // TODO: test overflow + return _pool->saveStringFromFreeZone(_size); + } + + size_t write(uint8_t c) { + if (_size >= _capacity) + return 0; + + _string[_size++] = char(c); + return 1; + } + + size_t write(const uint8_t* buffer, size_t size) { + if (_size + size >= _capacity) { + _size = _capacity; // mark as overflowed + return 0; + } + memcpy(&_string[_size], buffer, size); + _size += size; + return size; + } + + bool overflowed() const { + return _size >= _capacity; + } + + private: + MemoryPool* _pool; + size_t _size; + char* _string; + size_t _capacity; +}; + +inline bool convertToJson(VariantRef variant, const ::Printable& value) { + MemoryPool* pool = getPool(variant); + VariantData* data = getData(variant); + if (!pool || !data) + return false; + MemoryPoolPrint print(pool); + value.printTo(print); + if (print.overflowed()) { + pool->markAsOverflowed(); + data->setNull(); + return false; + } + data->setOwnedString(print.c_str()); + return true; +} + +#endif + } // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index 5ffd2d13d..1e6d5c405 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -244,16 +244,26 @@ class VariantData { setType(VALUE_IS_NULL); } - void setStringPointer(const char *s, storage_policies::store_by_copy) { + void setOwnedString(const char *s) { + ARDUINOJSON_ASSERT(s != 0); setType(VALUE_IS_OWNED_STRING); _content.asString = s; } - void setStringPointer(const char *s, storage_policies::store_by_address) { + void setLinkedString(const char *s) { + ARDUINOJSON_ASSERT(s != 0); setType(VALUE_IS_LINKED_STRING); _content.asString = s; } + void setStringPointer(const char *s, storage_policies::store_by_copy) { + setOwnedString(s); + } + + void setStringPointer(const char *s, storage_policies::store_by_address) { + setLinkedString(s); + } + template bool setString(TAdaptedString value, MemoryPool *pool) { return setString(value, pool, typename TAdaptedString::storage_policy());