RawJson() accepts any kind of string and obeys to duplication rules

This commit is contained in:
Benoit Blanchon
2018-01-18 09:43:37 +01:00
parent 7e4fcb0868
commit bae179ed67
20 changed files with 163 additions and 123 deletions

View File

@ -5,17 +5,19 @@ HEAD
---- ----
* Changed the rules of string duplication (issue #658) * Changed the rules of string duplication (issue #658)
* `RawJson()` accepts any kind of string and obeys to the same rules for duplication
* Changed the return type of `strdup()` to `const char*` to prevent double duplication * Changed the return type of `strdup()` to `const char*` to prevent double duplication
* Marked `strdup()` as deprecated * Marked `strdup()` as deprecated
> ### New rules for string duplication > ### New rules for string duplication
> >
> | type | duplication | > | type | duplication |
> |:-------------|:------------| > |:---------------------------|:------------|
> | const char* | no | > | const char* | no |
> | char* | ~~no~~ yes | > | char* | ~~no~~ yes |
> | String | yes | > | String | yes |
> | std::string | yes | > | std::string | yes |
> | const __FlashStringHelper* | yes |
> >
> These new rules make `JsonBuffer::strdup()` useless. > These new rules make `JsonBuffer::strdup()` useless.

View File

@ -37,6 +37,9 @@ void setup() {
// JsonBuffer. // JsonBuffer.
root["sensor"] = F("gps"); root["sensor"] = F("gps");
// It works with RawJson too:
root["sensor"] = RawJson(F("\"gps\""));
// You can compare the content of a JsonVariant to a Flash String // You can compare the content of a JsonVariant to a Flash String
if (root["sensor"] == F("gps")) { if (root["sensor"] == F("gps")) {
// ... // ...

View File

@ -40,6 +40,9 @@ void setup() {
// WARNING: the content of the String will be duplicated in the JsonBuffer. // WARNING: the content of the String will be duplicated in the JsonBuffer.
root["sensor"] = sensor; root["sensor"] = sensor;
// It works with RawJson too:
root["sensor"] = RawJson(sensor);
// You can also concatenate strings // You can also concatenate strings
// WARNING: the content of the String will be duplicated in the JsonBuffer. // WARNING: the content of the String will be duplicated in the JsonBuffer.
root[String("sen") + "sor"] = String("gp") + "s"; root[String("sen") + "sor"] = String("gp") + "s";

View File

@ -23,11 +23,29 @@ struct ValueSaver {
template <typename Source> template <typename Source>
struct ValueSaver<Source, typename TypeTraits::EnableIf< struct ValueSaver<Source, typename TypeTraits::EnableIf<
TypeTraits::IsString<Source>::value>::type> { StringTraits<Source>::should_duplicate>::type> {
template <typename Destination> template <typename Destination>
static bool save(JsonBuffer* buffer, Destination& destination, static bool save(JsonBuffer* buffer, Destination& dest, Source source) {
Source source) { if (!StringTraits<Source>::is_null(source)) {
return StringTraits<Source>::save(source, destination, buffer); typename StringTraits<Source>::duplicate_t dup =
StringTraits<Source>::duplicate(source, buffer);
if (!dup) return false;
dest = dup;
} else {
dest = reinterpret_cast<const char*>(0);
}
return true;
}
};
// const char*, const signed char*, const unsigned char*
template <typename Char>
struct ValueSaver<Char*, typename TypeTraits::EnableIf<
!StringTraits<Char*>::should_duplicate>::type> {
template <typename Destination>
static bool save(JsonBuffer*, Destination& dest, Char* source) {
dest = reinterpret_cast<const char*>(source);
return true;
} }
}; };
} }

View File

@ -46,7 +46,7 @@ class JsonObjectSubscript
// operator=(TValue); // operator=(TValue);
// TValue = char*, const char*, const FlashStringHelper* // TValue = char*, const char*, const FlashStringHelper*
template <typename TValue> template <typename TValue>
FORCE_INLINE this_type& operator=(const TValue* src) { FORCE_INLINE this_type& operator=(TValue* src) {
_object.set(_key, src); _object.set(_key, src);
return *this; return *this;
} }

View File

@ -117,7 +117,7 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
} }
// Create a JsonVariant containing an unparsed string // Create a JsonVariant containing an unparsed string
JsonVariant(RawJson value) { JsonVariant(Internals::RawJsonString<const char *> value) {
_type = Internals::JSON_UNPARSED; _type = Internals::JSON_UNPARSED;
_content.asString = value; _content.asString = value;
} }

View File

@ -104,7 +104,7 @@ class JsonVariantComparisons {
} }
template <typename TString> template <typename TString>
typename TypeTraits::EnableIf<TypeTraits::IsString<TString>::value, typename TypeTraits::EnableIf<Internals::StringTraits<TString>::has_equals,
bool>::type bool>::type
equals(const TString &comparand) const { equals(const TString &comparand) const {
const char *value = as<const char *>(); const char *value = as<const char *>();
@ -112,9 +112,10 @@ class JsonVariantComparisons {
} }
template <typename TComparand> template <typename TComparand>
typename TypeTraits::EnableIf<!TypeTraits::IsVariant<TComparand>::value && typename TypeTraits::EnableIf<
!TypeTraits::IsString<TComparand>::value, !TypeTraits::IsVariant<TComparand>::value &&
bool>::type !Internals::StringTraits<TComparand>::has_equals,
bool>::type
equals(const TComparand &comparand) const { equals(const TComparand &comparand) const {
return as<TComparand>() == comparand; return as<TComparand>() == comparand;
} }

View File

@ -6,15 +6,41 @@
namespace ArduinoJson { namespace ArduinoJson {
namespace Internals {
// A special type of data that can be used to insert pregenerated JSON portions. // A special type of data that can be used to insert pregenerated JSON portions.
class RawJson { template <typename T>
class RawJsonString {
public: public:
explicit RawJson(const char* str) : _str(str) {} explicit RawJsonString(T str) : _str(str) {}
operator const char*() const { operator T() const {
return _str; return _str;
} }
private: private:
const char* _str; T _str;
};
template <typename String>
struct StringTraits<RawJsonString<String>, void> {
static bool is_null(RawJsonString<String> source) {
return StringTraits<String>::is_null(static_cast<String>(source));
}
typedef RawJsonString<const char*> duplicate_t;
template <typename Buffer>
static duplicate_t duplicate(RawJsonString<String> source, Buffer* buffer) {
return duplicate_t(StringTraits<String>::duplicate(source, buffer));
}
static const bool has_append = false;
static const bool has_equals = false;
static const bool should_duplicate = StringTraits<String>::should_duplicate;
}; };
} }
template <typename T>
inline Internals::RawJsonString<T> RawJson(T str) {
return Internals::RawJsonString<T>(str);
}
}

View File

@ -29,8 +29,7 @@ template <typename T>
class JsonPrintable { class JsonPrintable {
public: public:
template <typename Print> template <typename Print>
typename TypeTraits::EnableIf<!TypeTraits::IsString<Print>::value, typename TypeTraits::EnableIf<!StringTraits<Print>::has_append, size_t>::type
size_t>::type
printTo(Print &print) const { printTo(Print &print) const {
JsonWriter<Print> writer(print); JsonWriter<Print> writer(print);
JsonSerializer<JsonWriter<Print> >::serialize(downcast(), writer); JsonSerializer<JsonWriter<Print> >::serialize(downcast(), writer);
@ -79,8 +78,7 @@ class JsonPrintable {
} }
template <typename Print> template <typename Print>
typename TypeTraits::EnableIf<!TypeTraits::IsString<Print>::value, typename TypeTraits::EnableIf<!StringTraits<Print>::has_append, size_t>::type
size_t>::type
prettyPrintTo(Print &print) const { prettyPrintTo(Print &print) const {
IndentedPrint<Print> indentedPrint(print); IndentedPrint<Print> indentedPrint(print);
return prettyPrintTo(indentedPrint); return prettyPrintTo(indentedPrint);

View File

@ -43,6 +43,9 @@ struct ArduinoStreamTraits {
return c; return c;
} }
}; };
static const bool has_append = false;
static const bool has_equals = false;
}; };
template <typename TStream> template <typename TStream>

View File

@ -33,58 +33,31 @@ struct CharPointerTraits {
return strcmp(reinterpret_cast<const char*>(str), expected) == 0; return strcmp(reinterpret_cast<const char*>(str), expected) == 0;
} }
// TODO: remove static bool is_null(const TChar* str) {
return !str;
}
typedef const char* duplicate_t;
template <typename Buffer> template <typename Buffer>
static char* duplicate(const TChar* str, Buffer* buffer) { static duplicate_t duplicate(const TChar* str, Buffer* buffer) {
if (!str) return NULL; if (!str) return NULL;
size_t size = strlen(reinterpret_cast<const char*>(str)) + 1; size_t size = strlen(reinterpret_cast<const char*>(str)) + 1;
void* dup = buffer->alloc(size); void* dup = buffer->alloc(size);
if (dup != NULL) memcpy(dup, str, size); if (dup != NULL) memcpy(dup, str, size);
return static_cast<char*>(dup); return static_cast<duplicate_t>(dup);
} }
static const bool has_append = false; static const bool has_append = false;
static const bool has_equals = true; static const bool has_equals = true;
}; static const bool should_duplicate = !TypeTraits::IsConst<TChar>::value;
// const char*, const unsigned char*, const signed char*
template <typename TChar>
struct StringTraits<TChar*, typename TypeTraits::EnableIf<
TypeTraits::IsChar<TChar>::value &&
TypeTraits::IsConst<TChar>::value>::type>
: CharPointerTraits<TChar> {
// Just save the pointer
template <typename Buffer, typename Destination>
static typename TypeTraits::EnableIf<TypeTraits::IsConst<TChar>::value,
bool>::type
save(const TChar* source, Destination& dest, Buffer*) {
dest = reinterpret_cast<const char*>(source);
return true;
}
}; };
// char*, unsigned char*, signed char* // char*, unsigned char*, signed char*
// const char*, const unsigned char*, const signed char*
template <typename TChar> template <typename TChar>
struct StringTraits<TChar*, typename TypeTraits::EnableIf< struct StringTraits<TChar*, typename TypeTraits::EnableIf<
TypeTraits::IsChar<TChar>::value && TypeTraits::IsChar<TChar>::value>::type>
!TypeTraits::IsConst<TChar>::value>::type> : CharPointerTraits<TChar> {};
: CharPointerTraits<TChar> {
// Make a copy of the string
template <typename Buffer, typename Destination>
static typename TypeTraits::EnableIf<!TypeTraits::IsConst<TChar>::value,
bool>::type
save(const TChar* source, Destination& dest, Buffer* buffer) {
if (source) {
size_t size = strlen(reinterpret_cast<const char*>(source)) + 1;
void* dup = buffer->alloc(size);
if (!dup) return false;
memcpy(dup, source, size);
dest = reinterpret_cast<const char*>(dup);
} else {
dest = reinterpret_cast<const char*>(source);
}
return true;
}
};
} }
} }

