Decouple VariantData from MemoryPool

This commit is contained in:
Benoit Blanchon
2023-04-17 10:41:37 +02:00
parent 30c111fd3d
commit b7c8e0d25c
17 changed files with 395 additions and 266 deletions

View File

@ -10,6 +10,8 @@
#include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofArray;
using ArduinoJson::detail::sizeofObject;
using ArduinoJson::detail::sizeofString;
@ -388,3 +390,19 @@ TEST_CASE("Deduplicate keys") {
CHECK(key1 == key2);
}
}
TEST_CASE("MemberProxy under memory constraints") {
ControllableAllocator allocator;
JsonDocument doc(4096, &allocator);
SECTION("key allocation fails") {
allocator.disable();
doc[std::string("hello")] = "world";
REQUIRE(doc.is<JsonObject>());
REQUIRE(doc.size() == 0);
REQUIRE(doc.memoryUsage() == sizeofObject(1));
REQUIRE(doc.overflowed() == true);
}
}

View File

@ -4,12 +4,15 @@
#include <ArduinoJson.h>
#include <catch.hpp>
#include "Allocators.hpp"
using ArduinoJson::detail::sizeofString;
TEST_CASE("JsonVariant::set(JsonVariant)") {
JsonDocument doc1(4096);
JsonDocument doc2(4096);
ControllableAllocator allocator;
SpyingAllocator spyingAllocator(&allocator);
JsonDocument doc1(4096, &spyingAllocator);
JsonDocument doc2(4096, &spyingAllocator);
JsonVariant var1 = doc1.to<JsonVariant>();
JsonVariant var2 = doc2.to<JsonVariant>();
@ -37,53 +40,103 @@ TEST_CASE("JsonVariant::set(JsonVariant)") {
SECTION("stores const char* by reference") {
var1.set("hello!!");
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == 0);
REQUIRE(doc2.memoryUsage() == 0);
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("stores char* by copy") {
char str[] = "hello!!";
var1.set(str);
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == sizeofString(7));
REQUIRE(doc2.memoryUsage() == sizeofString(7));
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString((7))));
}
SECTION("fails gracefully if string allocation fails") {
char str[] = "hello!!";
var1.set(str);
allocator.disable();
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == sizeofString(7));
REQUIRE(doc2.memoryUsage() == 0);
REQUIRE(doc2.overflowed() == true);
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::AllocateFail(sizeofString((7))));
}
SECTION("stores std::string by copy") {
var1.set(std::string("hello!!"));
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == sizeofString(7));
REQUIRE(doc2.memoryUsage() == sizeofString(7));
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString((7))));
}
SECTION("stores Serialized<const char*> by reference") {
var1.set(serialized("hello!!", 8));
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == 0);
REQUIRE(doc2.memoryUsage() == 0);
REQUIRE(spyingAllocator.log() == AllocatorLog());
}
SECTION("stores Serialized<char*> by copy") {
char str[] = "hello!!";
var1.set(serialized(str, 7));
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == sizeofString(7));
REQUIRE(doc2.memoryUsage() == sizeofString(7));
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString((7))));
}
SECTION("stores Serialized<std::string> by copy") {
var1.set(serialized(std::string("hello!!")));
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == sizeofString(7));
REQUIRE(doc2.memoryUsage() == sizeofString(7));
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::Allocate(sizeofString((7))));
}
SECTION("fails gracefully if raw string allocation fails") {
var1.set(serialized(std::string("hello!!")));
allocator.disable();
spyingAllocator.clearLog();
var2.set(var1);
REQUIRE(doc1.memoryUsage() == sizeofString(7));
REQUIRE(doc2.memoryUsage() == 0);
REQUIRE(doc2.overflowed() == true);
REQUIRE(spyingAllocator.log() ==
AllocatorLog() << AllocatorLog::AllocateFail(sizeofString((7))));
}
SECTION("destination is unbound") {

View File

@ -6,6 +6,7 @@
#include <ArduinoJson/Array/ElementProxy.hpp>
#include <ArduinoJson/Array/JsonArrayConst.hpp>
#include <ArduinoJson/Collection/CollectionFunctions.hpp>
ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE
@ -43,9 +44,7 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// Returns a reference to the new element.
// https://arduinojson.org/v6/api/jsonarray/add/
JsonVariant add() const {
if (!_data)
return JsonVariant();
return JsonVariant(_pool, _data->addElement(_pool));
return JsonVariant(_pool, collectionAddElement(_data, _pool));
}
// Appends a value to the array.
@ -79,9 +78,7 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// Copies an array.
// https://arduinojson.org/v6/api/jsonarray/set/
FORCE_INLINE bool set(JsonArrayConst src) const {
if (!_data || !src._data)
return false;
return _data->copyFrom(*src._data, _pool);
return collectionCopy(_data, src._data, _pool);
}
// Compares the content of two arrays.
@ -93,27 +90,21 @@ class JsonArray : public detail::VariantOperators<JsonArray> {
// ⚠️ Doesn't release the memory associated with the removed element.
// https://arduinojson.org/v6/api/jsonarray/remove/
FORCE_INLINE void remove(iterator it) const {
if (!_data)
return;
_data->removeSlot(it._slot);
collectionRemove(_data, it._slot, _pool);
}
// Removes the element at the specified index.
// ⚠️ Doesn't release the memory associated with the removed element.
// https://arduinojson.org/v6/api/jsonarray/remove/
FORCE_INLINE void remove(size_t index) const {
if (!_data)
return;
_data->removeElement(index);
collectionRemoveElement(_data, index, _pool);
}
// Removes all the elements of the array.
// ⚠️ Doesn't release the memory associated with the removed elements.
// https://arduinojson.org/v6/api/jsonarray/clear/
void clear() const {
if (!_data)
return;
_data->clear();
collectionClear(_data, _pool);
}
// Gets or sets the element at the specified index.

View File

@ -11,7 +11,6 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
class MemoryPool;
class VariantData;
class VariantSlot;
@ -28,30 +27,13 @@ class CollectionData {
// Array only
VariantData* addElement(MemoryPool* pool);
VariantData* getElement(size_t index) const;
VariantData* getOrAddElement(size_t index, MemoryPool* pool);
void removeElement(size_t index);
// Object only
template <typename TAdaptedString>
VariantData* addMember(TAdaptedString key, MemoryPool* pool);
template <typename TAdaptedString>
VariantData* getMember(TAdaptedString key) const;
template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key, MemoryPool* pool);
template <typename TAdaptedString>
void removeMember(TAdaptedString key) {
removeSlot(getSlot(key));
}
template <typename TAdaptedString>
bool containsKey(const TAdaptedString& key) const;
@ -61,23 +43,21 @@ class CollectionData {
size_t memoryUsage() const;
size_t size() const;
VariantSlot* addSlot(MemoryPool*);
void addSlot(VariantSlot*);
void removeSlot(VariantSlot* slot);
bool copyFrom(const CollectionData& src, MemoryPool* pool);
VariantSlot* head() const {
return _head;
}
void movePointers(ptrdiff_t variantDistance);
private:
VariantSlot* getSlot(size_t index) const;
template <typename TAdaptedString>
VariantSlot* getSlot(TAdaptedString key) const;
private:
VariantSlot* getPreviousSlot(VariantSlot*) const;
};

View File

@ -0,0 +1,93 @@
// ArduinoJson - https://arduinojson.org
// Copyright © 2014-2023, Benoit BLANCHON
// MIT License
#pragma once
#include <ArduinoJson/Collection/CollectionData.hpp>
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline VariantData* collectionAddElement(CollectionData* array,
MemoryPool* pool) {
if (!array)
return nullptr;
auto slot = pool->allocVariant();
if (!slot)
return nullptr;
array->addSlot(slot);
return slot->data();
}
template <typename TAdaptedString>
inline VariantData* collectionAddMember(CollectionData* obj, TAdaptedString key,
MemoryPool* pool) {
ARDUINOJSON_ASSERT(!key.isNull());
ARDUINOJSON_ASSERT(obj != nullptr);
if (!obj)
return nullptr;
auto slot = pool->allocVariant();
if (!slot)
return nullptr;
auto storedKey = storeString(pool, key);
if (!storedKey)
return nullptr;
slot->setKey(storedKey);
obj->addSlot(slot);
return slot->data();
}
inline void collectionClear(CollectionData* c, MemoryPool* pool) {
if (!c)
return;
for (auto slot = c->head(); slot; slot = slot->next())
slotRelease(slot, pool);
c->clear();
}
inline bool collectionCopy(CollectionData* dst, const CollectionData* src,
MemoryPool* pool) {
if (!dst || !src)
return false;
collectionClear(dst, pool);
for (VariantSlot* s = src->head(); s; s = s->next()) {
VariantData* var;
if (s->key() != 0) {
JsonString key(s->key(),
s->ownsKey() ? JsonString::Copied : JsonString::Linked);
var = collectionAddMember(dst, adaptString(key), pool);
} else {
var = collectionAddElement(dst, pool);
}
if (!variantCopyFrom(var, s->data(), pool))
return false;
}
return true;
}
inline void collectionRemove(CollectionData* data, VariantSlot* slot,
MemoryPool* pool) {
if (!data || !slot)
return;
data->removeSlot(slot);
slotRelease(slot, pool);
}
inline void collectionRemoveElement(CollectionData* array, size_t index,
MemoryPool* pool) {
if (!array)
return;
collectionRemove(array, array->getSlot(index), pool);
}
template <typename TAdaptedString>
inline void collectionRemoveMember(CollectionData* obj, TAdaptedString key,
MemoryPool* pool) {
if (!obj)
return;
collectionRemove(obj, obj->getSlot(key), pool);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -11,41 +11,16 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
inline VariantSlot* CollectionData::addSlot(MemoryPool* pool) {
VariantSlot* slot = pool->allocVariant();
if (!slot)
return 0;
inline void CollectionData::addSlot(VariantSlot* slot) {
ARDUINOJSON_ASSERT(slot != nullptr);
if (_tail) {
ARDUINOJSON_ASSERT(pool->owns(_tail)); // Can't alter a linked array/object
_tail->setNextNotNull(slot);
_tail = slot;
} else {
_head = slot;
_tail = slot;
}
slot->clear();
return slot;
}
inline VariantData* CollectionData::addElement(MemoryPool* pool) {
return slotData(addSlot(pool));
}
template <typename TAdaptedString>
inline VariantData* CollectionData::addMember(TAdaptedString key,
MemoryPool* pool) {
VariantSlot* slot = addSlot(pool);
if (!slot)
return 0;
auto storedKey = storeString(pool, key);
if (!storedKey) {
removeSlot(slot);
return 0;
}
slot->setKey(storedKey);
return slot->data();
}
inline void CollectionData::clear() {
@ -58,26 +33,6 @@ inline bool CollectionData::containsKey(const TAdaptedString& key) const {
return getSlot(key) != 0;
}
inline bool CollectionData::copyFrom(const CollectionData& src,
MemoryPool* pool) {
clear();
for (VariantSlot* s = src._head; s; s = s->next()) {
VariantData* var;
if (s->key() != 0) {
JsonString key(s->key(),
s->ownsKey() ? JsonString::Copied : JsonString::Linked);
var = addMember(adaptString(key), pool);
} else {
var = addElement(pool);
}
if (!var)
return false;
if (!var->copyFrom(*s->data(), pool))
return false;
}
return true;
}
template <typename TAdaptedString>
inline VariantSlot* CollectionData::getSlot(TAdaptedString key) const {
if (key.isNull())
@ -114,42 +69,11 @@ inline VariantData* CollectionData::getMember(TAdaptedString key) const {
return slot ? slot->data() : 0;
}
template <typename TAdaptedString>
inline VariantData* CollectionData::getOrAddMember(TAdaptedString key,
MemoryPool* pool) {
// ignore null key
if (key.isNull())
return 0;
// search a matching key
VariantSlot* slot = getSlot(key);
if (slot)
return slot->data();
return addMember(key, pool);
}
inline VariantData* CollectionData::getElement(size_t index) const {
VariantSlot* slot = getSlot(index);
return slot ? slot->data() : 0;
}
inline VariantData* CollectionData::getOrAddElement(size_t index,
MemoryPool* pool) {
VariantSlot* slot = _head;
while (slot && index > 0) {
slot = slot->next();
index--;
}
if (!slot)
index++;
while (index > 0) {
slot = addSlot(pool);
index--;
}
return slotData(slot);
}
inline void CollectionData::removeSlot(VariantSlot* slot) {
if (!slot)
return;
@ -163,10 +87,6 @@ inline void CollectionData::removeSlot(VariantSlot* slot) {
_tail = prev;
}
inline void CollectionData::removeElement(size_t index) {
removeSlot(getSlot(index));
}
inline size_t CollectionData::memoryUsage() const {
size_t total = 0;
for (VariantSlot* s = _head; s; s = s->next()) {

View File

@ -119,7 +119,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// https://arduinojson.org/v6/api/jsondocument/clear/
void clear() {
_pool.clear();
_data.setNull();
_data.reset();
}
// Returns true if the root is of the specified type.
@ -297,7 +297,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// Returns a reference to the new element.
// https://arduinojson.org/v6/api/jsondocument/add/
FORCE_INLINE JsonVariant add() {
return JsonVariant(&_pool, _data.addElement(&_pool));
return JsonVariant(&_pool, variantAddElement(&_data, &_pool));
}
// Appends a value to the root array.
@ -318,7 +318,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
// ⚠️ Doesn't release the memory associated with the removed element.
// https://arduinojson.org/v6/api/jsondocument/remove/
FORCE_INLINE void remove(size_t index) {
_data.remove(index);
variantRemoveElement(getData(), index, getPool());
}
// Removes a member of the root object.
@ -327,7 +327,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
template <typename TChar>
FORCE_INLINE typename detail::enable_if<detail::IsString<TChar*>::value>::type
remove(TChar* key) {
_data.remove(detail::adaptString(key));
variantRemoveMember(getData(), detail::adaptString(key), getPool());
}
// Removes a member of the root object.
@ -337,7 +337,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
FORCE_INLINE
typename detail::enable_if<detail::IsString<TString>::value>::type
remove(const TString& key) {
_data.remove(detail::adaptString(key));
variantRemoveMember(getData(), detail::adaptString(key), getPool());
}
FORCE_INLINE operator JsonVariant() {
@ -364,7 +364,7 @@ class JsonDocument : public detail::VariantOperators<const JsonDocument&> {
void moveAssignFrom(JsonDocument& src) {
_data = src._data;
src._data.setNull();
src._data.reset();
_pool = move(src._pool);
}

View File

@ -173,7 +173,7 @@ class JsonDeserializer {
for (;;) {
if (memberFilter.allow()) {
// Allocate slot in array
VariantData* value = array.addElement(_pool);
VariantData* value = collectionAddElement(&array, _pool);
if (!value)
return DeserializationError::NoMemory;
@ -280,11 +280,12 @@ class JsonDeserializer {
key = _stringStorage.save();
// Allocate slot in object
VariantSlot* slot = object.addSlot(_pool);
VariantSlot* slot = _pool->allocVariant();
if (!slot)
return DeserializationError::NoMemory;
slot->setKey(key);
object.addSlot(slot);
variant = slot->data();
}

View File

@ -99,7 +99,10 @@ class MemoryPool {
}
VariantSlot* allocVariant() {
return allocRight<VariantSlot>();
auto slot = allocRight<VariantSlot>();
if (slot)
slot->clear();
return slot;
}
template <typename TAdaptedString>

View File

@ -435,7 +435,7 @@ class MsgPackDeserializer {
if (memberFilter.allow()) {
ARDUINOJSON_ASSERT(array != 0);
value = array->addElement(_pool);
value = collectionAddElement(array, _pool);
if (!value)
return DeserializationError::NoMemory;
} else {
@ -496,11 +496,12 @@ class MsgPackDeserializer {
// Save key in memory pool.
key = _stringStorage.save();
VariantSlot* slot = object->addSlot(_pool);
VariantSlot* slot = _pool->allocVariant();
if (!slot)
return DeserializationError::NoMemory;
slot->setKey(key);
object->addSlot(slot);
member = slot->data();
} else {

View File

@ -87,17 +87,13 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// ⚠️ Doesn't release the memory associated with the removed members.
// https://arduinojson.org/v6/api/jsonobject/clear/
void clear() const {
if (!_data)
return;
_data->clear();
collectionClear(_data, _pool);
}
// Copies an object.
// https://arduinojson.org/v6/api/jsonobject/set/
FORCE_INLINE bool set(JsonObjectConst src) {
if (!_data || !src._data)
return false;
return _data->copyFrom(*src._data, _pool);
return collectionCopy(_data, src._data, _pool);
}
// Compares the content of two objects.
@ -129,9 +125,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// ⚠️ Doesn't release the memory associated with the removed member.
// https://arduinojson.org/v6/api/jsonobject/remove/
FORCE_INLINE void remove(iterator it) const {
if (!_data)
return;
_data->removeSlot(it._slot);
collectionRemove(_data, it._slot, _pool);
}
// Removes the member with the specified key.
@ -139,7 +133,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// https://arduinojson.org/v6/api/jsonobject/remove/
template <typename TString>
FORCE_INLINE void remove(const TString& key) const {
removeMember(detail::adaptString(key));
collectionRemoveMember(_data, detail::adaptString(key), _pool);
}
// Removes the member with the specified key.
@ -147,7 +141,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
// https://arduinojson.org/v6/api/jsonobject/remove/
template <typename TChar>
FORCE_INLINE void remove(TChar* key) const {
removeMember(detail::adaptString(key));
collectionRemoveMember(_data, detail::adaptString(key), _pool);
}
// Returns true if the object contains the specified key.
@ -214,9 +208,7 @@ class JsonObject : public detail::VariantOperators<JsonObject> {
template <typename TAdaptedString>
void removeMember(TAdaptedString key) const {
if (!_data)
return;
_data->removeMember(key);
collectionRemove(_data, _data->getSlot(key), _pool);
}
detail::CollectionData* _data;

View File

@ -42,10 +42,8 @@ struct Converter<
!detail::is_same<char, T>::value>::type>
: private detail::VariantAttorney {
static void toJson(T src, JsonVariant dst) {
auto data = getData(dst);
ARDUINOJSON_ASSERT_INTEGER_TYPE_IS_SUPPORTED(T);
if (data)
data->setInteger(src);
variantSetInteger(getData(dst), src, getPool(dst));
}
static T fromJson(JsonVariantConst src) {
@ -81,9 +79,7 @@ struct Converter<T, typename detail::enable_if<detail::is_enum<T>::value>::type>
template <>
struct Converter<bool> : private detail::VariantAttorney {
static void toJson(bool src, JsonVariant dst) {
auto data = getData(dst);
if (data)
data->setBoolean(src);
variantSetBoolean(getData(dst), src, getPool(dst));
}
static bool fromJson(JsonVariantConst src) {
@ -102,9 +98,7 @@ struct Converter<
T, typename detail::enable_if<detail::is_floating_point<T>::value>::type>
: private detail::VariantAttorney {
static void toJson(T src, JsonVariant dst) {
auto data = getData(dst);
if (data)
data->setFloat(static_cast<JsonFloat>(src));
variantSetFloat(getData(dst), static_cast<JsonFloat>(src), getPool(dst));
}
static T fromJson(JsonVariantConst src) {
@ -165,9 +159,7 @@ template <>
struct Converter<SerializedValue<const char*>>
: private detail::VariantAttorney {
static void toJson(SerializedValue<const char*> src, JsonVariant dst) {
auto data = getData(dst);
if (data)
data->setLinkedRaw(src);
variantSetLinkedRaw(getData(dst), src, getPool(dst));
}
};
@ -180,17 +172,14 @@ struct Converter<
typename detail::enable_if<!detail::is_same<const char*, T>::value>::type>
: private detail::VariantAttorney {
static void toJson(SerializedValue<T> src, JsonVariant dst) {
auto data = getData(dst);
auto pool = getPool(dst);
if (data)
data->storeOwnedRaw(src, pool);
variantSetOwnedRaw(getData(dst), src, getPool(dst));
}
};
template <>
struct Converter<decltype(nullptr)> : private detail::VariantAttorney {
static void toJson(decltype(nullptr), JsonVariant dst) {
variantSetNull(getData(dst));
variantSetNull(getData(dst), getPool(dst));
}
static decltype(nullptr) fromJson(JsonVariantConst) {
return nullptr;

View File

@ -21,4 +21,10 @@ inline size_t slotSize(const VariantSlot* var) {
inline VariantData* slotData(VariantSlot* slot) {
return reinterpret_cast<VariantData*>(slot);
}
inline void slotRelease(const VariantSlot* slot, MemoryPool* pool) {
ARDUINOJSON_ASSERT(slot != nullptr);
variantRelease(slot->data(), pool);
}
ARDUINOJSON_END_PRIVATE_NAMESPACE

View File

@ -4,7 +4,6 @@
#pragma once
#include <ArduinoJson/Memory/MemoryPool.hpp>
#include <ArduinoJson/Misc/SerializedValue.hpp>
#include <ArduinoJson/Numbers/convertNumber.hpp>
#include <ArduinoJson/Strings/JsonString.hpp>
@ -20,6 +19,10 @@ class VariantData {
public:
VariantData() : _flags(VALUE_IS_NULL) {}
void reset() {
_flags = VALUE_IS_NULL;
}
void operator=(const VariantData& src) {
_content = src._content;
_flags = uint8_t((_flags & OWNED_KEY_BIT) | (src._flags & ~OWNED_KEY_BIT));
@ -68,9 +71,17 @@ class VariantData {
T asFloat() const;
JsonString asString() const;
JsonString asRaw() const;
bool asBoolean() const;
const char* getOwnedString() const {
if (_flags & OWNED_VALUE_BIT)
return _content.asString.data;
else
return nullptr;
}
CollectionData* asArray() {
return isArray() ? &_content.asCollection : 0;
}
@ -91,8 +102,6 @@ class VariantData {
return const_cast<VariantData*>(this)->asObject();
}
bool copyFrom(const VariantData& src, MemoryPool* pool);
bool isArray() const {
return (_flags & VALUE_IS_ARRAY) != 0;
}
@ -139,17 +148,6 @@ class VariantData {
return !isFloat();
}
void remove(size_t index) {
if (isArray())
_content.asCollection.removeElement(index);
}
template <typename TAdaptedString>
void remove(TAdaptedString key) {
if (isObject())
_content.asCollection.removeMember(key);
}
void setBoolean(bool value) {
setType(VALUE_IS_BOOLEAN);
_content.asBoolean = value;
@ -160,28 +158,16 @@ class VariantData {
_content.asFloat = value;
}
void setLinkedRaw(SerializedValue<const char*> value) {
if (value.data()) {
setType(VALUE_IS_LINKED_RAW);
_content.asString.data = value.data();
_content.asString.size = value.size();
} else {
setType(VALUE_IS_NULL);
}
void setLinkedRaw(const char* data, size_t n) {
setType(VALUE_IS_LINKED_RAW);
_content.asString.data = data;
_content.asString.size = n;
}
template <typename T>
bool storeOwnedRaw(SerializedValue<T> value, MemoryPool* pool) {
const char* dup = pool->saveString(adaptString(value.data(), value.size()));
if (dup) {
setType(VALUE_IS_OWNED_RAW);
_content.asString.data = dup;
_content.asString.size = value.size();
return true;
} else {
setType(VALUE_IS_NULL);
return false;
}
void setOwnedRaw(const char* data, size_t n) {
setType(VALUE_IS_OWNED_RAW);
_content.asString.data = data;
_content.asString.size = n;
}
template <typename T>
@ -239,42 +225,17 @@ class VariantData {
return isCollection() ? _content.asCollection.size() : 0;
}
VariantData* addElement(MemoryPool* pool) {
if (isNull())
toArray();
if (!isArray())
return 0;
return _content.asCollection.addElement(pool);
}
VariantData* getElement(size_t index) const {
const CollectionData* col = asArray();
return col ? col->getElement(index) : 0;
}
VariantData* getOrAddElement(size_t index, MemoryPool* pool) {
if (isNull())
toArray();
if (!isArray())
return 0;
return _content.asCollection.getOrAddElement(index, pool);
}
template <typename TAdaptedString>
VariantData* getMember(TAdaptedString key) const {
const CollectionData* col = asObject();
return col ? col->getMember(key) : 0;
}
template <typename TAdaptedString>
VariantData* getOrAddMember(TAdaptedString key, MemoryPool* pool) {
if (isNull())
toObject();
if (!isObject())
return 0;
return _content.asCollection.getOrAddMember(key, pool);
}
void movePointers(ptrdiff_t variantDistance) {
if (_flags & COLLECTION_MASK)
_content.asCollection.movePointers(variantDistance);

View File

@ -4,6 +4,7 @@
#pragma once
#include <ArduinoJson/Polyfills/assert.hpp>
#include <ArduinoJson/Polyfills/attributes.hpp>
#include <ArduinoJson/Strings/StoragePolicy.hpp>
#include <ArduinoJson/Variant/VariantData.hpp>
@ -11,6 +12,16 @@
ARDUINOJSON_BEGIN_PRIVATE_NAMESPACE
void slotRelease(const VariantSlot* slot, MemoryPool* pool);
bool collectionCopy(CollectionData* dst, const CollectionData* src,
MemoryPool* pool);
VariantData* collectionAddElement(CollectionData* array, MemoryPool* pool);
void collectionRemoveElement(CollectionData* data, size_t index,
MemoryPool* pool);
template <typename TAdaptedString>
void collectionRemoveMember(CollectionData* data, TAdaptedString key,
MemoryPool* pool);
template <typename TVisitor>
inline typename TVisitor::result_type variantAccept(const VariantData* var,
TVisitor& visitor) {
@ -20,6 +31,15 @@ inline typename TVisitor::result_type variantAccept(const VariantData* var,
return visitor.visitNull();
}
inline void variantRelease(const VariantData* var, MemoryPool* pool) {
ARDUINOJSON_ASSERT(var != nullptr);
auto c = var->asCollection();
if (c) {
for (auto slot = c->head(); slot; slot = slot->next())
slotRelease(slot, pool);
}
}
inline bool variantCopyFrom(VariantData* dst, const VariantData* src,
MemoryPool* pool) {
if (!dst)
@ -28,20 +48,69 @@ inline bool variantCopyFrom(VariantData* dst, const VariantData* src,
dst->setNull();
return true;
}
return dst->copyFrom(*src, pool);
switch (src->type()) {
case VALUE_IS_ARRAY:
return collectionCopy(&dst->toArray(), src->asArray(), pool);
case VALUE_IS_OBJECT:
return collectionCopy(&dst->toObject(), src->asObject(), pool);
case VALUE_IS_OWNED_STRING: {
auto str = adaptString(src->asString());
auto dup = storeString(pool, str, StringStoragePolicy::Copy());
if (!dup)
return false;
dst->setString(dup);
return true;
}
case VALUE_IS_OWNED_RAW: {
auto str = adaptString(src->asRaw());
auto dup = storeString(pool, str, StringStoragePolicy::Copy());
if (!dup)
return false;
dst->setOwnedRaw(dup.c_str(), str.size());
return true;
}
default:
*dst = *src;
return true;
}
}
inline void variantSetNull(VariantData* var) {
inline void variantSetNull(VariantData* var, MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
var->setNull();
}
inline void variantSetBoolean(VariantData* var, bool value, MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
var->setBoolean(value);
}
inline void variantSetFloat(VariantData* var, JsonFloat value,
MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
var->setFloat(value);
}
template <typename T>
inline void variantSetInteger(VariantData* var, T value, MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
var->setInteger(value);
}
template <typename TAdaptedString>
inline void variantSetString(VariantData* var, TAdaptedString value,
MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
JsonString str = storeString(pool, value);
if (str)
var->setString(str);
@ -49,19 +118,46 @@ inline void variantSetString(VariantData* var, TAdaptedString value,
var->setNull();
}
template <typename T>
inline void variantSetOwnedRaw(VariantData* var, SerializedValue<T> value,
MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
const char* dup = pool->saveString(adaptString(value.data(), value.size()));
if (dup)
var->setOwnedRaw(dup, value.size());
else
var->setNull();
}
inline void variantSetLinkedRaw(VariantData* var,
SerializedValue<const char*> value,
MemoryPool* pool) {
if (!var)
return;
variantRelease(var, pool);
if (value.data())
var->setLinkedRaw(value.data(), value.size());
else
var->setNull();
}
inline size_t variantSize(const VariantData* var) {
return var != 0 ? var->size() : 0;
}
inline CollectionData* variantToArray(VariantData* var) {
inline CollectionData* variantToArray(VariantData* var, MemoryPool* pool) {
if (!var)
return 0;
variantRelease(var, pool);
return &var->toArray();
}
inline CollectionData* variantToObject(VariantData* var) {
inline CollectionData* variantToObject(VariantData* var, MemoryPool* pool) {
if (!var)
return 0;
variantRelease(var, pool);
return &var->toObject();
}
@ -69,15 +165,43 @@ inline VariantData* variantGetElement(const VariantData* var, size_t index) {
return var != 0 ? var->getElement(index) : 0;
}
inline NO_INLINE VariantData* variantAddElement(VariantData* var,
MemoryPool* pool) {
return var != 0 ? var->addElement(pool) : 0;
inline VariantData* variantAddElement(VariantData* var, MemoryPool* pool) {
if (!var)
return nullptr;
auto array = var->isNull() ? &var->toArray() : var->asArray();
return collectionAddElement(array, pool);
}
inline NO_INLINE VariantData* variantGetOrAddElement(VariantData* var,
size_t index,
MemoryPool* pool) {
return var != 0 ? var->getOrAddElement(index, pool) : 0;
if (!var)
return nullptr;
auto array = var->isNull() ? &var->toArray() : var->asArray();
if (!array)
return nullptr;
VariantSlot* slot = array->head();
while (slot && index > 0) {
slot = slot->next();
index--;
}
if (!slot)
index++;
while (index > 0) {
slot = pool->allocVariant();
if (!slot)
return nullptr;
array->addSlot(slot);
index--;
}
return slot->data();
}
inline void variantRemoveElement(VariantData* var, size_t index,
MemoryPool* pool) {
if (!var)
return;
collectionRemoveElement(var->asArray(), index, pool);
}
template <typename TAdaptedString>
@ -90,9 +214,23 @@ VariantData* variantGetMember(const VariantData* var, TAdaptedString key) {
template <typename TAdaptedString>
VariantData* variantGetOrAddMember(VariantData* var, TAdaptedString key,
MemoryPool* pool) {
if (!var || key.isNull())
return nullptr;
auto obj = var->isNull() ? &var->toObject() : var->asObject();
if (!obj)
return nullptr;
auto slot = obj->getSlot(key);
if (slot)
return slot->data();
return collectionAddMember(obj, key, pool);
}
template <typename TAdaptedString>
void variantRemoveMember(VariantData* var, TAdaptedString key,
MemoryPool* pool) {
if (!var)
return 0;
return var->getOrAddMember(key, pool);
return;
collectionRemoveMember(var->asObject(), key, pool);
}
inline bool variantIsNull(const VariantData* var) {

View File

@ -83,26 +83,16 @@ inline JsonString VariantData::asString() const {
}
}
inline bool VariantData::copyFrom(const VariantData& src, MemoryPool* pool) {
switch (src.type()) {
case VALUE_IS_ARRAY:
return toArray().copyFrom(src._content.asCollection, pool);
case VALUE_IS_OBJECT:
return toObject().copyFrom(src._content.asCollection, pool);
case VALUE_IS_OWNED_STRING: {
auto str = storeString(pool, adaptString(src.asString()),
StringStoragePolicy::Copy());
setString(str);
return !str.isNull();
}
inline JsonString VariantData::asRaw() const {
switch (type()) {
case VALUE_IS_LINKED_RAW:
return JsonString(_content.asString.data, _content.asString.size,
JsonString::Linked);
case VALUE_IS_OWNED_RAW:
return storeOwnedRaw(
serialized(src._content.asString.data, src._content.asString.size),
pool);
return JsonString(_content.asString.data, _content.asString.size,
JsonString::Copied);
default:
setType(src.type());
_content = src._content;
return true;
return JsonString();
}
}
@ -126,21 +116,21 @@ template <typename TDerived>
template <typename T>
inline typename enable_if<is_same<T, JsonArray>::value, JsonArray>::type
VariantRefBase<TDerived>::to() const {
return JsonArray(getPool(), variantToArray(getOrCreateData()));
return JsonArray(getPool(), variantToArray(getOrCreateData(), getPool()));
}
template <typename TDerived>
template <typename T>
typename enable_if<is_same<T, JsonObject>::value, JsonObject>::type
VariantRefBase<TDerived>::to() const {
return JsonObject(getPool(), variantToObject(getOrCreateData()));
return JsonObject(getPool(), variantToObject(getOrCreateData(), getPool()));
}
template <typename TDerived>
template <typename T>
typename enable_if<is_same<T, JsonVariant>::value, JsonVariant>::type
VariantRefBase<TDerived>::to() const {
variantSetNull(getOrCreateData());
variantSetNull(getOrCreateData(), getPool());
return *this;
}

View File

@ -30,7 +30,7 @@ class VariantRefBase : public VariantTag {
// ⚠️ Doesn't release the memory associated with the previous value.
// https://arduinojson.org/v6/api/jsonvariant/clear/
FORCE_INLINE void clear() const {
variantSetNull(getData());
variantSetNull(getOrCreateData(), getPool());
}
// Returns true if the value is null or the reference is unbound.
@ -112,11 +112,10 @@ class VariantRefBase : public VariantTag {
VariantData* data = getOrCreateData();
if (!data)
return;
variantSetNull(data, getPool());
const VariantData* targetData = VariantAttorney::getData(target);
if (targetData)
*data = *targetData;
else
data->setNull();
}
// Copies the specified value.
@ -179,9 +178,7 @@ class VariantRefBase : public VariantTag {
// ⚠️ Doesn't release the memory associated with the removed element.
// https://arduinojson.org/v6/api/jsonvariant/remove/
FORCE_INLINE void remove(size_t index) const {
VariantData* data = getData();
if (data)
data->remove(index);
variantRemoveElement(getData(), index, getPool());
}
// Removes a member of the object.
@ -190,9 +187,7 @@ class VariantRefBase : public VariantTag {
template <typename TChar>
FORCE_INLINE typename enable_if<IsString<TChar*>::value>::type remove(
TChar* key) const {
VariantData* data = getData();
if (data)
data->remove(adaptString(key));
variantRemoveMember(getData(), adaptString(key), getPool());
}
// Removes a member of the object.
@ -201,9 +196,7 @@ class VariantRefBase : public VariantTag {
template <typename TString>
FORCE_INLINE typename enable_if<IsString<TString>::value>::type remove(
const TString& key) const {
VariantData* data = getData();
if (data)
data->remove(adaptString(key));
variantRemoveMember(getData(), adaptString(key), getPool());
}
// Creates an array and appends it to the array.