Fixed object keys not being duplicated

This commit is contained in:
Benoit Blanchon
2018-10-04 11:16:23 +02:00
parent 527dc19794
commit 6b985b2d1f
30 changed files with 368 additions and 218 deletions

View File

@ -6,6 +6,8 @@ HEAD
* Added implicit conversion from `JsonArray` and `JsonObject` to `JsonVariant` * Added implicit conversion from `JsonArray` and `JsonObject` to `JsonVariant`
* Allow mixed configuration in compilation units (issue #809) * Allow mixed configuration in compilation units (issue #809)
* Fixed object keys not being duplicated
* `JsonPair::key()` now returns a `JsonKey`
v6.4.0-beta (2018-09-11) v6.4.0-beta (2018-09-11)
----------- -----------

View File

@ -31,7 +31,9 @@ using ARDUINOJSON_NAMESPACE::DynamicJsonDocument;
using ARDUINOJSON_NAMESPACE::JsonArray; using ARDUINOJSON_NAMESPACE::JsonArray;
using ARDUINOJSON_NAMESPACE::JsonFloat; using ARDUINOJSON_NAMESPACE::JsonFloat;
using ARDUINOJSON_NAMESPACE::JsonInteger; using ARDUINOJSON_NAMESPACE::JsonInteger;
using ARDUINOJSON_NAMESPACE::JsonKey;
using ARDUINOJSON_NAMESPACE::JsonObject; using ARDUINOJSON_NAMESPACE::JsonObject;
using ARDUINOJSON_NAMESPACE::JsonPair;
using ARDUINOJSON_NAMESPACE::JsonUInt; using ARDUINOJSON_NAMESPACE::JsonUInt;
using ARDUINOJSON_NAMESPACE::JsonVariant; using ARDUINOJSON_NAMESPACE::JsonVariant;
using ARDUINOJSON_NAMESPACE::serialized; using ARDUINOJSON_NAMESPACE::serialized;

View File

@ -1,42 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include <stdlib.h> // size_t
#include "JsonFloat.hpp"
#include "JsonInteger.hpp"
namespace ARDUINOJSON_NAMESPACE {
struct JsonObjectData {
struct Slot* head;
struct Slot* tail;
};
struct JsonArrayData {
struct Slot* head;
struct Slot* tail;
};
struct RawData {
const char* data;
size_t size;
};
// A union that defines the actual content of a JsonVariantData.
// The enum JsonVariantType determines which member is in use.
union JsonVariantContent {
JsonFloat asFloat;
JsonUInt asInteger;
JsonArrayData asArray;
JsonObjectData asObject;
const char* asString;
struct {
const char* data;
size_t size;
} asRaw;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -4,14 +4,60 @@
#pragma once #pragma once
#include "JsonVariantContent.hpp" #include <stdlib.h> // size_t
#include "JsonVariantType.hpp"
#include "JsonFloat.hpp"
#include "JsonInteger.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
enum JsonVariantType {
JSON_NULL,
JSON_LINKED_RAW,
JSON_OWNED_RAW,
JSON_LINKED_STRING,
JSON_OWNED_STRING,
JSON_BOOLEAN,
JSON_POSITIVE_INTEGER,
JSON_NEGATIVE_INTEGER,
JSON_ARRAY,
JSON_OBJECT,
JSON_FLOAT
};
struct JsonObjectData {
struct Slot *head;
struct Slot *tail;
};
struct JsonArrayData {
struct Slot *head;
struct Slot *tail;
};
struct RawData {
const char *data;
size_t size;
};
// A union that defines the actual content of a JsonVariantData.
// The enum JsonVariantType determines which member is in use.
union JsonVariantContent {
JsonFloat asFloat;
JsonUInt asInteger;
JsonArrayData asArray;
JsonObjectData asObject;
const char *asString;
struct {
const char *data;
size_t size;
} asRaw;
};
// this struct must be a POD type to prevent error calling offsetof on clang // this struct must be a POD type to prevent error calling offsetof on clang
struct JsonVariantData { struct JsonVariantData {
JsonVariantType type; bool keyIsStatic : 1;
JsonVariantType type : 7;
JsonVariantContent content; JsonVariantContent content;
}; };

View File

@ -1,23 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
namespace ARDUINOJSON_NAMESPACE {
// Enumerated type to know the current type of a JsonVariant.
// The value determines which member of JsonVariantContent is used.
enum JsonVariantType {
JSON_NULL,
JSON_LINKED_RAW,
JSON_OWNED_RAW,
JSON_LINKED_STRING,
JSON_OWNED_STRING,
JSON_BOOLEAN,
JSON_POSITIVE_INTEGER,
JSON_NEGATIVE_INTEGER,
JSON_ARRAY,
JSON_OBJECT,
JSON_FLOAT
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -16,6 +16,9 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TReader, typename TStringStorage> template <typename TReader, typename TStringStorage>
class JsonDeserializer { class JsonDeserializer {
typedef typename remove_reference<TStringStorage>::type::StringBuilder
StringBuilder;
public: public:
JsonDeserializer(MemoryPool &memoryPool, TReader reader, JsonDeserializer(MemoryPool &memoryPool, TReader reader,
TStringStorage stringStorage, uint8_t nestingLimit) TStringStorage stringStorage, uint8_t nestingLimit)
@ -121,8 +124,8 @@ class JsonDeserializer {
// Read each key value pair // Read each key value pair
for (;;) { for (;;) {
// Parse key // Parse key
const char *key; StringInMemoryPool key;
err = parseKey(&key); err = parseKey(key);
if (err) return err; if (err) return err;
// Skip spaces // Skip spaces
@ -162,7 +165,7 @@ class JsonDeserializer {
} }
} }
DeserializationError parseKey(const char **key) { DeserializationError parseKey(StringInMemoryPool &key) {
if (isQuote(current())) { if (isQuote(current())) {
return parseQuotedString(key); return parseQuotedString(key);
} else { } else {
@ -171,18 +174,16 @@ class JsonDeserializer {
} }
DeserializationError parseStringValue(JsonVariant variant) { DeserializationError parseStringValue(JsonVariant variant) {
const char *value; StringInMemoryPool value;
DeserializationError err = parseQuotedString(&value); DeserializationError err = parseQuotedString(value);
if (err) return err; if (err) return err;
variant.set(value); variant.set(value);
return DeserializationError::Ok; return DeserializationError::Ok;
} }
DeserializationError parseQuotedString(const char **result) { DeserializationError parseQuotedString(StringInMemoryPool &result) {
typename remove_reference<TStringStorage>::type::String str = StringBuilder str = _stringStorage.startString();
_stringStorage.startString(); const char stopChar = current();
char stopChar = current();
move(); move();
for (;;) { for (;;) {
@ -205,14 +206,13 @@ class JsonDeserializer {
str.append(c); str.append(c);
} }
*result = str.c_str(); result = str.complete();
if (*result == NULL) return DeserializationError::NoMemory; if (result.isNull()) return DeserializationError::NoMemory;
return DeserializationError::Ok; return DeserializationError::Ok;
} }
DeserializationError parseNonQuotedString(const char **result) { DeserializationError parseNonQuotedString(StringInMemoryPool &result) {
typename remove_reference<TStringStorage>::type::String str = StringBuilder str = _stringStorage.startString();
_stringStorage.startString();
char c = current(); char c = current();
if (c == '\0') return DeserializationError::IncompleteInput; if (c == '\0') return DeserializationError::IncompleteInput;
@ -227,8 +227,8 @@ class JsonDeserializer {
return DeserializationError::InvalidInput; return DeserializationError::InvalidInput;
} }
*result = str.c_str(); result = str.complete();
if (*result == NULL) return DeserializationError::NoMemory; if (result.isNull()) return DeserializationError::NoMemory;
return DeserializationError::Ok; return DeserializationError::Ok;
} }

View File

@ -57,7 +57,10 @@ class JsonObject {
bool ok = _data != 0; bool ok = _data != 0;
clear(); clear();
for (iterator it = src.begin(); it != src.end(); ++it) { for (iterator it = src.begin(); it != src.end(); ++it) {
ok &= set(it->key(), it->value()); if (it->key().isStatic())
ok &= set(it->key().c_str(), it->value());
else
ok &= set(const_cast<char*>(it->key().c_str()), it->value());
} }
return ok; return ok;
} }
@ -103,7 +106,7 @@ class JsonObject {
template <typename TValue, typename TString> template <typename TValue, typename TString>
FORCE_INLINE typename JsonVariantAs<TValue>::type get( FORCE_INLINE typename JsonVariantAs<TValue>::type get(
const TString& key) const { const TString& key) const {
return get_impl<const TString&, TValue>(key); return get_impl<TValue>(makeString(key));
} }
// //
// TValue get<TValue>(TKey) const; // TValue get<TValue>(TKey) const;
@ -112,7 +115,7 @@ class JsonObject {
// std::string, String, JsonArray, JsonObject // std::string, String, JsonArray, JsonObject
template <typename TValue, typename TString> template <typename TValue, typename TString>
FORCE_INLINE typename JsonVariantAs<TValue>::type get(TString* key) const { FORCE_INLINE typename JsonVariantAs<TValue>::type get(TString* key) const {
return get_impl<TString*, TValue>(key); return get_impl<TValue>(makeString(key));
} }
// Checks the type of the value associated with the specified key. // Checks the type of the value associated with the specified key.
@ -199,14 +202,14 @@ class JsonObject {
// TKey = const std::string&, const String& // TKey = const std::string&, const String&
template <typename TString> template <typename TString>
FORCE_INLINE void remove(const TString& key) { FORCE_INLINE void remove(const TString& key) {
remove_impl<const TString&>(key); remove_impl(makeString(key));
} }
// //
// void remove(TKey); // void remove(TKey);
// TKey = char*, const char*, char[], const char[], const FlashStringHelper* // TKey = char*, const char*, char[], const char[], const FlashStringHelper*
template <typename TString> template <typename TString>
FORCE_INLINE void remove(TString* key) { FORCE_INLINE void remove(TString* key) {
remove_impl<TString*>(key); remove_impl(makeString(key));
} }
// Sets the specified key with the specified value. // Sets the specified key with the specified value.
@ -247,12 +250,16 @@ class JsonObject {
template <typename TString> template <typename TString>
FORCE_INLINE JsonVariant set(TString* key) { FORCE_INLINE JsonVariant set(TString* key) {
return set_impl<TString*>(key); return set_impl(makeString(key));
} }
template <typename TString> template <typename TString>
FORCE_INLINE JsonVariant set(const TString& key) { FORCE_INLINE JsonVariant set(const TString& key) {
return set_impl<const TString&>(key); return set_impl(makeString(key));
}
FORCE_INLINE JsonVariant set(const StringInMemoryPool& key) {
return set_impl(key);
} }
FORCE_INLINE size_t size() const { FORCE_INLINE size_t size() const {
@ -281,7 +288,7 @@ class JsonObject {
private: private:
template <typename TStringRef> template <typename TStringRef>
FORCE_INLINE bool containsKey_impl(TStringRef key) const { FORCE_INLINE bool containsKey_impl(TStringRef key) const {
return findSlot<TStringRef>(key) != 0; return findSlot(makeString(key)) != 0;
} }
template <typename TStringRef> template <typename TStringRef>
@ -296,34 +303,34 @@ class JsonObject {
if (!_data) return 0; if (!_data) return 0;
Slot* slot = _data->head; Slot* slot = _data->head;
while (slot) { while (slot) {
if (makeString(key).equals(slot->key)) break; if (key.equals(slot->key)) break;
slot = slot->next; slot = slot->next;
} }
return slot; return slot;
} }
template <typename TStringRef> template <typename TStringRef>
FORCE_INLINE Slot* findSlot(TStringRef key) const { FORCE_INLINE Slot* findSlot(TStringRef key) const {
return const_cast<JsonObject*>(this)->findSlot<TStringRef>(key); return const_cast<JsonObject*>(this)->findSlot(key);
} }
template <typename TStringRef, typename TValue> template <typename TValue, typename TStringRef>
FORCE_INLINE typename JsonVariantAs<TValue>::type get_impl( FORCE_INLINE typename JsonVariantAs<TValue>::type get_impl(
TStringRef key) const { TStringRef key) const {
Slot* slot = findSlot<TStringRef>(key); Slot* slot = findSlot(key);
return slot ? JsonVariant(_memoryPool, &slot->value).as<TValue>() return slot ? JsonVariant(_memoryPool, &slot->value).as<TValue>()
: TValue(); : TValue();
} }
template <typename TStringRef, typename TValue> template <typename TStringRef, typename TValue>
FORCE_INLINE bool is_impl(TStringRef key) const { FORCE_INLINE bool is_impl(TStringRef key) const {
Slot* slot = findSlot<TStringRef>(key); Slot* slot = findSlot(makeString(key));
return slot ? JsonVariant(_memoryPool, &slot->value).is<TValue>() : false; return slot ? JsonVariant(_memoryPool, &slot->value).is<TValue>() : false;
} }
template <typename TStringRef> template <typename TStringRef>
FORCE_INLINE void remove_impl(TStringRef key) { FORCE_INLINE void remove_impl(TStringRef key) {
if (!_data) return; if (!_data) return;
Slot* slot = findSlot<TStringRef>(key); Slot* slot = findSlot(key);
if (!slot) return; if (!slot) return;
if (slot->prev) if (slot->prev)
slot->prev->next = slot->next; slot->prev->next = slot->next;
@ -340,10 +347,10 @@ class JsonObject {
if (!_data) return JsonVariant(); if (!_data) return JsonVariant();
// ignore null key // ignore null key
if (makeString(key).is_null()) return JsonVariant(); if (key.isNull()) return JsonVariant();
// search a matching key // search a matching key
Slot* slot = findSlot<TStringRef>(key); Slot* slot = findSlot(key);
if (!slot) { if (!slot) {
// add the key // add the key
slot = new (_memoryPool) Slot(); slot = new (_memoryPool) Slot();
@ -367,20 +374,28 @@ class JsonObject {
return JsonVariant(_memoryPool, &slot->value); return JsonVariant(_memoryPool, &slot->value);
} }
FORCE_INLINE bool set_key(Slot* slot, const char* key) { template <typename TStringRef>
slot->key = key; FORCE_INLINE bool set_key(Slot* slot, TStringRef key) {
const char* dup = key.save(_memoryPool);
if (!dup) return false;
slot->key = dup;
slot->value.keyIsStatic = false;
return true; return true;
} }
template <typename T> FORCE_INLINE bool set_key(Slot* slot, ZeroTerminatedRamStringConst key) {
FORCE_INLINE bool set_key(Slot* slot, const T& key) { slot->key = key.save(_memoryPool);
const char* dup = makeString(key).save(_memoryPool); slot->value.keyIsStatic = true;
if (!dup) return false; return true;
slot->key = dup; }
FORCE_INLINE bool set_key(Slot* slot, StringInMemoryPool key) {
slot->key = key.save(_memoryPool);
slot->value.keyIsStatic = false;
return true; return true;
} }
mutable MemoryPool* _memoryPool; mutable MemoryPool* _memoryPool;
mutable JsonObjectData* _data; mutable JsonObjectData* _data;
}; // namespace ARDUINOJSON_NAMESPACE };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -8,19 +8,45 @@
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
class JsonKey {
public:
JsonKey(Slot* slot) : _slot(slot) {}
operator const char*() const {
return c_str();
}
const char* c_str() const {
return _slot ? _slot->key : 0;
}
bool isNull() const {
return _slot == 0 || _slot->key == 0;
}
bool isStatic() const {
return _slot ? _slot->value.keyIsStatic : true;
}
friend bool operator==(JsonKey lhs, const char* rhs) {
if (lhs.isNull()) return rhs == 0;
return rhs ? !strcmp(lhs, rhs) : false;
}
private:
Slot* _slot;
};
// A key value pair for JsonObjectData. // A key value pair for JsonObjectData.
class JsonPair { class JsonPair {
public: public:
JsonPair(MemoryPool* memoryPool, Slot* slot) { JsonPair(MemoryPool* memoryPool, Slot* slot) : _key(slot) {
if (slot) { if (slot) {
_key = slot->key;
_value = JsonVariant(memoryPool, &slot->value); _value = JsonVariant(memoryPool, &slot->value);
} else {
_key = 0;
} }
} }
const char* key() const { JsonKey key() const {
return _key; return _key;
} }
@ -29,7 +55,7 @@ class JsonPair {
} }
private: private:
const char* _key; JsonKey _key;
JsonVariant _value; JsonVariant _value;
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

@ -125,32 +125,14 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
template <typename T> template <typename T>
FORCE_INLINE bool set(const T &value, FORCE_INLINE bool set(const T &value,
typename enable_if<IsString<T>::value>::type * = 0) { typename enable_if<IsString<T>::value>::type * = 0) {
if (!_data) return false; return setString(makeString(value));
const char *dup = makeString(value).save(_memoryPool);
if (dup) {
_data->type = JSON_OWNED_STRING;
_data->content.asString = dup;
return true;
} else {
_data->type = JSON_NULL;
return false;
}
} }
// set(char*) // set(char*)
template <typename T> template <typename T>
FORCE_INLINE bool set(T *value, FORCE_INLINE bool set(T *value,
typename enable_if<IsString<T *>::value>::type * = 0) { typename enable_if<IsString<T *>::value>::type * = 0) {
if (!_data) return false; return setString(makeString(value));
const char *dup = makeString(value).save(_memoryPool);
if (dup) {
_data->type = JSON_OWNED_STRING;
_data->content.asString = dup;
return true;
} else {
_data->type = JSON_NULL;
return false;
}
} }
// set(const char*); // set(const char*);
@ -161,6 +143,14 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
return true; return true;
} }
// set(const char*);
FORCE_INLINE bool set(StringInMemoryPool value) {
if (!_data) return false;
_data->type = JSON_OWNED_STRING;
_data->content.asString = value.save(_memoryPool);
return true;
}
bool set(const JsonVariant &value); bool set(const JsonVariant &value);
FORCE_INLINE bool set(JsonArray array); FORCE_INLINE bool set(JsonArray array);
@ -367,6 +357,20 @@ class JsonVariant : public JsonVariantBase<JsonVariant> {
typename enable_if<is_same<T, JsonVariant>::value, JsonVariant>::type to(); typename enable_if<is_same<T, JsonVariant>::value, JsonVariant>::type to();
private: private:
template <typename TStringRef>
bool setString(TStringRef value) {
if (!_data) return false;
const char *dup = value.save(_memoryPool);
if (dup) {
_data->type = JSON_OWNED_STRING;
_data->content.asString = dup;
return true;
} else {
_data->type = JSON_NULL;
return false;
}
}
MemoryPool *_memoryPool; MemoryPool *_memoryPool;
JsonVariantData *_data; JsonVariantData *_data;
}; };

View File

@ -4,6 +4,7 @@
#pragma once #pragma once
#include "../Strings/StringInMemoryPool.hpp"
#include "MemoryPool.hpp" #include "MemoryPool.hpp"
#include <stdlib.h> #include <stdlib.h>
@ -77,9 +78,9 @@ class DynamicMemoryPoolBase : public MemoryPool {
_head = 0; _head = 0;
} }
class String { class StringBuilder {
public: public:
String(DynamicMemoryPoolBase* parent) explicit StringBuilder(DynamicMemoryPoolBase* parent)
: _parent(parent), _start(NULL), _length(0) {} : _parent(parent), _start(NULL), _length(0) {}
void append(char c) { void append(char c) {
@ -97,7 +98,7 @@ class DynamicMemoryPoolBase : public MemoryPool {
_length++; _length++;
} }
const char* c_str() { StringInMemoryPool complete() {
append(0); append(0);
return _start; return _start;
} }
@ -108,8 +109,8 @@ class DynamicMemoryPoolBase : public MemoryPool {
size_t _length; size_t _length;
}; };
String startString() { StringBuilder startString() {
return String(this); return StringBuilder(this);
} }
private: private:

View File

@ -5,15 +5,16 @@
#pragma once #pragma once
#include "../Polyfills/mpl/max.hpp" #include "../Polyfills/mpl/max.hpp"
#include "../Strings/StringInMemoryPool.hpp"
#include "MemoryPool.hpp" #include "MemoryPool.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
class StaticMemoryPoolBase : public MemoryPool { class StaticMemoryPoolBase : public MemoryPool {
public: public:
class String { class StringBuilder {
public: public:
String(StaticMemoryPoolBase* parent) : _parent(parent) { explicit StringBuilder(StaticMemoryPoolBase* parent) : _parent(parent) {
_start = parent->_buffer + parent->_size; _start = parent->_buffer + parent->_size;
} }
@ -24,7 +25,7 @@ class StaticMemoryPoolBase : public MemoryPool {
} }
} }
const char* c_str() const { StringInMemoryPool complete() const {
if (_parent->canAlloc(1)) { if (_parent->canAlloc(1)) {
char* last = static_cast<char*>(_parent->doAlloc(1)); char* last = static_cast<char*>(_parent->doAlloc(1));
*last = '\0'; *last = '\0';
@ -65,8 +66,8 @@ class StaticMemoryPoolBase : public MemoryPool {
_size = 0; _size = 0;
} }
String startString() { StringBuilder startString() {
return String(this); return StringBuilder(this);
} }
protected: protected:

View File

@ -15,6 +15,9 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TReader, typename TStringStorage> template <typename TReader, typename TStringStorage>
class MsgPackDeserializer { class MsgPackDeserializer {
typedef typename remove_reference<TStringStorage>::type::StringBuilder
StringBuilder;
public: public:
MsgPackDeserializer(MemoryPool &memoryPool, TReader reader, MsgPackDeserializer(MemoryPool &memoryPool, TReader reader,
TStringStorage stringStorage, uint8_t nestingLimit) TStringStorage stringStorage, uint8_t nestingLimit)
@ -218,15 +221,14 @@ class MsgPackDeserializer {
} }
DeserializationError readString(JsonVariant variant, size_t n) { DeserializationError readString(JsonVariant variant, size_t n) {
typename remove_reference<TStringStorage>::type::String str = StringBuilder str = _stringStorage.startString();
_stringStorage.startString();
for (; n; --n) { for (; n; --n) {
uint8_t c; uint8_t c;
if (!readBytes(c)) return DeserializationError::IncompleteInput; if (!readBytes(c)) return DeserializationError::IncompleteInput;
str.append(static_cast<char>(c)); str.append(static_cast<char>(c));
} }
const char *s = str.c_str(); StringInMemoryPool s = str.complete();
if (s == NULL) return DeserializationError::NoMemory; if (s.isNull()) return DeserializationError::NoMemory;
variant.set(s); variant.set(s);
return DeserializationError::Ok; return DeserializationError::Ok;
} }
@ -281,7 +283,7 @@ class MsgPackDeserializer {
if (err) return err; if (err) return err;
if (!key.is<char *>()) return DeserializationError::NotSupported; if (!key.is<char *>()) return DeserializationError::NotSupported;
JsonVariant value = object.set(key.as<char *>()); JsonVariant value = object.set(StringInMemoryPool(key.as<char *>()));
if (value.isInvalid()) return DeserializationError::NoMemory; if (value.isInvalid()) return DeserializationError::NoMemory;
err = parse(value); err = parse(value);

View File

@ -11,9 +11,9 @@ class StringCopier {
public: public:
StringCopier(TMemoryPool& memoryPool) : _memoryPool(&memoryPool) {} StringCopier(TMemoryPool& memoryPool) : _memoryPool(&memoryPool) {}
typedef typename TMemoryPool::String String; typedef typename TMemoryPool::StringBuilder StringBuilder;
String startString() { StringBuilder startString() {
return _memoryPool->startString(); return _memoryPool->startString();
} }

View File

@ -9,15 +9,15 @@ namespace ARDUINOJSON_NAMESPACE {
template <typename TChar> template <typename TChar>
class StringMover { class StringMover {
public: public:
class String { class StringBuilder {
public: public:
String(TChar** ptr) : _writePtr(ptr), _startPtr(*ptr) {} StringBuilder(TChar** ptr) : _writePtr(ptr), _startPtr(*ptr) {}
void append(char c) { void append(char c) {
*(*_writePtr)++ = TChar(c); *(*_writePtr)++ = TChar(c);
} }
const char* c_str() const { StringInMemoryPool complete() const {
*(*_writePtr)++ = 0; *(*_writePtr)++ = 0;
return reinterpret_cast<const char*>(_startPtr); return reinterpret_cast<const char*>(_startPtr);
} }
@ -29,8 +29,8 @@ class StringMover {
StringMover(TChar* ptr) : _ptr(ptr) {} StringMover(TChar* ptr) : _ptr(ptr) {}
String startString() { StringBuilder startString() {
return String(&_ptr); return StringBuilder(&_ptr);
} }
private: private:

View File

@ -12,16 +12,16 @@ class ArduinoString {
public: public:
ArduinoString(const ::String& str) : _str(&str) {} ArduinoString(const ::String& str) : _str(&str) {}
template <typename Buffer> template <typename TMemoryPool>
const char* save(Buffer* memoryPool) const { const char* save(TMemoryPool* memoryPool) const {
if (is_null()) return NULL; if (isNull()) return NULL;
size_t n = _str->length() + 1; size_t n = _str->length() + 1;
void* dup = memoryPool->alloc(n); void* dup = memoryPool->alloc(n);
if (dup != NULL) memcpy(dup, _str->c_str(), n); if (dup != NULL) memcpy(dup, _str->c_str(), n);
return static_cast<const char*>(dup); return static_cast<const char*>(dup);
} }
bool is_null() const { bool isNull() const {
// Arduino's String::c_str() can return NULL // Arduino's String::c_str() can return NULL
return !_str->c_str(); return !_str->c_str();
} }

View File

@ -17,12 +17,12 @@ class FixedSizeFlashString {
return strncmp_P(expected, actual, _size) == 0; return strncmp_P(expected, actual, _size) == 0;
} }
bool is_null() const { bool isNull() const {
return !_str; return !_str;
} }
template <typename Buffer> template <typename TMemoryPool>
const char* save(Buffer* memoryPool) const { const char* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
void* dup = memoryPool->alloc(_size); void* dup = memoryPool->alloc(_size);
if (dup != NULL) memcpy_P(dup, (const char*)_str, _size); if (dup != NULL) memcpy_P(dup, (const char*)_str, _size);

View File

@ -18,12 +18,12 @@ class FixedSizeRamString {
return strcmp(actual, expected) == 0; return strcmp(actual, expected) == 0;
} }
bool is_null() const { bool isNull() const {
return !_str; return !_str;
} }
template <typename Buffer> template <typename TMemoryPool>
const char* save(Buffer* memoryPool) const { const char* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
void* dup = memoryPool->alloc(_size); void* dup = memoryPool->alloc(_size);
if (!dup) return NULL; if (!dup) return NULL;

View File

@ -12,15 +12,15 @@ class StlString {
public: public:
StlString(const std::string& str) : _str(&str) {} StlString(const std::string& str) : _str(&str) {}
template <typename Buffer> template <typename TMemoryPool>
const char* save(Buffer* memoryPool) const { const char* save(TMemoryPool* memoryPool) const {
size_t n = _str->length() + 1; size_t n = _str->length() + 1;
void* dup = memoryPool->alloc(n); void* dup = memoryPool->alloc(n);
if (dup != NULL) memcpy(dup, _str->c_str(), n); if (dup != NULL) memcpy(dup, _str->c_str(), n);
return static_cast<const char*>(dup); return static_cast<const char*>(dup);
} }
bool is_null() const { bool isNull() const {
return false; return false;
} }

View File

@ -0,0 +1,16 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include "ZeroTerminatedRamStringConst.hpp"
namespace ARDUINOJSON_NAMESPACE {
class StringInMemoryPool : public ZeroTerminatedRamStringConst {
public:
StringInMemoryPool(const char* str = 0) : ZeroTerminatedRamStringConst(str) {}
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -18,7 +18,9 @@ struct IsString<T&> : IsString<T> {};
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE
#include "FixedSizeRamString.hpp" #include "FixedSizeRamString.hpp"
#include "StringInMemoryPool.hpp"
#include "ZeroTerminatedRamString.hpp" #include "ZeroTerminatedRamString.hpp"
#include "ZeroTerminatedRamStringConst.hpp"
#if ARDUINOJSON_ENABLE_STD_STRING #if ARDUINOJSON_ENABLE_STD_STRING
#include "StlString.hpp" #include "StlString.hpp"

View File

@ -16,12 +16,12 @@ class ZeroTerminatedFlashString {
return strcmp_P(expected, actual) == 0; return strcmp_P(expected, actual) == 0;
} }
bool is_null() const { bool isNull() const {
return !_str; return !_str;
} }
template <typename Buffer> template <typename TMemoryPool>
const char* save(Buffer* memoryPool) const { const char* save(TMemoryPool* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
size_t n = size() + 1; // copy the terminator size_t n = size() + 1; // copy the terminator
void* dup = memoryPool->alloc(n); void* dup = memoryPool->alloc(n);

View File

@ -4,24 +4,17 @@
#pragma once #pragma once
#include "ZeroTerminatedRamStringConst.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
class ZeroTerminatedRamString { class ZeroTerminatedRamString : public ZeroTerminatedRamStringConst {
public: public:
ZeroTerminatedRamString(const char* str) : _str(str) {} ZeroTerminatedRamString(const char* str)
: ZeroTerminatedRamStringConst(str) {}
bool equals(const char* expected) const { template <typename TMemoryPool>
const char* actual = reinterpret_cast<const char*>(_str); const char* save(TMemoryPool* memoryPool) const {
if (!actual || !expected) return actual == expected;
return strcmp(actual, expected) == 0;
}
bool is_null() const {
return !_str;
}
template <typename Buffer>
const char* save(Buffer* memoryPool) const {
if (!_str) return NULL; if (!_str) return NULL;
size_t n = size() + 1; size_t n = size() + 1;
void* dup = memoryPool->alloc(n); void* dup = memoryPool->alloc(n);
@ -29,13 +22,6 @@ class ZeroTerminatedRamString {
memcpy(dup, _str, n); memcpy(dup, _str, n);
return static_cast<const char*>(dup); return static_cast<const char*>(dup);
} }
size_t size() const {
return strlen(reinterpret_cast<const char*>(_str));
}
private:
const char* _str;
}; };
template <typename TChar> template <typename TChar>
@ -43,6 +29,10 @@ inline ZeroTerminatedRamString makeString(const TChar* str) {
return ZeroTerminatedRamString(reinterpret_cast<const char*>(str)); return ZeroTerminatedRamString(reinterpret_cast<const char*>(str));
} }
inline ZeroTerminatedRamString makeString(char* str) {
return ZeroTerminatedRamString(str);
}
template <typename TChar> template <typename TChar>
struct IsString<TChar*> { struct IsString<TChar*> {
static const bool value = sizeof(TChar) == 1; static const bool value = sizeof(TChar) == 1;

View File

@ -0,0 +1,43 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include <stdlib.h> // size_t
#include <string.h> // strcmp
namespace ARDUINOJSON_NAMESPACE {
class ZeroTerminatedRamStringConst {
public:
ZeroTerminatedRamStringConst(const char* str) : _str(str) {}
bool equals(const char* expected) const {
const char* actual = _str;
if (!actual || !expected) return actual == expected;
return strcmp(actual, expected) == 0;
}
bool isNull() const {
return !_str;
}
template <typename TMemoryPool>
const char* save(TMemoryPool*) const {
return _str;
}
size_t size() const {
return strlen(_str);
}
protected:
const char* _str;
};
inline ZeroTerminatedRamStringConst makeString(const char* str) {
return ZeroTerminatedRamStringConst(str);
}
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -17,6 +17,8 @@ struct NoMemoryAllocator {
TEST_CASE("DynamicMemoryPool no memory") { TEST_CASE("DynamicMemoryPool no memory") {
DynamicMemoryPoolBase<NoMemoryAllocator> _memoryPool; DynamicMemoryPoolBase<NoMemoryAllocator> _memoryPool;
typedef DynamicMemoryPoolBase<NoMemoryAllocator>::StringBuilder StringBuilder;
SECTION("FixCodeCoverage") { SECTION("FixCodeCoverage") {
// call this function to fix code coverage // call this function to fix code coverage
NoMemoryAllocator().deallocate(NULL); NoMemoryAllocator().deallocate(NULL);
@ -33,9 +35,8 @@ TEST_CASE("DynamicMemoryPool no memory") {
// } // }
SECTION("startString()") { SECTION("startString()") {
DynamicMemoryPoolBase<NoMemoryAllocator>::String str = StringBuilder str = _memoryPool.startString();
_memoryPool.startString();
str.append('!'); str.append('!');
REQUIRE(0 == str.c_str()); REQUIRE(str.complete().isNull());
} }
} }

View File

@ -7,43 +7,45 @@
using namespace ARDUINOJSON_NAMESPACE; using namespace ARDUINOJSON_NAMESPACE;
typedef DynamicMemoryPool::StringBuilder StringBuilder;
TEST_CASE("DynamicMemoryPool::startString()") { TEST_CASE("DynamicMemoryPool::startString()") {
SECTION("WorksWhenBufferIsBigEnough") { SECTION("WorksWhenBufferIsBigEnough") {
DynamicMemoryPool memoryPool(6); DynamicMemoryPool memoryPool(6);
DynamicMemoryPool::String str = memoryPool.startString(); StringBuilder str = memoryPool.startString();
str.append('h'); str.append('h');
str.append('e'); str.append('e');
str.append('l'); str.append('l');
str.append('l'); str.append('l');
str.append('o'); str.append('o');
REQUIRE(std::string("hello") == str.c_str()); REQUIRE(str.complete().equals("hello"));
} }
SECTION("GrowsWhenBufferIsTooSmall") { SECTION("GrowsWhenBufferIsTooSmall") {
DynamicMemoryPool memoryPool(5); DynamicMemoryPool memoryPool(5);
DynamicMemoryPool::String str = memoryPool.startString(); StringBuilder str = memoryPool.startString();
str.append('h'); str.append('h');
str.append('e'); str.append('e');
str.append('l'); str.append('l');
str.append('l'); str.append('l');
str.append('o'); str.append('o');
REQUIRE(std::string("hello") == str.c_str()); REQUIRE(str.complete().equals("hello"));
} }
SECTION("SizeIncreases") { SECTION("SizeIncreases") {
DynamicMemoryPool memoryPool(5); DynamicMemoryPool memoryPool(5);
DynamicMemoryPool::String str = memoryPool.startString(); StringBuilder str = memoryPool.startString();
REQUIRE(0 == memoryPool.size()); REQUIRE(0 == memoryPool.size());
str.append('h'); str.append('h');
REQUIRE(1 == memoryPool.size()); REQUIRE(1 == memoryPool.size());
str.c_str(); str.complete();
REQUIRE(2 == memoryPool.size()); REQUIRE(2 == memoryPool.size());
} }
} }

View File

@ -4,6 +4,7 @@
add_executable(JsonObjectTests add_executable(JsonObjectTests
containsKey.cpp containsKey.cpp
copy.cpp
createNestedArray.cpp createNestedArray.cpp
createNestedObject.cpp createNestedObject.cpp
get.cpp get.cpp

59
test/JsonObject/copy.cpp Normal file
View File

@ -0,0 +1,59 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#include <ArduinoJson.h>
#include <catch.hpp>
TEST_CASE("JsonObject::copyFrom()") {
DynamicJsonDocument doc1;
DynamicJsonDocument doc2;
JsonObject obj1 = doc1.to<JsonObject>();
JsonObject obj2 = doc2.to<JsonObject>();
SECTION("doesn't copy static string in key or value") {
obj1["hello"] = "world";
obj2.copyFrom(obj1);
REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
REQUIRE(obj2["hello"] == std::string("world"));
}
SECTION("copy local string value") {
obj1["hello"] = std::string("world");
obj2.copyFrom(obj1);
REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
REQUIRE(obj2["hello"] == std::string("world"));
}
SECTION("copy local key") {
obj1[std::string("hello")] = "world";
obj2.copyFrom(obj1);
REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
REQUIRE(obj2["hello"] == std::string("world"));
}
SECTION("copy string from deserializeJson()") {
deserializeJson(doc1, "{'hello':'world'}");
obj2.copyFrom(obj1);
REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
REQUIRE(obj2["hello"] == std::string("world"));
}
SECTION("copy string from deserializeMsgPack()") {
deserializeMsgPack(doc1, "\x81\xA5hello\xA5world");
obj2.copyFrom(obj1);
REQUIRE(doc1.memoryUsage() == doc2.memoryUsage());
REQUIRE(obj2["hello"] == std::string("world"));
}
}

View File

@ -16,11 +16,11 @@ TEST_CASE("JsonObject::begin()/end()") {
SECTION("NonConstIterator") { SECTION("NonConstIterator") {
JsonObject::iterator it = obj.begin(); JsonObject::iterator it = obj.begin();
REQUIRE(obj.end() != it); REQUIRE(obj.end() != it);
REQUIRE_THAT(it->key(), Equals("ab")); REQUIRE(it->key() == "ab");
REQUIRE(12 == it->value()); REQUIRE(12 == it->value());
++it; ++it;
REQUIRE(obj.end() != it); REQUIRE(obj.end() != it);
REQUIRE_THAT(it->key(), Equals("cd")); REQUIRE(it->key() == "cd");
REQUIRE(34 == it->value()); REQUIRE(34 == it->value());
++it; ++it;
REQUIRE(obj.end() == it); REQUIRE(obj.end() == it);
@ -42,7 +42,7 @@ TEST_CASE("JsonObject::begin()/end()") {
// } // }
SECTION("Dereferencing end() is safe") { SECTION("Dereferencing end() is safe") {
REQUIRE(obj.end()->key() == 0); REQUIRE(obj.end()->key().isNull());
REQUIRE(obj.end()->value().isNull()); REQUIRE(obj.end()->value().isNull());
} }
} }

View File

@ -13,21 +13,21 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
SECTION("stores JsonArray by copy") { SECTION("stores JsonArray by copy") {
JsonArray arr = doc2.to<JsonArray>(); JsonArray arr = doc2.to<JsonArray>();
arr.add(42); arr.add(42);
var1.set(doc2.as<JsonVariant>());
arr[0] = 666;
var1.set(arr);
arr[0] = 666;
REQUIRE(var1.as<std::string>() == "[42]"); REQUIRE(var1.as<std::string>() == "[42]");
} }
SECTION("stores JsonObject by copy") { SECTION("stores JsonObject by copy") {
JsonObject obj = doc2.to<JsonObject>(); JsonObject obj = doc2.to<JsonObject>();
obj["value"] = 42; obj["value"] = 42;
var1.set(doc2.as<JsonVariant>());
obj["value"] = 666;
var1.set(obj);
obj["value"] = 666;
REQUIRE(var1.as<std::string>() == "{\"value\":42}"); REQUIRE(var1.as<std::string>() == "{\"value\":42}");
} }

View File

@ -8,42 +8,44 @@
using namespace ARDUINOJSON_NAMESPACE; using namespace ARDUINOJSON_NAMESPACE;
TEST_CASE("StaticMemoryPool::startString()") { TEST_CASE("StaticMemoryPool::startString()") {
typedef StaticMemoryPoolBase::StringBuilder StringBuilder;
SECTION("WorksWhenBufferIsBigEnough") { SECTION("WorksWhenBufferIsBigEnough") {
StaticMemoryPool<6> memoryPool; StaticMemoryPool<6> memoryPool;
StaticMemoryPoolBase::String str = memoryPool.startString(); StringBuilder str = memoryPool.startString();
str.append('h'); str.append('h');
str.append('e'); str.append('e');
str.append('l'); str.append('l');
str.append('l'); str.append('l');
str.append('o'); str.append('o');
REQUIRE(std::string("hello") == str.c_str()); REQUIRE(str.complete().equals("hello"));
} }
SECTION("ReturnsNullWhenTooSmall") { SECTION("ReturnsNullWhenTooSmall") {
StaticMemoryPool<5> memoryPool; StaticMemoryPool<5> memoryPool;
StaticMemoryPoolBase::String str = memoryPool.startString(); StringBuilder str = memoryPool.startString();
str.append('h'); str.append('h');
str.append('e'); str.append('e');
str.append('l'); str.append('l');
str.append('l'); str.append('l');
str.append('o'); str.append('o');
REQUIRE(0 == str.c_str()); REQUIRE(str.complete().isNull());
} }
SECTION("SizeIncreases") { SECTION("SizeIncreases") {
StaticMemoryPool<5> memoryPool; StaticMemoryPool<5> memoryPool;
StaticMemoryPoolBase::String str = memoryPool.startString(); StringBuilder str = memoryPool.startString();
REQUIRE(0 == memoryPool.size()); REQUIRE(0 == memoryPool.size());
str.append('h'); str.append('h');
REQUIRE(1 == memoryPool.size()); REQUIRE(1 == memoryPool.size());
str.c_str(); str.complete();
REQUIRE(2 == memoryPool.size()); REQUIRE(2 == memoryPool.size());
} }
} }