From 70739f5cfd6daa9637c4876808ad7fc46fc73124 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Wed, 23 Jan 2019 18:19:24 +0100 Subject: [PATCH] Reduced the size of the pretty JSON serializer --- CHANGELOG.md | 2 + .../JsonGeneratorExample.ino | 4 +- examples/JsonServer/JsonServer.ino | 4 +- examples/JsonUdpBeacon/JsonUdpBeacon.ino | 4 +- src/ArduinoJson/Configuration.hpp | 4 + src/ArduinoJson/Json/IndentedPrint.hpp | 69 --------- src/ArduinoJson/Json/JsonSerializer.hpp | 57 ++++--- src/ArduinoJson/Json/PrettyJsonSerializer.hpp | 73 ++++++--- src/ArduinoJson/Json/Prettyfier.hpp | 143 ------------------ .../{JsonWriter.hpp => TextFormatter.hpp} | 46 +++--- test/CMakeLists.txt | 2 +- .../CMakeLists.txt | 2 +- .../writeFloat.cpp | 8 +- .../writeString.cpp | 6 +- 14 files changed, 123 insertions(+), 301 deletions(-) delete mode 100644 src/ArduinoJson/Json/IndentedPrint.hpp delete mode 100644 src/ArduinoJson/Json/Prettyfier.hpp rename src/ArduinoJson/Json/{JsonWriter.hpp => TextFormatter.hpp} (83%) rename test/{JsonWriter => TextFormatter}/CMakeLists.txt (83%) rename test/{JsonWriter => TextFormatter}/writeFloat.cpp (93%) rename test/{JsonWriter => TextFormatter}/writeString.cpp (88%) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f1786a..43e37638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 > diff --git a/examples/JsonGeneratorExample/JsonGeneratorExample.ino b/examples/JsonGeneratorExample/JsonGeneratorExample.ino index 81f12375..24362f6a 100644 --- a/examples/JsonGeneratorExample/JsonGeneratorExample.ino +++ b/examples/JsonGeneratorExample/JsonGeneratorExample.ino @@ -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", diff --git a/examples/JsonServer/JsonServer.ino b/examples/JsonServer/JsonServer.ino index d1350904..8356044d 100644 --- a/examples/JsonServer/JsonServer.ino +++ b/examples/JsonServer/JsonServer.ino @@ -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(); diff --git a/examples/JsonUdpBeacon/JsonUdpBeacon.ino b/examples/JsonUdpBeacon/JsonUdpBeacon.ino index 850a3a2f..c636b795 100644 --- a/examples/JsonUdpBeacon/JsonUdpBeacon.ino +++ b/examples/JsonUdpBeacon/JsonUdpBeacon.ino @@ -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(); diff --git a/src/ArduinoJson/Configuration.hpp b/src/ArduinoJson/Configuration.hpp index 3b9a4f7b..accff366 100644 --- a/src/ArduinoJson/Configuration.hpp +++ b/src/ArduinoJson/Configuration.hpp @@ -140,3 +140,7 @@ #define ARDUINOJSON_LITTLE_ENDIAN 0 #endif #endif + +#ifndef ARDUINOJSON_TAB +#define ARDUINOJSON_TAB " " +#endif diff --git a/src/ArduinoJson/Json/IndentedPrint.hpp b/src/ArduinoJson/Json/IndentedPrint.hpp deleted file mode 100644 index 68ec5c3a..00000000 --- a/src/ArduinoJson/Json/IndentedPrint.hpp +++ /dev/null @@ -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 -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 diff --git a/src/ArduinoJson/Json/JsonSerializer.hpp b/src/ArduinoJson/Json/JsonSerializer.hpp index a959926c..1c96c0d2 100644 --- a/src/ArduinoJson/Json/JsonSerializer.hpp +++ b/src/ArduinoJson/Json/JsonSerializer.hpp @@ -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 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 _writer; + TextFormatter _formatter; }; template diff --git a/src/ArduinoJson/Json/PrettyJsonSerializer.hpp b/src/ArduinoJson/Json/PrettyJsonSerializer.hpp index 307a81d0..872cd85e 100644 --- a/src/ArduinoJson/Json/PrettyJsonSerializer.hpp +++ b/src/ArduinoJson/Json/PrettyJsonSerializer.hpp @@ -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 -class PrettyJsonSerializer_Base { - public: - PrettyJsonSerializer_Base(TPrint &output) - : _indentedPrint(output), _prettyfier(_indentedPrint) {} +template +class PrettyJsonSerializer : public JsonSerializer { + typedef JsonSerializer base; - protected: - IndentedPrint _indentedPrint; - Prettyfier _prettyfier; -}; - -template -class PrettyJsonSerializer : PrettyJsonSerializer_Base, - public JsonSerializer > { public: - PrettyJsonSerializer(TPrint &output) - : PrettyJsonSerializer_Base(output), - JsonSerializer >( - PrettyJsonSerializer_Base::_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 -size_t serializeJsonPretty(TSource &source, TDestination &destination) { +size_t serializeJsonPretty(const TSource &source, TDestination &destination) { return serialize(source, destination); } diff --git a/src/ArduinoJson/Json/Prettyfier.hpp b/src/ArduinoJson/Json/Prettyfier.hpp deleted file mode 100644 index 4da7ce76..00000000 --- a/src/ArduinoJson/Json/Prettyfier.hpp +++ /dev/null @@ -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 -class Prettyfier { - public: - explicit Prettyfier(IndentedPrint& 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(c)); - } - - template - size_t write(const char (&s)[N]) { - return _sink.write(reinterpret_cast(s), N - 1); - } - - char _previousChar; - IndentedPrint& _sink; - bool _inString; -}; -} // namespace ARDUINOJSON_NAMESPACE diff --git a/src/ArduinoJson/Json/JsonWriter.hpp b/src/ArduinoJson/Json/TextFormatter.hpp similarity index 83% rename from src/ArduinoJson/Json/JsonWriter.hpp rename to src/ArduinoJson/Json/TextFormatter.hpp index a1e57759..abb910a3 100644 --- a/src/ArduinoJson/Json/JsonWriter.hpp +++ b/src/ArduinoJson/Json/TextFormatter.hpp @@ -14,36 +14,15 @@ namespace ARDUINOJSON_NAMESPACE { template -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 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 - 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(s), strlen(s)); } + + void writeRaw(const char *s, size_t n) { + _length += _writer.write(reinterpret_cast(s), n); + } + void writeRaw(const char *begin, const char *end) { _length += _writer.write(reinterpret_cast(begin), static_cast(end - begin)); } + template void writeRaw(const char (&s)[N]) { _length += _writer.write(reinterpret_cast(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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5ed00b24..7f578ba8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/JsonWriter/CMakeLists.txt b/test/TextFormatter/CMakeLists.txt similarity index 83% rename from test/JsonWriter/CMakeLists.txt rename to test/TextFormatter/CMakeLists.txt index 12a12bfd..be28de3e 100644 --- a/test/JsonWriter/CMakeLists.txt +++ b/test/TextFormatter/CMakeLists.txt @@ -8,4 +8,4 @@ add_executable(JsonWriterTests ) target_link_libraries(JsonWriterTests catch) -add_test(JsonWriter JsonWriterTests) +add_test(TextFormatter JsonWriterTests) diff --git a/test/JsonWriter/writeFloat.cpp b/test/TextFormatter/writeFloat.cpp similarity index 93% rename from test/JsonWriter/writeFloat.cpp rename to test/TextFormatter/writeFloat.cpp index e71854a4..f44a5d57 100644 --- a/test/JsonWriter/writeFloat.cpp +++ b/test/TextFormatter/writeFloat.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include using namespace ARDUINOJSON_NAMESPACE; @@ -15,13 +15,13 @@ template void check(TFloat input, const std::string& expected) { std::string output; DynamicStringWriter sb(output); - JsonWriter > writer(sb); + TextFormatter > 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(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(3.14159265359f, "3.141593"); } diff --git a/test/JsonWriter/writeString.cpp b/test/TextFormatter/writeString.cpp similarity index 88% rename from test/JsonWriter/writeString.cpp rename to test/TextFormatter/writeString.cpp index ce2d398d..2bfecb5a 100644 --- a/test/JsonWriter/writeString.cpp +++ b/test/TextFormatter/writeString.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include 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 writer(sb); + TextFormatter 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"); }