Reduced the size of the pretty JSON serializer

This commit is contained in:
Benoit Blanchon
2019-01-23 18:19:24 +01:00
parent 933a66a070
commit 70739f5cfd
14 changed files with 123 additions and 301 deletions

View File

@ -20,6 +20,8 @@ HEAD
* Added the ability to create/assign a `StaticJsonDocument`/`DynamicJsonDocument` from a `JsonArray`/`JsonObject`/`JsonVariant`
* Added `JsonDocument::isNull()`
* Added `JsonDocument::operator[]`
* Added `ARDUINOJSON_TAB` to configure the indentation character
* Reduced the size of the pretty JSON serializer
> ### BREAKING CHANGES
>

View File

@ -39,13 +39,13 @@ void setup() {
data.add(48.756080);
data.add(2.302038);
serializeJson(root, Serial);
serializeJson(doc, Serial);
// This prints:
// {"sensor":"gps","time":1351824120,"data":[48.756080,2.302038]}
Serial.println();
serializeJsonPretty(root, Serial);
serializeJsonPretty(doc, Serial);
// This prints:
// {
// "sensor": "gps",

View File

@ -79,7 +79,7 @@ void loop() {
}
Serial.print(F("Sending: "));
serializeJson(root, Serial);
serializeJson(doc, Serial);
Serial.println();
// Write response headers
@ -89,7 +89,7 @@ void loop() {
client.println();
// Write JSON document
serializeJsonPretty(root, client);
serializeJsonPretty(doc, client);
// Disconnect
client.stop();

View File

@ -75,11 +75,11 @@ void loop() {
Serial.print(remoteIp);
Serial.print(F(" on port "));
Serial.println(remotePort);
serializeJson(root, Serial);
serializeJson(doc, Serial);
// Send UDP packet
udp.beginPacket(remoteIp, remotePort);
serializeJson(root, udp);
serializeJson(doc, udp);
udp.println();
udp.endPacket();

View File

@ -140,3 +140,7 @@
#define ARDUINOJSON_LITTLE_ENDIAN 0
#endif
#endif
#ifndef ARDUINOJSON_TAB
#define ARDUINOJSON_TAB " "
#endif

View File

@ -1,69 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
namespace ARDUINOJSON_NAMESPACE {
// Decorator on top of Print to allow indented output.
// This class is used by serializeJsonPretty() but can also be used
// for your own purpose, like logging.
template <typename Print>
class IndentedPrint {
public:
explicit IndentedPrint(Print &p) : sink(&p) {
level = 0;
tabSize = 2;
isNewLine = true;
}
size_t write(uint8_t c) {
size_t n = 0;
if (isNewLine) n += writeTabs();
n += sink->write(c);
isNewLine = c == '\n';
return n;
}
size_t write(const uint8_t *s, size_t n) {
// TODO: optimize
size_t bytesWritten = 0;
while (n > 0) {
bytesWritten += write(*s++);
n--;
}
return bytesWritten;
}
// Adds one level of indentation
void indent() {
if (level < MAX_LEVEL) level++;
}
// Removes one level of indentation
void unindent() {
if (level > 0) level--;
}
// Set the number of space printed for each level of indentation
void setTabSize(uint8_t n) {
if (n < MAX_TAB_SIZE) tabSize = n & MAX_TAB_SIZE;
}
private:
Print *sink;
uint8_t level : 4;
uint8_t tabSize : 3;
bool isNewLine : 1;
size_t writeTabs() {
size_t n = 0;
for (int i = 0; i < level * tabSize; i++) n += sink->write(' ');
return n;
}
static const int MAX_LEVEL = 15; // because it's only 4 bits
static const int MAX_TAB_SIZE = 7; // because it's only 3 bits
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -7,21 +7,17 @@
#include "../Misc/Visitable.hpp"
#include "../Serialization/measure.hpp"
#include "../Serialization/serialize.hpp"
#include "JsonWriter.hpp"
#include "TextFormatter.hpp"
namespace ARDUINOJSON_NAMESPACE {
template <typename TWriter>
class JsonSerializer {
public:
JsonSerializer(TWriter &writer) : _writer(writer) {}
JsonSerializer(TWriter &writer) : _formatter(writer) {}
void visitFloat(Float value) {
_writer.writeFloat(value);
}
void visitArray(const CollectionData &array) {
_writer.beginArray();
FORCE_INLINE void visitArray(const CollectionData &array) {
write('[');
VariantSlot *slot = array.head();
@ -31,63 +27,74 @@ class JsonSerializer {
slot = slot->next();
if (slot == 0) break;
_writer.writeComma();
write(',');
}
_writer.endArray();
write(']');
}
void visitObject(const CollectionData &object) {
_writer.beginObject();
write('{');
VariantSlot *slot = object.head();
while (slot != 0) {
_writer.writeString(slot->key());
_writer.writeColon();
_formatter.writeString(slot->key());
write(':');
slot->data()->accept(*this);
slot = slot->next();
if (slot == 0) break;
_writer.writeComma();
write(',');
}
_writer.endObject();
write('}');
}
void visitFloat(Float value) {
_formatter.writeFloat(value);
}
void visitString(const char *value) {
_writer.writeString(value);
_formatter.writeString(value);
}
void visitRawJson(const char *data, size_t n) {
// TODO
for (size_t i = 0; i < n; i++) _writer.writeRaw(data[i]);
_formatter.writeRaw(data, n);
}
void visitNegativeInteger(UInt value) {
_writer.writeRaw('-');
_writer.writeInteger(value);
_formatter.writeNegativeInteger(value);
}
void visitPositiveInteger(UInt value) {
_writer.writeInteger(value);
_formatter.writePositiveInteger(value);
}
void visitBoolean(bool value) {
_writer.writeBoolean(value);
_formatter.writeBoolean(value);
}
void visitNull() {
_writer.writeRaw("null");
_formatter.writeRaw("null");
}
size_t bytesWritten() const {
return _writer.bytesWritten();
return _formatter.bytesWritten();
}
protected:
void write(char c) {
_formatter.writeRaw(c);
}
void write(const char *s) {
_formatter.writeRaw(s);
}
private:
JsonWriter<TWriter> _writer;
TextFormatter<TWriter> _formatter;
};
template <typename TSource, typename TDestination>

View File

@ -4,37 +4,68 @@
#pragma once
#include "../Configuration.hpp"
#include "../Serialization/measure.hpp"
#include "../Serialization/serialize.hpp"
#include "./IndentedPrint.hpp"
#include "./JsonSerializer.hpp"
#include "./Prettyfier.hpp"
#include "JsonSerializer.hpp"
namespace ARDUINOJSON_NAMESPACE {
template <typename TPrint>
class PrettyJsonSerializer_Base {
public:
PrettyJsonSerializer_Base(TPrint &output)
: _indentedPrint(output), _prettyfier(_indentedPrint) {}
template <typename TWriter>
class PrettyJsonSerializer : public JsonSerializer<TWriter> {
typedef JsonSerializer<TWriter> base;
protected:
IndentedPrint<TPrint> _indentedPrint;
Prettyfier<TPrint> _prettyfier;
};
template <typename TPrint>
class PrettyJsonSerializer : PrettyJsonSerializer_Base<TPrint>,
public JsonSerializer<Prettyfier<TPrint> > {
public:
PrettyJsonSerializer(TPrint &output)
: PrettyJsonSerializer_Base<TPrint>(output),
JsonSerializer<Prettyfier<TPrint> >(
PrettyJsonSerializer_Base<TPrint>::_prettyfier) {}
PrettyJsonSerializer(TWriter &writer) : base(writer), _nesting(0) {}
void visitArray(const CollectionData &array) {
VariantSlot *slot = array.head();
if (!slot) return base::write("[]");
base::write("[\r\n");
_nesting++;
while (slot != 0) {
indent();
slot->data()->accept(*this);
slot = slot->next();
base::write(slot ? ",\r\n" : "\r\n");
}
_nesting--;
indent();
base::write("]");
}
void visitObject(const CollectionData &object) {
VariantSlot *slot = object.head();
if (!slot) return base::write("{}");
base::write("{\r\n");
_nesting++;
while (slot != 0) {
indent();
base::visitString(slot->key());
base::write(": ");
slot->data()->accept(*this);
slot = slot->next();
base::write(slot ? ",\r\n" : "\r\n");
}
_nesting--;
indent();
base::write("}");
}
private:
void indent() {
for (uint8_t i = 0; i < _nesting; i++) base::write(ARDUINOJSON_TAB);
}
uint8_t _nesting;
};
template <typename TSource, typename TDestination>
size_t serializeJsonPretty(TSource &source, TDestination &destination) {
size_t serializeJsonPretty(const TSource &source, TDestination &destination) {
return serialize<PrettyJsonSerializer>(source, destination);
}

View File

@ -1,143 +0,0 @@
// ArduinoJson - arduinojson.org
// Copyright Benoit Blanchon 2014-2018
// MIT License
#pragma once
#include "IndentedPrint.hpp"
namespace ARDUINOJSON_NAMESPACE {
// Converts a compact JSON string into an indented one.
template <typename TWriter>
class Prettyfier {
public:
explicit Prettyfier(IndentedPrint<TWriter>& p) : _sink(p) {
_previousChar = 0;
_inString = false;
}
size_t write(uint8_t c) {
size_t n = _inString ? handleStringChar(c) : handleMarkupChar(char(c));
_previousChar = char(c);
return n;
}
size_t write(const uint8_t* s, size_t n) {
// TODO: optimize
size_t bytesWritten = 0;
while (n > 0) {
bytesWritten += write(*s++);
n--;
}
return bytesWritten;
}
private:
Prettyfier& operator=(const Prettyfier&); // cannot be assigned
bool inEmptyBlock() {
return _previousChar == '{' || _previousChar == '[';
}
size_t handleStringChar(uint8_t c) {
bool isQuote = c == '"' && _previousChar != '\\';
if (isQuote) _inString = false;
return _sink.write(c);
}
size_t handleMarkupChar(char c) {
switch (c) {
case '{':
case '[':
return writeBlockOpen(c);
case '}':
case ']':
return writeBlockClose(c);
case ':':
return writeColon();
case ',':
return writeComma();
case '"':
return writeQuoteOpen();
default:
return writeNormalChar(c);
}
}
size_t writeBlockClose(char c) {
size_t n = 0;
n += unindentIfNeeded();
n += write(c);
return n;
}
size_t writeBlockOpen(char c) {
size_t n = 0;
n += indentIfNeeded();
n += write(c);
return n;
}
size_t writeColon() {
size_t n = 0;
n += write(": ");
return n;
}
size_t writeComma() {
size_t n = 0;
n += write(",\r\n");
return n;
}
size_t writeQuoteOpen() {
_inString = true;
size_t n = 0;
n += indentIfNeeded();
n += write('"');
return n;
}
size_t writeNormalChar(char c) {
size_t n = 0;
n += indentIfNeeded();
n += write(c);
return n;
}
size_t indentIfNeeded() {
if (!inEmptyBlock()) return 0;
_sink.indent();
return write("\r\n");
}
size_t unindentIfNeeded() {
if (inEmptyBlock()) return 0;
_sink.unindent();
return write("\r\n");
}
size_t write(char c) {
return _sink.write(static_cast<uint8_t>(c));
}
template <size_t N>
size_t write(const char (&s)[N]) {
return _sink.write(reinterpret_cast<const uint8_t*>(s), N - 1);
}
char _previousChar;
IndentedPrint<TWriter>& _sink;
bool _inString;
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -14,36 +14,15 @@
namespace ARDUINOJSON_NAMESPACE {
template <typename TWriter>
class JsonWriter {
class TextFormatter {
public:
explicit JsonWriter(TWriter &writer) : _writer(writer), _length(0) {}
explicit TextFormatter(TWriter &writer) : _writer(writer), _length(0) {}
// Returns the number of bytes sent to the TWriter implementation.
size_t bytesWritten() const {
return _length;
}
void beginArray() {
writeRaw('[');
}
void endArray() {
writeRaw(']');
}
void beginObject() {
writeRaw('{');
}
void endObject() {
writeRaw('}');
}
void writeColon() {
writeRaw(':');
}
void writeComma() {
writeRaw(',');
}
void writeBoolean(bool value) {
if (value)
writeRaw("true");
@ -84,22 +63,27 @@ class JsonWriter {
FloatParts<T> parts(value);
writeInteger(parts.integral);
writePositiveInteger(parts.integral);
if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces);
if (parts.exponent < 0) {
writeRaw("e-");
writeInteger(-parts.exponent);
writePositiveInteger(-parts.exponent);
}
if (parts.exponent > 0) {
writeRaw('e');
writeInteger(parts.exponent);
writePositiveInteger(parts.exponent);
}
}
void writeNegativeInteger(UInt value) {
writeRaw('-');
writePositiveInteger(value);
}
template <typename T>
void writeInteger(T value) {
void writePositiveInteger(T value) {
char buffer[22];
char *end = buffer + sizeof(buffer);
char *begin = end;
@ -134,10 +118,16 @@ class JsonWriter {
void writeRaw(const char *s) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(s), strlen(s));
}
void writeRaw(const char *s, size_t n) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(s), n);
}
void writeRaw(const char *begin, const char *end) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(begin),
static_cast<size_t>(end - begin));
}
template <size_t N>
void writeRaw(const char (&s)[N]) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(s), N - 1);
@ -151,6 +141,6 @@ class JsonWriter {
size_t _length;
private:
JsonWriter &operator=(const JsonWriter &); // cannot be assigned
TextFormatter &operator=(const TextFormatter &); // cannot be assigned
};
} // namespace ARDUINOJSON_NAMESPACE

View File

@ -78,7 +78,7 @@ add_subdirectory(JsonDocument)
add_subdirectory(JsonObject)
add_subdirectory(JsonSerializer)
add_subdirectory(JsonVariant)
add_subdirectory(JsonWriter)
add_subdirectory(TextFormatter)
add_subdirectory(MemoryPool)
add_subdirectory(Misc)
add_subdirectory(MixedConfiguration)

View File

@ -8,4 +8,4 @@ add_executable(JsonWriterTests
)
target_link_libraries(JsonWriterTests catch)
add_test(JsonWriter JsonWriterTests)
add_test(TextFormatter JsonWriterTests)

View File

@ -6,7 +6,7 @@
#include <limits>
#include <string>
#include <ArduinoJson/Json/JsonWriter.hpp>
#include <ArduinoJson/Json/TextFormatter.hpp>
#include <ArduinoJson/Serialization/DynamicStringWriter.hpp>
using namespace ARDUINOJSON_NAMESPACE;
@ -15,13 +15,13 @@ template <typename TFloat>
void check(TFloat input, const std::string& expected) {
std::string output;
DynamicStringWriter<std::string> sb(output);
JsonWriter<DynamicStringWriter<std::string> > writer(sb);
TextFormatter<DynamicStringWriter<std::string> > writer(sb);
writer.writeFloat(input);
REQUIRE(writer.bytesWritten() == output.size());
CHECK(expected == output);
}
TEST_CASE("JsonWriter::writeFloat(double)") {
TEST_CASE("TextFormatter::writeFloat(double)") {
SECTION("Pi") {
check<double>(3.14159265359, "3.141592654");
}
@ -102,7 +102,7 @@ TEST_CASE("JsonWriter::writeFloat(double)") {
}
}
TEST_CASE("JsonWriter::writeFloat(float)") {
TEST_CASE("TextFormatter::writeFloat(float)") {
SECTION("Pi") {
check<float>(3.14159265359f, "3.141593");
}

View File

@ -4,7 +4,7 @@
#include <catch.hpp>
#include <ArduinoJson/Json/JsonWriter.hpp>
#include <ArduinoJson/Json/TextFormatter.hpp>
#include <ArduinoJson/Serialization/StaticStringWriter.hpp>
using namespace ARDUINOJSON_NAMESPACE;
@ -12,13 +12,13 @@ using namespace ARDUINOJSON_NAMESPACE;
void check(const char* input, std::string expected) {
char output[1024];
StaticStringWriter sb(output, sizeof(output));
JsonWriter<StaticStringWriter> writer(sb);
TextFormatter<StaticStringWriter> writer(sb);
writer.writeString(input);
REQUIRE(expected == output);
REQUIRE(writer.bytesWritten() == expected.size());
}
TEST_CASE("JsonWriter::writeString()") {
TEST_CASE("TextFormatter::writeString()") {
SECTION("Null") {
check(0, "null");
}