View File

@ -34,32 +34,24 @@ struct StringTraits<const __FlashStringHelper*, void> {
return strcmp_P(expected, (const char*)str) == 0; return strcmp_P(expected, (const char*)str) == 0;
} }
// TODO: remove static bool is_null(const __FlashStringHelper* str) {
return !str;
}
typedef const char* duplicate_t;
template <typename Buffer> template <typename Buffer>
static char* duplicate(const __FlashStringHelper* str, Buffer* buffer) { static duplicate_t duplicate(const __FlashStringHelper* str, Buffer* buffer) {
if (!str) return NULL; if (!str) return NULL;
size_t size = strlen_P((const char*)str) + 1; size_t size = strlen_P((const char*)str) + 1;
void* dup = buffer->alloc(size); void* dup = buffer->alloc(size);
if (dup != NULL) memcpy_P(dup, (const char*)str, size); if (dup != NULL) memcpy_P(dup, (const char*)str, size);
return static_cast<char*>(dup); return static_cast<duplicate_t>(dup);
}
template <typename Buffer, typename Destination>
static bool save(const __FlashStringHelper* source, Destination& dest,
Buffer* buffer) {
if (source) {
size_t size = strlen_P((const char*)source) + 1;
void* dup = buffer->alloc(size);
if (dup != NULL) memcpy_P(dup, (const char*)source, size);
dest = reinterpret_cast<const char*>(dup);
} else {
dest = reinterpret_cast<const char*>(source);
}
return true;
} }
static const bool has_append = false; static const bool has_append = false;
static const bool has_equals = true; static const bool has_equals = true;
static const bool should_duplicate = true;
}; };
} }
} }

