Skip to content

Commit

Permalink
Added support for Printable (closes #1444)
Browse files Browse the repository at this point in the history
  • Loading branch information
bblanchon committed Mar 27, 2021
1 parent d7f5b56 commit 347ac42
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
1 change: 1 addition & 0 deletions extras/tests/Helpers/Arduino.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

#pragma once

#include "api/Print.h"
#include "api/Stream.h"
#include "api/String.h"
33 changes: 33 additions & 0 deletions extras/tests/Helpers/api/Print.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2021
// MIT License

#pragma once

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

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<const uint8_t *>(str), strlen(str));
}

size_t write(const char *buffer, size_t size) {
return write(reinterpret_cast<const uint8_t *>(buffer), size);
}
};

class Printable {
public:
virtual ~Printable() {}
virtual size_t printTo(Print &p) const = 0;
};
1 change: 1 addition & 0 deletions extras/tests/Misc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_executable(MiscTests
conflicts.cpp
FloatParts.cpp
JsonString.cpp
printable.cpp
Readers.cpp
StringAdapters.cpp
StringWriter.cpp
Expand Down
2 changes: 1 addition & 1 deletion extras/tests/Misc/Readers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright Benoit Blanchon 2014-2021
// MIT License

#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1
#include <Arduino.h>
#include <ArduinoJson.hpp>
#include <catch.hpp>

Expand Down
112 changes: 112 additions & 0 deletions extras/tests/Misc/printable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2021
// MIT License

#include <Arduino.h>
#include <catch.hpp>

#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1
#include <ArduinoJson.h>

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 <typename PrintPolicy>
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<PrintOneCharacterAtATime> printable = "Hello World!";
CHECK(doc.set(printable) == true);
CHECK(doc.as<std::string>() == "Hello World!");
CHECK(printable.totalBytesWritten() == 12);
CHECK(doc.overflowed() == false);
CHECK(doc.memoryUsage() == 13);
}

SECTION("Via Print::write(const char* size_t)") {
PrintableString<PrintAllAtOnce> printable = "Hello World!";
CHECK(doc.set(printable) == true);
CHECK(doc.as<std::string>() == "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<PrintOneCharacterAtATime> 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<PrintAllAtOnce> 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<PrintOneCharacterAtATime> printable = "Hello World!";
CHECK(var.set(printable) == false);
CHECK(var.isNull());
CHECK(printable.totalBytesWritten() == 0);
}

SECTION("String deduplication") {
StaticJsonDocument<128> doc;
doc.add(PrintableString<PrintOneCharacterAtATime>("Hello World!"));
doc.add(PrintableString<PrintAllAtOnce>("Hello World!"));
REQUIRE(doc.size() == 2);
CHECK(doc[0] == "Hello World!");
CHECK(doc[1] == "Hello World!");
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 13);
}
}
63 changes: 63 additions & 0 deletions src/ArduinoJson/Variant/ConverterImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,67 @@ struct Converter<decltype(nullptr)> {

#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
14 changes: 12 additions & 2 deletions src/ArduinoJson/Variant/VariantData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename TAdaptedString>
bool setString(TAdaptedString value, MemoryPool *pool) {
return setString(value, pool, typename TAdaptedString::storage_policy());
Expand Down

0 comments on commit 347ac42

Please sign in to comment.