Reduced memory consumption by not duplicating spaces and comments

This commit is contained in:
Benoit Blanchon
2016-12-29 17:26:16 +01:00
parent 8032a4b564
commit 3f96e070ce
20 changed files with 596 additions and 287 deletions

View File

@ -5,6 +5,7 @@ HEAD
---- ----
* Added operator `==` to compare `JsonVariant` and strings (issue #402) * Added operator `==` to compare `JsonVariant` and strings (issue #402)
* Reduced memory consumption by not duplicating spaces and comments
v5.7.3 v5.7.3
------ ------

View File

@ -1,115 +0,0 @@
// Copyright Benoit Blanchon 2014-2016
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include "../JsonBuffer.hpp"
#include <stdlib.h>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
namespace ArduinoJson {
namespace Internals {
class DefaultAllocator {
public:
void* allocate(size_t size) {
return malloc(size);
}
void deallocate(void* pointer) {
free(pointer);
}
};
template <typename TAllocator>
class BlockJsonBuffer : public JsonBuffer {
struct Block;
struct EmptyBlock {
Block* next;
size_t capacity;
size_t size;
};
struct Block : EmptyBlock {
uint8_t data[1];
};
public:
BlockJsonBuffer(size_t initialSize = 256)
: _head(NULL), _nextBlockSize(initialSize) {}
~BlockJsonBuffer() {
Block* currentBlock = _head;
while (currentBlock != NULL) {
Block* nextBlock = currentBlock->next;
_allocator.deallocate(currentBlock);
currentBlock = nextBlock;
}
}
size_t size() const {
size_t total = 0;
for (const Block* b = _head; b; b = b->next) total += b->size;
return total;
}
virtual void* alloc(size_t bytes) {
return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes);
}
private:
bool canAllocInHead(size_t bytes) const {
return _head != NULL && _head->size + bytes <= _head->capacity;
}
void* allocInHead(size_t bytes) {
void* p = _head->data + _head->size;
_head->size += round_size_up(bytes);
return p;
}
void* allocInNewBlock(size_t bytes) {
size_t capacity = _nextBlockSize;
if (bytes > capacity) capacity = bytes;
if (!addNewBlock(capacity)) return NULL;
_nextBlockSize *= 2;
return allocInHead(bytes);
}
bool addNewBlock(size_t capacity) {
size_t bytes = sizeof(EmptyBlock) + capacity;
Block* block = static_cast<Block*>(_allocator.allocate(bytes));
if (block == NULL) return false;
block->capacity = capacity;
block->size = 0;
block->next = _head;
_head = block;
return true;
}
TAllocator _allocator;
Block* _head;
size_t _nextBlockSize;
};
}
}
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop
#endif
#endif

View File

