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 the ability to create/assign a `StaticJsonDocument`/`DynamicJsonDocument` from a `JsonArray`/`JsonObject`/`JsonVariant`
* Added `JsonDocument::isNull()` * Added `JsonDocument::isNull()`
* Added `JsonDocument::operator[]` * Added `JsonDocument::operator[]`
* Added `ARDUINOJSON_TAB` to configure the indentation character
* Reduced the size of the pretty JSON serializer
> ### BREAKING CHANGES > ### BREAKING CHANGES
> >

View File

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

View File

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

View File

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

View File

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

View File

@ -4,37 +4,68 @@
#pragma once #pragma once
#include "../Configuration.hpp"
#include "../Serialization/measure.hpp" #include "../Serialization/measure.hpp"
#include "../Serialization/serialize.hpp" #include "../Serialization/serialize.hpp"
#include "./IndentedPrint.hpp" #include "JsonSerializer.hpp"
#include "./JsonSerializer.hpp"
#include "./Prettyfier.hpp"
namespace ARDUINOJSON_NAMESPACE { namespace ARDUINOJSON_NAMESPACE {
template <typename TPrint> template <typename TWriter>
class PrettyJsonSerializer_Base { class PrettyJsonSerializer : public JsonSerializer<TWriter> {
public: typedef JsonSerializer<TWriter> base;
PrettyJsonSerializer_Base(TPrint &output)
: _indentedPrint(output), _prettyfier(_indentedPrint) {}
protected:
IndentedPrint<TPrint> _indentedPrint;
Prettyfier<TPrint> _prettyfier;
};
template <typename TPrint>
class PrettyJsonSerializer : PrettyJsonSerializer_Base<TPrint>,
public JsonSerializer<Prettyfier<TPrint> > {
public: public:
PrettyJsonSerializer(TPrint &output) PrettyJsonSerializer(TWriter &writer) : base(writer), _nesting(0) {}
: PrettyJsonSerializer_Base<TPrint>(output),
JsonSerializer<Prettyfier<TPrint> >( void visitArray(const CollectionData &array) {
PrettyJsonSerializer_Base<TPrint>::_prettyfier) {} 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> 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); 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 { namespace ARDUINOJSON_NAMESPACE {
template <typename TWriter> template <typename TWriter>
class JsonWriter { class TextFormatter {
public: 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. // Returns the number of bytes sent to the TWriter implementation.
size_t bytesWritten() const { size_t bytesWritten() const {
return _length; return _length;
} }
void beginArray() {
writeRaw('[');
}
void endArray() {
writeRaw(']');
}
void beginObject() {
writeRaw('{');
}
void endObject() {
writeRaw('}');
}
void writeColon() {
writeRaw(':');
}
void writeComma() {
writeRaw(',');
}
void writeBoolean(bool value) { void writeBoolean(bool value) {
if (value) if (value)
writeRaw("true"); writeRaw("true");
@ -84,22 +63,27 @@ class JsonWriter {
FloatParts<T> parts(value); FloatParts<T> parts(value);
writeInteger(parts.integral); writePositiveInteger(parts.integral);
if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces); if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces);
if (parts.exponent < 0) { if (parts.exponent < 0) {
writeRaw("e-"); writeRaw("e-");
writeInteger(-parts.exponent); writePositiveInteger(-parts.exponent);
} }
if (parts.exponent > 0) { if (parts.exponent > 0) {
writeRaw('e'); writeRaw('e');
writeInteger(parts.exponent); writePositiveInteger(parts.exponent);
} }
} }
void writeNegativeInteger(UInt value) {
writeRaw('-');
writePositiveInteger(value);
}
template <typename T> template <typename T>
void writeInteger(T value) { void writePositiveInteger(T value) {
char buffer[22]; char buffer[22];
char *end = buffer + sizeof(buffer); char *end = buffer + sizeof(buffer);
char *begin = end; char *begin = end;
@ -134,10 +118,16 @@ class JsonWriter {
void writeRaw(const char *s) { void writeRaw(const char *s) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(s), strlen(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) { void writeRaw(const char *begin, const char *end) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(begin), _length += _writer.write(reinterpret_cast<const uint8_t *>(begin),
static_cast<size_t>(end - begin)); static_cast<size_t>(end - begin));
} }
template <size_t N> template <size_t N>
void writeRaw(const char (&s)[N]) { void writeRaw(const char (&s)[N]) {
_length += _writer.write(reinterpret_cast<const uint8_t *>(s), N - 1); _length += _writer.write(reinterpret_cast<const uint8_t *>(s), N - 1);
@ -151,6 +141,6 @@ class JsonWriter {
size_t _length; size_t _length;
private: private:
JsonWriter &operator=(const JsonWriter &); // cannot be assigned TextFormatter &operator=(const TextFormatter &); // cannot be assigned
}; };
} // namespace ARDUINOJSON_NAMESPACE } // namespace ARDUINOJSON_NAMESPACE

View File

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

View File

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

View File

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

View File

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