View File

@ -42,6 +42,9 @@ struct StdStreamTraits {
return _stream.eof() ? '\0' : static_cast<char>(_stream.get()); return _stream.eof() ? '\0' : static_cast<char>(_stream.get());
} }
}; };
static const bool has_append = false;
static const bool has_equals = false;
}; };
template <typename TStream> template <typename TStream>

View File

@ -19,29 +19,20 @@ namespace Internals {
template <typename TString> template <typename TString>
struct StdStringTraits { struct StdStringTraits {
// TODO: remove typedef const char* duplicate_t;
template <typename Buffer> template <typename Buffer>
static char* duplicate(const TString& str, Buffer* buffer) { static duplicate_t duplicate(const TString& str, Buffer* buffer) {
if (!str.c_str()) return NULL; // <- Arduino string can return NULL if (!str.c_str()) return NULL; // <- Arduino string can return NULL
size_t size = str.length() + 1; size_t size = str.length() + 1;
void* dup = buffer->alloc(size); void* dup = buffer->alloc(size);
if (dup != NULL) memcpy(dup, str.c_str(), size); if (dup != NULL) memcpy(dup, str.c_str(), size);
return static_cast<char*>(dup); return static_cast<duplicate_t>(dup);
} }
template <typename Buffer, typename Destination> static bool is_null(const TString& str) {
static bool save(const TString& str, Destination& dest, Buffer* buffer) {
// Arduino's String::c_str() can return NULL // Arduino's String::c_str() can return NULL
if (str.c_str()) { return !str.c_str();
size_t size = str.length() + 1;
void* dup = buffer->alloc(size);
if (!dup) return false;
memcpy(dup, str.c_str(), size);
dest = reinterpret_cast<const char*>(dup);
} else {
dest = str.c_str();
}
return true;
} }
struct Reader : CharPointerTraits<char>::Reader { struct Reader : CharPointerTraits<char>::Reader {
@ -62,6 +53,7 @@ struct StdStringTraits {
static const bool has_append = true; static const bool has_append = true;
static const bool has_equals = true; static const bool has_equals = true;
static const bool should_duplicate = true;
}; };
#if ARDUINOJSON_ENABLE_ARDUINO_STRING #if ARDUINOJSON_ENABLE_ARDUINO_STRING

View File

@ -16,7 +16,10 @@ namespace ArduinoJson {
namespace Internals { namespace Internals {
template <typename TString, typename Enable = void> template <typename TString, typename Enable = void>
struct StringTraits {}; struct StringTraits {
static const bool has_append = false;
static const bool has_equals = false;
};
template <typename TString> template <typename TString>
struct StringTraits<const TString, void> : StringTraits<TString> {}; struct StringTraits<const TString, void> : StringTraits<TString> {};
@ -31,18 +34,3 @@ struct StringTraits<TString&, void> : StringTraits<TString> {};
#include "FlashString.hpp" #include "FlashString.hpp"
#include "StdStream.hpp" #include "StdStream.hpp"
#include "StdString.hpp" #include "StdString.hpp"
namespace ArduinoJson {
namespace TypeTraits {
template <typename T, typename Enable = void>
struct IsString {
static const bool value = false;
};
template <typename T>
struct IsString<T, typename TypeTraits::EnableIf<
Internals::StringTraits<T>::has_equals>::type> {
static const bool value = Internals::StringTraits<T>::has_equals;
};
}
}

View File

@ -84,7 +84,7 @@ TEST_CASE("JsonArray::add()") {
REQUIRE(expectedSize == _jsonBuffer.size()); REQUIRE(expectedSize == _jsonBuffer.size());
} }
SECTION("should duplicate char*") { SECTION("should duplicate char*") {
_array.add(const_cast<char*>("world")); _array.add(const_cast<char*>("world"));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6; const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6;
REQUIRE(expectedSize == _jsonBuffer.size()); REQUIRE(expectedSize == _jsonBuffer.size());
@ -95,4 +95,16 @@ TEST_CASE("JsonArray::add()") {
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6; const size_t expectedSize = JSON_ARRAY_SIZE(1) + 6;
REQUIRE(expectedSize == _jsonBuffer.size()); REQUIRE(expectedSize == _jsonBuffer.size());
} }
SECTION("should not duplicate RawJson(const char*)") {
_array.add(RawJson("{}"));
const size_t expectedSize = JSON_ARRAY_SIZE(1);
REQUIRE(expectedSize == _jsonBuffer.size());
}
SECTION("should duplicate RawJson(char*)") {
_array.add(RawJson(const_cast<char*>("{}")));
const size_t expectedSize = JSON_ARRAY_SIZE(1) + 3;
REQUIRE(expectedSize == _jsonBuffer.size());
}
} }

View File

@ -8,10 +8,10 @@
static void check(JsonArray &array, std::string expected) { static void check(JsonArray &array, std::string expected) {
std::string actual; std::string actual;
size_t actualLen = array.printTo(actual); size_t actualLen = array.printTo(actual);
size_t measuredLen = array.measureLength();
CHECK(actualLen == expected.size());
CHECK(measuredLen == expected.size());
REQUIRE(expected == actual); REQUIRE(expected == actual);
REQUIRE(actualLen == expected.size());
size_t measuredLen = array.measureLength();
REQUIRE(measuredLen == expected.size());
} }
TEST_CASE("JsonArray::printTo()") { TEST_CASE("JsonArray::printTo()") {
@ -67,12 +67,22 @@ TEST_CASE("JsonArray::printTo()") {
check(array, "[1,2]"); check(array, "[1,2]");
} }
SECTION("RawJson") { SECTION("RawJson(const char*)") {
array.add(RawJson("{\"key\":\"value\"}")); array.add(RawJson("{\"key\":\"value\"}"));
check(array, "[{\"key\":\"value\"}]"); check(array, "[{\"key\":\"value\"}]");
} }
SECTION("RawJson(char*)") {
DynamicJsonBuffer jb2;
JsonArray &arr = jb2.createArray();
char tmp[] = "{\"key\":\"value\"}";
arr.add(RawJson(tmp));
check(arr, "[{\"key\":\"value\"}]");
}
SECTION("OneIntegerOverCapacity") { SECTION("OneIntegerOverCapacity") {
array.add(1); array.add(1);
array.add(2); array.add(2);

View File

@ -8,6 +8,7 @@ add_executable(MiscTests
std_stream.cpp std_stream.cpp
std_string.cpp std_string.cpp
StringBuilder.cpp StringBuilder.cpp
StringTraits.cpp
TypeTraits.cpp TypeTraits.cpp
unsigned_char.cpp unsigned_char.cpp
vla.cpp vla.cpp

View File

@ -0,0 +1,22 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson.h>
#include <catch.hpp>
using namespace ArduinoJson::Internals;
template <typename String>
bool should_duplicate() {
return StringTraits<String>::should_duplicate;
}
TEST_CASE("StringTraits") {
SECTION("should_duplicate") {
REQUIRE(false == should_duplicate<const char*>());
REQUIRE(true == should_duplicate<char*>());
REQUIRE(true == should_duplicate<RawJsonString<char*> >());
REQUIRE(false == should_duplicate<RawJsonString<const char*> >());
}
}

View File

@ -4,7 +4,6 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <catch.hpp> #include <catch.hpp>
#include <sstream>
using namespace ArduinoJson::TypeTraits; using namespace ArduinoJson::TypeTraits;
@ -31,12 +30,6 @@ TEST_CASE("TypeTraits") {
REQUIRE(static_cast<bool>(IsVariant<JsonVariant>::value)); REQUIRE(static_cast<bool>(IsVariant<JsonVariant>::value));
} }
SECTION("IsString") {
REQUIRE((IsString<const char*>::value));
REQUIRE((IsString<std::string>::value));
REQUIRE_FALSE((IsString<double>::value));
}
SECTION("IsConst") { SECTION("IsConst") {
REQUIRE_FALSE((IsConst<char>::value)); REQUIRE_FALSE((IsConst<char>::value));
REQUIRE((IsConst<const char>::value)); REQUIRE((IsConst<const char>::value));