@ -30,6 +30,17 @@ template <typename TString>
struct StringFuncs<TString&> : StringFuncs<TString> {}; struct StringFuncs<TString&> : StringFuncs<TString> {};
struct CharPtrFuncs { struct CharPtrFuncs {
class Iterator {
const char* _ptr;
public:
Iterator(const char* ptr) : _ptr(ptr ? ptr : "") {}
char next() {
return *_ptr++;
}
};
static bool equals(const char* str, const char* expected) { static bool equals(const char* str, const char* expected) {
return strcmp(str, expected) == 0; return strcmp(str, expected) == 0;
} }
@ -71,6 +82,10 @@ struct StdStringFuncs {
return static_cast<char*>(dup); return static_cast<char*>(dup);
} }
struct Iterator : CharPtrFuncs::Iterator {
Iterator(const TString& str) : CharPtrFuncs::Iterator(str.c_str()) {}
};
static bool equals(const TString& str, const char* expected) { static bool equals(const TString& str, const char* expected) {
return str == expected; return str == expected;
} }
@ -99,6 +114,18 @@ struct StringFuncs<std::string> : StdStringFuncs<std::string> {};
#if ARDUINOJSON_ENABLE_PROGMEM #if ARDUINOJSON_ENABLE_PROGMEM
template <> template <>
struct StringFuncs<const __FlashStringHelper*> { struct StringFuncs<const __FlashStringHelper*> {
class Iterator {
const char* _ptr;
public:
Iterator(const __FlashStringHelper* ptr)
: _ptr(reinterpret_cast<const char*>(ptr)) {}
char next() {
return pgm_read_byte_near(_ptr++);
}
};
static bool equals(const __FlashStringHelper* str, const char* expected) { static bool equals(const __FlashStringHelper* str, const char* expected) {
return strcmp_P(expected, (PGM_P)str) == 0; return strcmp_P(expected, (PGM_P)str) == 0;
} }

View File

@ -12,51 +12,51 @@ namespace Internals {
template <typename TInput> template <typename TInput>
void skipSpacesAndComments(TInput& input) { void skipSpacesAndComments(TInput& input) {
for (;;) { for (;;) {
switch (input.peek()) { switch (input.current()) {
// spaces // spaces
case ' ': case ' ':
case '\t': case '\t':
case '\r': case '\r':
case '\n': case '\n':
input.skip(); input.move();
continue; continue;
// comments // comments
case '/': case '/':
switch (input.peekNext()) { switch (input.next()) {
// C-style block comment // C-style block comment
case '*': case '*':
input.skip(); // skip '/' input.move(); // skip '/'
input.skip(); // skip '*' input.move(); // skip '*'
for (;;) { for (;;) {
switch (input.peek()) { switch (input.current()) {
case '\0': case '\0':
return; return;
case '*': case '*':
input.skip(); // skip '*' input.move(); // skip '*'
if (input.peek() == '/') { if (input.current() == '/') {
input.skip(); // skip '/' input.move(); // skip '/'
return; return;
} }
break; break;
default: default:
input.skip(); input.move();
} }
} }
break; break;
// C++-style line comment // C++-style line comment
case '/': case '/':
input.skip(); // skip '/' input.move(); // skip '/'
for (;;) { for (;;) {
switch (input.peek()) { switch (input.current()) {
case '\0': case '\0':
return; return;
case '\n': case '\n':
input.skip(); input.move();
return; return;
default: default:
input.skip(); input.move();
} }
} }
return; return;

View File

@ -18,12 +18,14 @@ namespace Internals {
// Parse JSON string to create JsonArrays and JsonObjects // Parse JSON string to create JsonArrays and JsonObjects
// This internal class is not indended to be used directly. // This internal class is not indended to be used directly.
// Instead, use JsonBuffer.parseArray() or .parseObject() // Instead, use JsonBuffer.parseArray() or .parseObject()
template <typename TReader, typename TWriter>
class JsonParser { class JsonParser {
public: public:
JsonParser(JsonBuffer *buffer, char *json, uint8_t nestingLimit) JsonParser(JsonBuffer *buffer, TReader reader, TWriter writer,
uint8_t nestingLimit)
: _buffer(buffer), : _buffer(buffer),
_reader(json), _reader(reader),
_writer(json), _writer(writer),
_nestingLimit(nestingLimit) {} _nestingLimit(nestingLimit) {}
JsonArray &parseArray(); JsonArray &parseArray();
@ -36,7 +38,9 @@ class JsonParser {
} }
private: private:
static bool eat(StringReader &, char charToSkip); JsonParser &operator=(const JsonParser &); // non-copiable
static bool eat(TReader &, char charToSkip);
FORCE_INLINE bool eat(char charToSkip) { FORCE_INLINE bool eat(char charToSkip) {
return eat(_reader, charToSkip); return eat(_reader, charToSkip);
} }
@ -63,9 +67,42 @@ class JsonParser {
} }
JsonBuffer *_buffer; JsonBuffer *_buffer;
StringReader _reader; TReader _reader;
StringWriter _writer; TWriter _writer;
uint8_t _nestingLimit; uint8_t _nestingLimit;
}; };
template <typename TJsonBuffer, typename TString>
struct JsonParserBuilder {
typedef typename Internals::StringFuncs<TString>::Iterator InputIterator;
typedef JsonParser<StringReader<InputIterator>, TJsonBuffer &> TParser;
static TParser makeParser(TJsonBuffer *buffer, const TString &json,
uint8_t nestingLimit) {
return TParser(buffer, InputIterator(json), *buffer, nestingLimit);
}
};
template <typename TJsonBuffer>
struct JsonParserBuilder<TJsonBuffer, char *> {
typedef typename Internals::StringFuncs<char *>::Iterator InputIterator;
typedef JsonParser<StringReader<InputIterator>, StringWriter> TParser;
static TParser makeParser(TJsonBuffer *buffer, char *json,
uint8_t nestingLimit) {
return TParser(buffer, InputIterator(json), json, nestingLimit);
}
};
template <typename TJsonBuffer, typename TChar, size_t N>
struct JsonParserBuilder<TJsonBuffer, TChar[N]>
: JsonParserBuilder<TJsonBuffer, TChar *> {};
template <typename TJsonBuffer, typename TString>
inline typename JsonParserBuilder<TJsonBuffer, TString>::TParser makeParser(
TJsonBuffer *buffer, TString &json, uint8_t nestingLimit) {
return JsonParserBuilder<TJsonBuffer, TString>::makeParser(buffer, json,
nestingLimit);
}
} }
} }

View File

@ -10,16 +10,19 @@
#include "Comments.hpp" #include "Comments.hpp"
#include "JsonParser.hpp" #include "JsonParser.hpp"
inline bool ArduinoJson::Internals::JsonParser::eat(StringReader &reader, template <typename TReader, typename TWriter>
char charToSkip) { inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::eat(
TReader &reader, char charToSkip) {
skipSpacesAndComments(reader); skipSpacesAndComments(reader);
if (reader.peek() != charToSkip) return false; if (reader.current() != charToSkip) return false;
reader.skip(); reader.move();
skipSpacesAndComments(reader); skipSpacesAndComments(reader);
return true; return true;
} }
inline bool ArduinoJson::Internals::JsonParser::parseAnythingTo( template <typename TReader, typename TWriter>
inline bool
ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseAnythingTo(
JsonVariant *destination) { JsonVariant *destination) {
if (_nestingLimit == 0) return false; if (_nestingLimit == 0) return false;
_nestingLimit--; _nestingLimit--;
@ -28,11 +31,13 @@ inline bool ArduinoJson::Internals::JsonParser::parseAnythingTo(
return success; return success;
} }
inline bool ArduinoJson::Internals::JsonParser::parseAnythingToUnsafe( template <typename TReader, typename TWriter>
inline bool
ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseAnythingToUnsafe(
JsonVariant *destination) { JsonVariant *destination) {
skipSpacesAndComments(_reader); skipSpacesAndComments(_reader);
switch (_reader.peek()) { switch (_reader.current()) {
case '[': case '[':
return parseArrayTo(destination); return parseArrayTo(destination);
@ -44,8 +49,9 @@ inline bool ArduinoJson::Internals::JsonParser::parseAnythingToUnsafe(
} }
} }
template <typename TReader, typename TWriter>
inline ArduinoJson::JsonArray & inline ArduinoJson::JsonArray &
ArduinoJson::Internals::JsonParser::parseArray() { ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseArray() {
// Create an empty array // Create an empty array
JsonArray &array = _buffer->createArray(); JsonArray &array = _buffer->createArray();
@ -76,7 +82,8 @@ ERROR_NO_MEMORY:
return JsonArray::invalid(); return JsonArray::invalid();
} }
inline bool ArduinoJson::Internals::JsonParser::parseArrayTo( template <typename TReader, typename TWriter>
inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseArrayTo(
JsonVariant *destination) { JsonVariant *destination) {
JsonArray &array = parseArray(); JsonArray &array = parseArray();
if (!array.success()) return false; if (!array.success()) return false;
@ -85,8 +92,9 @@ inline bool ArduinoJson::Internals::JsonParser::parseArrayTo(
return true; return true;
} }
template <typename TReader, typename TWriter>
inline ArduinoJson::JsonObject & inline ArduinoJson::JsonObject &
ArduinoJson::Internals::JsonParser::parseObject() { ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseObject() {
// Create an empty object // Create an empty object
JsonObject &object = _buffer->createObject(); JsonObject &object = _buffer->createObject();
@ -124,7 +132,8 @@ ERROR_NO_MEMORY:
return JsonObject::invalid(); return JsonObject::invalid();
} }
inline bool ArduinoJson::Internals::JsonParser::parseObjectTo( template <typename TReader, typename TWriter>
inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseObjectTo(
JsonVariant *destination) { JsonVariant *destination) {
JsonObject &object = parseObject(); JsonObject &object = parseObject();
if (!object.success()) return false; if (!object.success()) return false;
@ -133,46 +142,49 @@ inline bool ArduinoJson::Internals::JsonParser::parseObjectTo(
return true; return true;
} }
inline const char *ArduinoJson::Internals::JsonParser::parseString() { template <typename TReader, typename TWriter>
const char *str = _writer.startString(); inline const char *
ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseString() {
typename TypeTraits::RemoveReference<TWriter>::type::String str =
_writer.startString();
char c = _reader.peek(); char c = _reader.current();
if (isQuote(c)) { // quotes if (isQuote(c)) { // quotes
_reader.skip(); _reader.move();
char stopChar = c; char stopChar = c;
for (;;) { for (;;) {
c = _reader.peek(); c = _reader.current();
if (c == '\0') break; if (c == '\0') break;
_reader.skip(); _reader.move();
if (c == stopChar) break; if (c == stopChar) break;
if (c == '\\') { if (c == '\\') {
// replace char // replace char
c = Encoding::unescapeChar(_reader.peek()); c = Encoding::unescapeChar(_reader.current());
if (c == '\0') break; if (c == '\0') break;
_reader.skip(); _reader.move();
} }
_writer.append(c); str.append(c);
} }
} else { // no quotes } else { // no quotes
for (;;) { for (;;) {
if (!isLetterOrNumber(c)) break; if (!isLetterOrNumber(c)) break;
_reader.skip(); _reader.move();
_writer.append(c); str.append(c);
c = _reader.peek(); c = _reader.current();
} }
} }
_writer.stopString(); return str.c_str();
return str;
} }
inline bool ArduinoJson::Internals::JsonParser::parseStringTo( template <typename TReader, typename TWriter>
inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseStringTo(
JsonVariant *destination) { JsonVariant *destination) {
bool hasQuotes = isQuote(_reader.peek()); bool hasQuotes = isQuote(_reader.current());
const char *value = parseString(); const char *value = parseString();
if (value == NULL) return false; if (value == NULL) return false;
if (hasQuotes) { if (hasQuotes) {

View File

@ -13,24 +13,29 @@ namespace Internals {
// Parse JSON string to create JsonArrays and JsonObjects // Parse JSON string to create JsonArrays and JsonObjects
// This internal class is not indended to be used directly. // This internal class is not indended to be used directly.
// Instead, use JsonBuffer.parseArray() or .parseObject() // Instead, use JsonBuffer.parseArray() or .parseObject()
template <typename TIterator>
class StringReader { class StringReader {
TIterator _input;
char _current, _next;
public: public:
StringReader(const char *input) : _ptr(input ? input : "") {} StringReader(const TIterator& input) : _input(input) {
_current = _input.next();
void skip() { _next = _input.next();
_ptr++;
} }
char peek() const { void move() {
return _ptr[0]; _current = _next;
_next = _input.next();
} }
char peekNext() const { char current() const {
return _ptr[1]; return _current;
} }
private: char next() const {
const char *_ptr; return _next;
}
}; };
} }
} }

View File

@ -10,27 +10,34 @@
namespace ArduinoJson { namespace ArduinoJson {
namespace Internals { namespace Internals {
// Parse JSON string to create JsonArrays and JsonObjects
// This internal class is not indended to be used directly.
// Instead, use JsonBuffer.parseArray() or .parseObject()
class StringWriter { class StringWriter {
public: public:
StringWriter(char *buffer) : _ptr(buffer) {} class String {
public:
const char *startString() { String(char** ptr) : _writePtr(ptr), _startPtr(*ptr) {}
return _ptr;
}
void stopString() {
*_ptr++ = 0;
}
void append(char c) { void append(char c) {
*_ptr++ = c; *(*_writePtr)++ = c;
}
const char* c_str() const {
*(*_writePtr)++ = 0;
return _startPtr;
} }
private: private:
char *_ptr; char** _writePtr;
char* _startPtr;
};
StringWriter(char* buffer) : _ptr(buffer) {}
String startString() {
return String(&_ptr);
}
private:
char* _ptr;
}; };
} }
} }

View File

@ -7,12 +7,153 @@
#pragma once #pragma once
#include "Data/BlockJsonBuffer.hpp" #include "JsonBufferBase.hpp"
#include <stdlib.h>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
namespace ArduinoJson { namespace ArduinoJson {
class DefaultAllocator {
public:
void* allocate(size_t size) {
return malloc(size);
}
void deallocate(void* pointer) {
free(pointer);
}
};
template <typename TAllocator>
class DynamicJsonBufferBase
: public JsonBufferBase<DynamicJsonBufferBase<TAllocator> > {
struct Block;
struct EmptyBlock {
Block* next;
size_t capacity;
size_t size;
};
struct Block : EmptyBlock {
uint8_t data[1];
};
public:
DynamicJsonBufferBase(size_t initialSize = 256)
: _head(NULL), _nextBlockCapacity(initialSize) {}
~DynamicJsonBufferBase() {
Block* currentBlock = _head;
while (currentBlock != NULL) {
Block* nextBlock = currentBlock->next;
_allocator.deallocate(currentBlock);
currentBlock = nextBlock;
}
}
size_t size() const {
size_t total = 0;
for (const Block* b = _head; b; b = b->next) total += b->size;
return total;
}
virtual void* alloc(size_t bytes) {
alignNextAlloc();
return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes);
}
class String {
public:
String(DynamicJsonBufferBase* parent)
: _parent(parent), _start(NULL), _length(0) {}
void append(char c) {
if (_parent->canAllocInHead(1)) {
char* end = static_cast<char*>(_parent->allocInHead(1));
*end = c;
if (_length == 0) _start = end;
} else {
char* newStart =
static_cast<char*>(_parent->allocInNewBlock(_length + 1));
if (_start && newStart) memcpy(newStart, _start, _length);
newStart[_length] = c;
_start = newStart;
}
_length++;
}
const char* c_str() {
append(0);
return _start;
}
private:
DynamicJsonBufferBase* _parent;
char* _start;
int _length;
};
String startString() {
return String(this);
}
private:
void alignNextAlloc() {
if (_head) _head->size = this->round_size_up(_head->size);
}
bool canAllocInHead(size_t bytes) const {
return _head != NULL && _head->size + bytes <= _head->capacity;
}
void* allocInHead(size_t bytes) {
void* p = _head->data + _head->size;
_head->size += bytes;
return p;
}
void* allocInNewBlock(size_t bytes) {
size_t capacity = _nextBlockCapacity;
if (bytes > capacity) capacity = bytes;
if (!addNewBlock(capacity)) return NULL;
_nextBlockCapacity *= 2;
return allocInHead(bytes);
}
bool addNewBlock(size_t capacity) {
size_t bytes = sizeof(EmptyBlock) + capacity;
Block* block = static_cast<Block*>(_allocator.allocate(bytes));
if (block == NULL) return false;
block->capacity = capacity;
block->size = 0;
block->next = _head;
_head = block;
return true;
}
TAllocator _allocator;
Block* _head;
size_t _nextBlockCapacity;
};
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop
#endif
#endif
// Implements a JsonBuffer with dynamic memory allocation. // Implements a JsonBuffer with dynamic memory allocation.
// You are strongly encouraged to consider using StaticJsonBuffer which is much // You are strongly encouraged to consider using StaticJsonBuffer which is much
// more suitable for embedded systems. // more suitable for embedded systems.
typedef Internals::BlockJsonBuffer<Internals::DefaultAllocator> typedef DynamicJsonBufferBase<DefaultAllocator> DynamicJsonBuffer;
DynamicJsonBuffer;
} }

View File

@ -51,58 +51,6 @@ class JsonBuffer {
// allocation fails. // allocation fails.
JsonObject &createObject(); JsonObject &createObject();
// Allocates and populate a JsonArray from a JSON string.
//
// The First argument is a pointer to the JSON string, the memory must be
// writable
// because the parser will insert null-terminators and replace escaped chars.
//
// The second argument set the nesting limit
//
// Returns a reference to the new JsonObject or JsonObject::invalid() if the
// allocation fails.
JsonArray &parseArray(
char *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT);
// With this overload, the JsonBuffer will make a copy of the string
template <typename TString>
JsonArray &parseArray(const TString &json,
uint8_t nesting = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return parseArray(strdup(json), nesting);
}
// Allocates and populate a JsonObject from a JSON string.
//
// The First argument is a pointer to the JSON string, the memory must be
// writable
// because the parser will insert null-terminators and replace escaped chars.
//
// The second argument set the nesting limit
//
// Returns a reference to the new JsonObject or JsonObject::invalid() if the
// allocation fails.
JsonObject &parseObject(
char *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT);
// With this overload, the JsonBuffer will make a copy of the string
template <typename TString>
JsonObject &parseObject(const TString &json,
uint8_t nesting = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return parseObject(strdup(json), nesting);
}
// Generalized version of parseArray() and parseObject(), also works for
// integral types.
JsonVariant parse(char *json,
uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT);
// With this overload, the JsonBuffer will make a copy of the string
template <typename TString>
JsonVariant parse(const TString &json,
uint8_t nesting = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return parse(strdup(json), nesting);
}
// Duplicate a string // Duplicate a string
template <typename TString> template <typename TString>
char *strdup(const TString &src) { char *strdup(const TString &src) {
@ -114,7 +62,7 @@ class JsonBuffer {
virtual void *alloc(size_t size) = 0; virtual void *alloc(size_t size) = 0;
protected: protected:
// Preserve aligment if nessary // Preserve aligment if necessary
static FORCE_INLINE size_t round_size_up(size_t bytes) { static FORCE_INLINE size_t round_size_up(size_t bytes) {
#if ARDUINOJSON_ENABLE_ALIGNMENT #if ARDUINOJSON_ENABLE_ALIGNMENT
const size_t x = sizeof(void *) - 1; const size_t x = sizeof(void *) - 1;

View File

@ -0,0 +1,97 @@
// Copyright Benoit Blanchon 2014-2016
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#pragma once
#include "Deserialization/JsonParser.hpp"
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic push
#endif
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#endif
namespace ArduinoJson {
template <typename TDerived>
class JsonBufferBase : public JsonBuffer {
public:
// Allocates and populate a JsonArray from a JSON string.
//
// The First argument is a pointer to the JSON string, the memory must be
// writable
// because the parser will insert null-terminators and replace escaped chars.
//
// The second argument set the nesting limit
//
// Returns a reference to the new JsonObject or JsonObject::invalid() if the
// allocation fails.
// With this overload, the JsonBuffer will make a copy of the string
template <typename TString>
JsonArray &parseArray(
const TString &json,
uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return Internals::makeParser(that(), json, nestingLimit).parseArray();
}
template <typename TString>
JsonArray &parseArray(
TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return Internals::makeParser(that(), json, nestingLimit).parseArray();
}
// Allocates and populate a JsonObject from a JSON string.
//
// The First argument is a pointer to the JSON string, the memory must be
// writable
// because the parser will insert null-terminators and replace escaped chars.
//
// The second argument set the nesting limit
//
// Returns a reference to the new JsonObject or JsonObject::invalid() if the
// allocation fails.
template <typename TString>
JsonObject &parseObject(
const TString &json,
uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return Internals::makeParser(that(), json, nestingLimit).parseObject();
}
template <typename TString>
JsonObject &parseObject(
TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return Internals::makeParser(that(), json, nestingLimit).parseObject();
}
// Generalized version of parseArray() and parseObject(), also works for
// integral types.
template <typename TString>
JsonVariant parse(const TString &json,
uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return Internals::makeParser(that(), json, nestingLimit).parseVariant();
}
template <typename TString>
JsonVariant parse(TString &json,
uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
return Internals::makeParser(that(), json, nestingLimit).parseVariant();
}
private:
TDerived *that() {
return static_cast<TDerived *>(this);
}
};
}
#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
#pragma GCC diagnostic pop
#endif
#endif

View File

@ -18,21 +18,3 @@ inline ArduinoJson::JsonObject &ArduinoJson::JsonBuffer::createObject() {
JsonObject *ptr = new (this) JsonObject(this); JsonObject *ptr = new (this) JsonObject(this);
return ptr ? *ptr : JsonObject::invalid(); return ptr ? *ptr : JsonObject::invalid();
} }
inline ArduinoJson::JsonArray &ArduinoJson::JsonBuffer::parseArray(
char *json, uint8_t nestingLimit) {
Internals::JsonParser parser(this, json, nestingLimit);
return parser.parseArray();
}
inline ArduinoJson::JsonObject &ArduinoJson::JsonBuffer::parseObject(
char *json, uint8_t nestingLimit) {
Internals::JsonParser parser(this, json, nestingLimit);
return parser.parseObject();
}
inline ArduinoJson::JsonVariant ArduinoJson::JsonBuffer::parse(
char *json, uint8_t nestingLimit) {
Internals::JsonParser parser(this, json, nestingLimit);
return parser.parseVariant();
}

View File

@ -7,7 +7,7 @@
#pragma once #pragma once
#include "Internals/StringFuncs.hpp" #include "Data/StringFuncs.hpp"
#include "JsonVariantBase.hpp" #include "JsonVariantBase.hpp"
#include "TypeTraits/EnableIf.hpp" #include "TypeTraits/EnableIf.hpp"

View File

@ -7,7 +7,7 @@
#pragma once #pragma once
#include "JsonBuffer.hpp" #include "JsonBufferBase.hpp"
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
@ -21,32 +21,87 @@
namespace ArduinoJson { namespace ArduinoJson {
// Implements a JsonBuffer with fixed memory allocation. class StaticJsonBufferBase : public JsonBufferBase<StaticJsonBufferBase> {
// The template paramenter CAPACITY specifies the capacity of the buffer in
// bytes.
template <size_t CAPACITY>
class StaticJsonBuffer : public JsonBuffer {
public: public:
explicit StaticJsonBuffer() : _size(0) {} class String {
public:
String(StaticJsonBufferBase* parent) : _parent(parent) {
_start = parent->_buffer + parent->_size;
}
void append(char c) {
if (_parent->canAlloc(1)) {
char* last = static_cast<char*>(_parent->doAlloc(1));
*last = c;
}
}
const char* c_str() const {
if (_parent->canAlloc(1)) {
char* last = static_cast<char*>(_parent->doAlloc(1));
*last = '\0';
return _start;
} else {
return NULL;
}
}
private:
StaticJsonBufferBase* _parent;
char* _start;
};
StaticJsonBufferBase(char* buffer, size_t capa)
: _buffer(buffer), _capacity(capa), _size(0) {}
size_t capacity() const { size_t capacity() const {
return CAPACITY; return _capacity;
} }
size_t size() const { size_t size() const {
return _size; return _size;
} }
virtual void* alloc(size_t bytes) { virtual void* alloc(size_t bytes) {
if (_size + bytes > CAPACITY) return NULL; alignNextAlloc();
void* p = &_buffer[_size]; if (!canAlloc(bytes)) return NULL;
_size += round_size_up(bytes); return doAlloc(bytes);
return p; }
String startString() {
return String(this);
} }
private: private:
uint8_t _buffer[CAPACITY]; void alignNextAlloc() {
_size = round_size_up(_size);
}
bool canAlloc(size_t bytes) const {
return _size + bytes <= _capacity;
}
void* doAlloc(size_t bytes) {
void* p = &_buffer[_size];
_size += bytes;
return p;
}
char* _buffer;
size_t _capacity;
size_t _size; size_t _size;
}; };
// Implements a JsonBuffer with fixed memory allocation.
// The template paramenter CAPACITY specifies the capacity of the buffer in
// bytes.
template <size_t CAPACITY>
class StaticJsonBuffer : public StaticJsonBufferBase {
public:
explicit StaticJsonBuffer() : StaticJsonBufferBase(_buffer, CAPACITY) {}
private:
char _buffer[CAPACITY];
};
} }
#if defined(__clang__) #if defined(__clang__)

View File

@ -30,13 +30,19 @@ TEST_F(DynamicJsonBuffer_Basic_Tests, ReturnDifferentPointer) {
ASSERT_NE(p1, p2); ASSERT_NE(p1, p2);
} }
TEST_F(DynamicJsonBuffer_Basic_Tests, Alignment) { static bool isAligned(void* ptr) {
size_t mask = sizeof(void*) - 1; const size_t mask = sizeof(void*) - 1;
size_t addr = reinterpret_cast<size_t>(ptr);
return (addr & mask) == 0;
}
for (size_t size = 1; size <= sizeof(void*); size++) { TEST_F(DynamicJsonBuffer_Basic_Tests, Alignment) {
size_t addr = reinterpret_cast<size_t>(buffer.alloc(1)); // make room for tow but not three
ASSERT_EQ(0, addr & mask); buffer = DynamicJsonBuffer(2 * sizeof(void*) + 1);
}
ASSERT_TRUE(isAligned(buffer.alloc(1))); // this on is aligned by design
ASSERT_TRUE(isAligned(buffer.alloc(1))); // this one fits in the first block
ASSERT_TRUE(isAligned(buffer.alloc(1))); // this one requires a new block
} }
TEST_F(DynamicJsonBuffer_Basic_Tests, strdup) { TEST_F(DynamicJsonBuffer_Basic_Tests, strdup) {

View File

@ -5,18 +5,20 @@
// https://github.com/bblanchon/ArduinoJson // https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star! // If you like this project, please add a star!
#include <gtest/gtest.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <gtest/gtest.h>
class NoMemoryAllocator { class NoMemoryAllocator {
public: public:
void* allocate(size_t) { return NULL; } void* allocate(size_t) {
return NULL;
}
void deallocate(void*) {} void deallocate(void*) {}
}; };
class DynamicJsonBuffer_NoMemory_Tests : public ::testing::Test { class DynamicJsonBuffer_NoMemory_Tests : public ::testing::Test {
protected: protected:
Internals::BlockJsonBuffer<NoMemoryAllocator> _jsonBuffer; DynamicJsonBufferBase<NoMemoryAllocator> _jsonBuffer;
}; };
TEST_F(DynamicJsonBuffer_NoMemory_Tests, FixCodeCoverage) { TEST_F(DynamicJsonBuffer_NoMemory_Tests, FixCodeCoverage) {

View File

@ -0,0 +1,48 @@
// Copyright Benoit Blanchon 2014-2016
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <ArduinoJson.h>
#include <gtest/gtest.h>
TEST(DynamicJsonBuffer_String_Tests, WorksWhenBufferIsBigEnough) {
DynamicJsonBuffer jsonBuffer(6);
DynamicJsonBuffer::String str = jsonBuffer.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
ASSERT_STREQ("hello", str.c_str());
}
TEST(DynamicJsonBuffer_String_Tests, GrowsWhenBufferIsTooSmall) {
DynamicJsonBuffer jsonBuffer(5);
DynamicJsonBuffer::String str = jsonBuffer.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
ASSERT_STREQ("hello", str.c_str());
}
TEST(DynamicJsonBuffer_String_Tests, SizeIncreases) {
DynamicJsonBuffer jsonBuffer(5);
DynamicJsonBuffer::String str = jsonBuffer.startString();
ASSERT_EQ(0, jsonBuffer.size());
str.append('h');
ASSERT_EQ(1, jsonBuffer.size());
str.c_str();
ASSERT_EQ(2, jsonBuffer.size());
}

View File

@ -5,12 +5,12 @@
// https://github.com/bblanchon/ArduinoJson // https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star! // If you like this project, please add a star!
#include <gtest/gtest.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <gtest/gtest.h>
class StaticJsonBuffer_ParseArray_Tests : public testing::Test { class StaticJsonBuffer_ParseArray_Tests : public testing::Test {
protected: protected:
void with(JsonBuffer& jsonBuffer) { void with(StaticJsonBufferBase& jsonBuffer) {
_jsonBuffer = &jsonBuffer; _jsonBuffer = &jsonBuffer;
} }
@ -27,7 +27,7 @@ class StaticJsonBuffer_ParseArray_Tests : public testing::Test {
} }
private: private:
JsonBuffer* _jsonBuffer; StaticJsonBufferBase* _jsonBuffer;
char _jsonString[256]; char _jsonString[256];
}; };
@ -86,3 +86,11 @@ TEST_F(StaticJsonBuffer_ParseArray_Tests, ConstCharPtrNull) {
.parseArray(static_cast<const char*>(0)) .parseArray(static_cast<const char*>(0))
.success()); .success());
} }
TEST_F(StaticJsonBuffer_ParseArray_Tests, CopyStringNotSpaces) {
StaticJsonBuffer<100> jsonBuffer;
jsonBuffer.parseArray(" [ \"1234567\" ] ");
ASSERT_EQ(JSON_ARRAY_SIZE(1) + sizeof("1234567"), jsonBuffer.size());
// note we use a string of 8 bytes to be sure that the StaticJsonBuffer
// will not insert bytes to enforce alignement
}

View File

@ -10,7 +10,7 @@
class StaticJsonBuffer_ParseObject_Tests : public testing::Test { class StaticJsonBuffer_ParseObject_Tests : public testing::Test {
protected: protected:
void with(JsonBuffer& jsonBuffer) { void with(StaticJsonBufferBase& jsonBuffer) {
_jsonBuffer = &jsonBuffer; _jsonBuffer = &jsonBuffer;
} }
@ -27,7 +27,7 @@ class StaticJsonBuffer_ParseObject_Tests : public testing::Test {
} }
private: private:
JsonBuffer* _jsonBuffer; StaticJsonBufferBase* _jsonBuffer;
char _jsonString[256]; char _jsonString[256];
}; };

View File

@ -0,0 +1,48 @@
// Copyright Benoit Blanchon 2014-2016
// MIT License
//
// Arduino JSON library
// https://github.com/bblanchon/ArduinoJson
// If you like this project, please add a star!
#include <ArduinoJson.h>
#include <gtest/gtest.h>
TEST(StaticJsonBuffer_String_Tests, WorksWhenBufferIsBigEnough) {
StaticJsonBuffer<6> jsonBuffer;
StaticJsonBufferBase::String str = jsonBuffer.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
ASSERT_STREQ("hello", str.c_str());
}
TEST(StaticJsonBuffer_String_Tests, ReturnsNullWhenTooSmall) {
StaticJsonBuffer<5> jsonBuffer;
StaticJsonBufferBase::String str = jsonBuffer.startString();
str.append('h');
str.append('e');
str.append('l');
str.append('l');
str.append('o');
ASSERT_EQ(NULL, str.c_str());
}
TEST(StaticJsonBuffer_String_Tests, SizeIncreases) {
StaticJsonBuffer<5> jsonBuffer;
StaticJsonBufferBase::String str = jsonBuffer.startString();
ASSERT_EQ(0, jsonBuffer.size());
str.append('h');
ASSERT_EQ(1, jsonBuffer.size());
str.c_str();
ASSERT_EQ(2, jsonBuffer.size());
}