From 7a40711af3af4d329dc3bc04953699433bc12fc9 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Tue, 7 Oct 2014 11:22:10 +0200 Subject: [PATCH] Added JsonContainer::prettyPrintTo() --- srcs/Internals/CompactJsonWriter.cpp | 1 + srcs/Internals/CompactJsonWriter.h | 43 +++++++++ srcs/Internals/IndentedPrint.cpp | 45 +++++++++ srcs/Internals/IndentedPrint.h | 53 +++++++++++ srcs/Internals/JsonNode.cpp | 61 ++++++++---- srcs/Internals/JsonWriter.cpp | 51 +--------- srcs/Internals/JsonWriter.h | 43 +++++---- srcs/Internals/PrettyJsonWriter.cpp | 1 + srcs/Internals/PrettyJsonWriter.h | 54 +++++++++++ srcs/JsonArray.cpp | 13 +++ srcs/JsonArray.h | 2 + srcs/JsonContainer.cpp | 24 ++++- srcs/JsonContainer.h | 8 ++ srcs/srcs.vcxproj | 6 ++ srcs/srcs.vcxproj.filters | 18 ++++ tests/JsonArray_PrettyPrintTo_Tests.cpp | 93 +++++++++++++++++++ ..._Tests.cpp => JsonArray_PrintTo_Tests.cpp} | 34 +++---- tests/tests.vcxproj | 3 +- tests/tests.vcxproj.filters | 9 +- 19 files changed, 454 insertions(+), 108 deletions(-) create mode 100644 srcs/Internals/CompactJsonWriter.cpp create mode 100644 srcs/Internals/CompactJsonWriter.h create mode 100644 srcs/Internals/IndentedPrint.cpp create mode 100644 srcs/Internals/IndentedPrint.h create mode 100644 srcs/Internals/PrettyJsonWriter.cpp create mode 100644 srcs/Internals/PrettyJsonWriter.h create mode 100644 tests/JsonArray_PrettyPrintTo_Tests.cpp rename tests/{JsonArray_Serialization_Tests.cpp => JsonArray_PrintTo_Tests.cpp} (62%) diff --git a/srcs/Internals/CompactJsonWriter.cpp b/srcs/Internals/CompactJsonWriter.cpp new file mode 100644 index 00000000..18c74747 --- /dev/null +++ b/srcs/Internals/CompactJsonWriter.cpp @@ -0,0 +1 @@ +#include "CompactJsonWriter.h" diff --git a/srcs/Internals/CompactJsonWriter.h b/srcs/Internals/CompactJsonWriter.h new file mode 100644 index 00000000..4af2df15 --- /dev/null +++ b/srcs/Internals/CompactJsonWriter.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../Internals/JsonWriter.h" + +class CompactJsonWriter : public JsonWriter +{ +public: + explicit CompactJsonWriter(Print& sink) + : JsonWriter(sink) + { + } + + virtual void beginArray() + { + _length += _sink.write('['); + } + + virtual void endArray() + { + _length += _sink.write(']'); + } + + virtual void writeColon() + { + _length += _sink.write(':'); + } + + virtual void writeComma() + { + _length += _sink.write(','); + } + + virtual void beginObject() + { + _length += _sink.write('{'); + } + + virtual void endObject() + { + _length += _sink.write('}'); + } +}; + diff --git a/srcs/Internals/IndentedPrint.cpp b/srcs/Internals/IndentedPrint.cpp new file mode 100644 index 00000000..75032831 --- /dev/null +++ b/srcs/Internals/IndentedPrint.cpp @@ -0,0 +1,45 @@ +#include "IndentedPrint.h" + +using namespace ArduinoJson::Generator; + +void IndentedPrint::indent() +{ + if (level < MAX_LEVEL) + level++; +} + +void IndentedPrint::unindent() +{ + if (level > 0) + level--; +} + +void IndentedPrint::setTabSize(uint8_t n) +{ + if (n < MAX_TAB_SIZE) + tabSize = n; +} + +size_t IndentedPrint::write(uint8_t c) +{ + size_t n = 0; + + if (isNewLine) + n += writeTabs(); + + n += sink.write(c); + + isNewLine = c == '\n'; + + return n; +} + +inline size_t IndentedPrint::writeTabs() +{ + size_t n = 0; + + for (int i = 0; i < level*tabSize; i++) + n += sink.write(' '); + + return n; +} \ No newline at end of file diff --git a/srcs/Internals/IndentedPrint.h b/srcs/Internals/IndentedPrint.h new file mode 100644 index 00000000..2f6f0500 --- /dev/null +++ b/srcs/Internals/IndentedPrint.h @@ -0,0 +1,53 @@ +/* +* Arduino JSON library +* Benoit Blanchon 2014 - MIT License +*/ + +#pragma once + +#include "../Arduino/Print.h" + +namespace ArduinoJson +{ + namespace Generator + { + // Decorator on top of Print to allow indented output. + // This class is used by JsonPrintable::prettyPrintTo() but can also be used + // for your own purpose, like logging. + class IndentedPrint : public Print + { + public: + + IndentedPrint(Print& p) + : sink(p) + { + level = 0; + tabSize = 2; + isNewLine = true; + } + + virtual size_t write(uint8_t); + + // Adds one level of indentation + void indent(); + + // Removes one level of indentation + void unindent(); + + // Set the number of space printed for each level of indentation + void setTabSize(uint8_t n); + + private: + Print& sink; + uint8_t level : 4; + uint8_t tabSize : 3; + bool isNewLine : 1; + + size_t writeTabs(); + + 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 + }; + } +} + diff --git a/srcs/Internals/JsonNode.cpp b/srcs/Internals/JsonNode.cpp index 004b8d17..f431c710 100644 --- a/srcs/Internals/JsonNode.cpp +++ b/srcs/Internals/JsonNode.cpp @@ -17,15 +17,15 @@ void JsonNode::writeTo(JsonWriter& writer) break; case JSON_STRING: - writer.writeValue(content.asString); + writer.writeString(content.asString); break; case JSON_INTEGER: - writer.writeValue(content.asInteger); + writer.writeInteger(content.asInteger); break; case JSON_BOOLEAN: - writer.writeValue(content.asBoolean); + writer.writeBoolean(content.asBoolean); break; case JSON_PROXY: @@ -33,40 +33,61 @@ void JsonNode::writeTo(JsonWriter& writer) break; default: // >= JSON_DOUBLE_0_DECIMALS - writer.writeValue(content.asDouble, type - JSON_DOUBLE_0_DECIMALS); + writer.writeDouble(content.asDouble, type - JSON_DOUBLE_0_DECIMALS); break; } } void JsonNode::writeArrayTo(JsonWriter& writer) { - writer.beginArray(); - JsonNode* child = content.asContainer.child; - - while(child) + + if (child) { - child->writeTo(writer); + writer.beginArray(); - child = child->next; + while (true) + { + child->writeTo(writer); + + child = child->next; + if (!child) break; + + writer.writeComma(); + } + + writer.endArray(); + } + else + { + writer.writeEmptyArray(); } - - writer.endArray(); } void JsonNode::writeObjectTo(JsonWriter& writer) { - writer.beginObject(); - JsonNode* child = content.asContainer.child; - while (child) + if (child) { - writer.writeKey(child->content.asKey.key); - child->content.asKey.value->writeTo(writer); + writer.beginObject(); - child = child->next; + while (true) + { + writer.writeString(child->content.asKey.key); + writer.writeColon(); + child->content.asKey.value->writeTo(writer); + + child = child->next; + if (!child) break; + + writer.writeComma(); + } + + writer.endObject(); + } + else + { + writer.writeEmptyObject(); } - - writer.endObject(); } \ No newline at end of file diff --git a/srcs/Internals/JsonWriter.cpp b/srcs/Internals/JsonWriter.cpp index 536bda63..d59f067b 100644 --- a/srcs/Internals/JsonWriter.cpp +++ b/srcs/Internals/JsonWriter.cpp @@ -3,64 +3,23 @@ using namespace ArduinoJson::Internals; -void JsonWriter::beginArray() +void JsonWriter::writeString(char const* value) { - writeCommaIfNeeded(); - _length += _sink.write('['); - _isCommaNeeded = false; -} - -void JsonWriter::endArray() -{ - _length += _sink.write(']'); - _isCommaNeeded = true; -} - -void JsonWriter::beginObject() -{ - writeCommaIfNeeded(); - _length += _sink.write('{'); - _isCommaNeeded = false; -} - -void JsonWriter::endObject() -{ - _length += _sink.write('}'); - _isCommaNeeded = true; -} - -void JsonWriter::writeKey(char const* key) -{ - writeCommaIfNeeded(); - _length += EscapedString::printTo(key, _sink); - _length += _sink.write(':'); - _isCommaNeeded = false; -} - -void JsonWriter::writeValue(char const* value) -{ - writeCommaIfNeeded(); _length += EscapedString::printTo(value, _sink); - _isCommaNeeded = true; } -void JsonWriter::writeValue(long value) +void JsonWriter::writeInteger(long value) { - writeCommaIfNeeded(); + _length += _sink.print(value); - _isCommaNeeded = true; } -void JsonWriter::writeValue(bool value) +void JsonWriter::writeBoolean(bool value) { - writeCommaIfNeeded(); _length += _sink.print(value ? "true" : "false"); - _isCommaNeeded = true; } -void JsonWriter::writeValue(double value, int decimals) +void JsonWriter::writeDouble(double value, int decimals) { - writeCommaIfNeeded(); _length += _sink.print(value, decimals); - _isCommaNeeded = true; } \ No newline at end of file diff --git a/srcs/Internals/JsonWriter.h b/srcs/Internals/JsonWriter.h index 7a65b3a2..5dfc6b5b 100644 --- a/srcs/Internals/JsonWriter.h +++ b/srcs/Internals/JsonWriter.h @@ -6,7 +6,7 @@ class JsonWriter { public: explicit JsonWriter(Print& sink) - : _sink(sink), _length(0), _isCommaNeeded(false) + : _sink(sink), _length(0) { } @@ -15,28 +15,35 @@ public: return _length; } - void beginArray(); - void endArray(); + virtual void beginArray() = 0; - void beginObject(); - void endObject(); + virtual void endArray() = 0; - void writeKey(const char* key); + virtual void beginObject() = 0; - void writeValue(const char* value); - void writeValue(long value); - void writeValue(bool value); - void writeValue(double value, int decimals); + virtual void endObject() = 0; -private: + void writeString(const char* value); + void writeInteger(long value); + void writeBoolean(bool value); + void writeDouble(double value, int decimals); + + virtual void writeColon() = 0; + + virtual void writeComma() = 0; + + void writeEmptyArray() + { + _length += _sink.print("[]"); + } + + void writeEmptyObject() + { + _length += _sink.print("{}"); + } + +protected: Print& _sink; size_t _length; - bool _isCommaNeeded; - - void writeCommaIfNeeded() - { - if (_isCommaNeeded) - _length += _sink.write(','); - } }; diff --git a/srcs/Internals/PrettyJsonWriter.cpp b/srcs/Internals/PrettyJsonWriter.cpp new file mode 100644 index 00000000..7965d675 --- /dev/null +++ b/srcs/Internals/PrettyJsonWriter.cpp @@ -0,0 +1 @@ +#include "PrettyJsonWriter.h" diff --git a/srcs/Internals/PrettyJsonWriter.h b/srcs/Internals/PrettyJsonWriter.h new file mode 100644 index 00000000..d4fe18c5 --- /dev/null +++ b/srcs/Internals/PrettyJsonWriter.h @@ -0,0 +1,54 @@ +#pragma once + +#include "JsonWriter.h" +#include "IndentedPrint.h" + +using namespace ArduinoJson::Generator; + +class PrettyJsonWriter : public JsonWriter +{ +public: + explicit PrettyJsonWriter(IndentedPrint& sink) + : JsonWriter(sink), _indenter(sink) + { + } + + virtual void beginArray() + { + _length += _sink.write('['); + _indenter.indent(); + _length += _indenter.println(); + } + + virtual void endArray() + { + _length += _indenter.println(); + _indenter.unindent(); + _length += _sink.write(']'); + } + + virtual void writeColon() + { + _length += _sink.print(": "); + } + + virtual void writeComma() + { + _length += _sink.write(','); + _length += _indenter.println(); + } + + virtual void beginObject() + { + _length += _sink.write('{'); + } + + virtual void endObject() + { + _length += _sink.write('}'); + _indenter.unindent(); + } + +private: + IndentedPrint& _indenter; +}; diff --git a/srcs/JsonArray.cpp b/srcs/JsonArray.cpp index 5286ce75..2c8aba1c 100644 --- a/srcs/JsonArray.cpp +++ b/srcs/JsonArray.cpp @@ -56,4 +56,17 @@ void JsonArray::add(JsonContainer& innerContainer) node->content.asProxy.target = innerContainer._node; addChild(node); +} + +JsonArray JsonArray::createNestedArray() +{ + JsonNode* node = createNode(JSON_ARRAY); + + if (node) + { + node->content.asContainer.buffer = _node->content.asContainer.buffer; + addChild(node); + } + + return JsonArray(node); } \ No newline at end of file diff --git a/srcs/JsonArray.h b/srcs/JsonArray.h index 7d694a06..69b2675c 100644 --- a/srcs/JsonArray.h +++ b/srcs/JsonArray.h @@ -22,5 +22,7 @@ public: void add(int value) { add((long) value); } void add(long value); void add(JsonContainer& innerContainer); + + JsonArray createNestedArray(); }; diff --git a/srcs/JsonContainer.cpp b/srcs/JsonContainer.cpp index 77628861..1e2462f2 100644 --- a/srcs/JsonContainer.cpp +++ b/srcs/JsonContainer.cpp @@ -1,24 +1,42 @@ #include "JsonContainer.h" #include "JsonBuffer.h" -#include "Internals/JsonWriter.h" #include "Internals/StringBuilder.h" +#include "Internals/CompactJsonWriter.h" +#include "Internals/PrettyJsonWriter.h" + +using namespace ArduinoJson::Internals; size_t JsonContainer::printTo(char* buffer, size_t bufferSize) const { - ArduinoJson::Internals::StringBuilder sb(buffer, bufferSize); + StringBuilder sb(buffer, bufferSize); return printTo(sb); } size_t JsonContainer::printTo(Print& p) const { - JsonWriter writer(p); + CompactJsonWriter writer(p); + _node->writeTo(writer); + return writer.bytesWritten(); +} + +size_t JsonContainer::prettyPrintTo(char* buffer, size_t bufferSize) const +{ + StringBuilder sb(buffer, bufferSize); + return prettyPrintTo(sb); +} + +size_t JsonContainer::prettyPrintTo(IndentedPrint& p) const +{ + PrettyJsonWriter writer(p); _node->writeTo(writer); return writer.bytesWritten(); } JsonNode* JsonContainer::createNode(JsonNodeType type) { + if (!_node) return 0; + JsonBuffer* buffer = _node->content.asContainer.buffer; return buffer->createNode(type); } diff --git a/srcs/JsonContainer.h b/srcs/JsonContainer.h index 5b4ac9d7..28bce5a5 100644 --- a/srcs/JsonContainer.h +++ b/srcs/JsonContainer.h @@ -3,6 +3,7 @@ #include "Arduino/Printable.h" #include "Internals/JsonNodeIterator.h" #include "Internals/JsonNode.h" +#include "Internals/IndentedPrint.h" struct JsonNode; class JsonValue; @@ -31,6 +32,13 @@ public: size_t printTo(char* buffer, size_t bufferSize) const; virtual size_t printTo(Print& print) const; + size_t prettyPrintTo(char* buffer, size_t bufferSize) const; + size_t prettyPrintTo(ArduinoJson::Generator::IndentedPrint& print) const; + size_t prettyPrintTo(Print& print) const + { + return prettyPrintTo(ArduinoJson::Generator::IndentedPrint(print)); + } + protected: JsonNodeIterator beginChildren() const diff --git a/srcs/srcs.vcxproj b/srcs/srcs.vcxproj index 36440c22..44dc01ff 100644 --- a/srcs/srcs.vcxproj +++ b/srcs/srcs.vcxproj @@ -74,7 +74,9 @@ + + @@ -85,11 +87,14 @@ + + + @@ -99,6 +104,7 @@ + diff --git a/srcs/srcs.vcxproj.filters b/srcs/srcs.vcxproj.filters index 35f07402..f0fff5aa 100644 --- a/srcs/srcs.vcxproj.filters +++ b/srcs/srcs.vcxproj.filters @@ -54,6 +54,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -86,5 +95,14 @@ Source Files + + Source Files + + + Source Files + + + Source Files + \ No newline at end of file diff --git a/tests/JsonArray_PrettyPrintTo_Tests.cpp b/tests/JsonArray_PrettyPrintTo_Tests.cpp new file mode 100644 index 00000000..4debcf4b --- /dev/null +++ b/tests/JsonArray_PrettyPrintTo_Tests.cpp @@ -0,0 +1,93 @@ +/* +* Arduino JSON library +* Benoit Blanchon 2014 - MIT License +*/ + +#include +#include +#include +#include + +class JsonArray_PrettyPrintTo_Tests : public testing::Test +{ +protected: + JsonArray array; + StaticJsonBuffer<30> json; + + virtual void SetUp() + { + array = json.createArray(); + } + + void outputMustBe(const char* expected) + { + size_t n = array.prettyPrintTo(buffer, sizeof(buffer)); + EXPECT_STREQ(expected, buffer); + EXPECT_EQ(strlen(expected), n); + } + +private: + char buffer[256]; +}; + +TEST_F(JsonArray_PrettyPrintTo_Tests, Empty) +{ + outputMustBe("[]"); +} + +TEST_F(JsonArray_PrettyPrintTo_Tests, OneElement) +{ + array.add(1); + + outputMustBe( + "[\r\n" + " 1\r\n" + "]"); +} + +TEST_F(JsonArray_PrettyPrintTo_Tests, TwoElements) +{ + array.add(1); + array.add(2); + + outputMustBe( + "[\r\n" + " 1,\r\n" + " 2\r\n" + "]"); +} + +TEST_F(JsonArray_PrettyPrintTo_Tests, EmptyNestedArrays) +{ + array.createNestedArray(); + array.createNestedArray(); + + outputMustBe( + "[\r\n" + " [],\r\n" + " []\r\n" + "]"); +} + +TEST_F(JsonArray_PrettyPrintTo_Tests, NestedArrays) +{ + JsonArray nested1 = array.createNestedArray(); + nested1.add(1); + nested1.add(2); + + JsonArray nested2 = array.createNestedArray(); + nested2.add(3); + nested2.add(4); + + outputMustBe( + "[\r\n" + " [\r\n" + " 1,\r\n" + " 2\r\n" + " ],\r\n" + " [\r\n" + " 3,\r\n" + " 4\r\n" + " ]\r\n" + "]"); +} \ No newline at end of file diff --git a/tests/JsonArray_Serialization_Tests.cpp b/tests/JsonArray_PrintTo_Tests.cpp similarity index 62% rename from tests/JsonArray_Serialization_Tests.cpp rename to tests/JsonArray_PrintTo_Tests.cpp index 13c0f4c3..71246ff3 100644 --- a/tests/JsonArray_Serialization_Tests.cpp +++ b/tests/JsonArray_PrintTo_Tests.cpp @@ -8,7 +8,7 @@ #include #include -class JsonArray_Serialization_Tests : public testing::Test +class JsonArray_PrintTo_Tests : public testing::Test { protected: JsonArray array; @@ -30,26 +30,26 @@ private: char buffer[256]; }; -TEST_F(JsonArray_Serialization_Tests, Empty) +TEST_F(JsonArray_PrintTo_Tests, Empty) { outputMustBe("[]"); } -TEST_F(JsonArray_Serialization_Tests, Null) +TEST_F(JsonArray_PrintTo_Tests, Null) { array.add((char*) 0); outputMustBe("[null]"); } -TEST_F(JsonArray_Serialization_Tests, OneString) +TEST_F(JsonArray_PrintTo_Tests, OneString) { array.add("hello"); outputMustBe("[\"hello\"]"); } -TEST_F(JsonArray_Serialization_Tests, TwoStrings) +TEST_F(JsonArray_PrintTo_Tests, TwoStrings) { array.add("hello"); array.add("world"); @@ -57,7 +57,7 @@ TEST_F(JsonArray_Serialization_Tests, TwoStrings) outputMustBe("[\"hello\",\"world\"]"); } -TEST_F(JsonArray_Serialization_Tests, OneStringOverCapacity) +TEST_F(JsonArray_PrintTo_Tests, OneStringOverCapacity) { array.add("hello"); array.add("world"); @@ -66,26 +66,26 @@ TEST_F(JsonArray_Serialization_Tests, OneStringOverCapacity) outputMustBe("[\"hello\",\"world\"]"); } -TEST_F(JsonArray_Serialization_Tests, OneDoubleDefaultDigits) +TEST_F(JsonArray_PrintTo_Tests, OneDoubleDefaultDigits) { array.add(3.14159265358979323846); outputMustBe("[3.14]"); } -TEST_F(JsonArray_Serialization_Tests, OneDoubleFourDigits) +TEST_F(JsonArray_PrintTo_Tests, OneDoubleFourDigits) { array.add(3.14159265358979323846, 4); outputMustBe("[3.1416]"); } -TEST_F(JsonArray_Serialization_Tests, OneInteger) +TEST_F(JsonArray_PrintTo_Tests, OneInteger) { array.add(1); outputMustBe("[1]"); } -TEST_F(JsonArray_Serialization_Tests, TwoIntegers) +TEST_F(JsonArray_PrintTo_Tests, TwoIntegers) { array.add(1); array.add(2); @@ -93,7 +93,7 @@ TEST_F(JsonArray_Serialization_Tests, TwoIntegers) outputMustBe("[1,2]"); } -TEST_F(JsonArray_Serialization_Tests, OneIntegerOverCapacity) +TEST_F(JsonArray_PrintTo_Tests, OneIntegerOverCapacity) { array.add(1); array.add(2); @@ -102,21 +102,21 @@ TEST_F(JsonArray_Serialization_Tests, OneIntegerOverCapacity) outputMustBe("[1,2]"); } -TEST_F(JsonArray_Serialization_Tests, OneTrue) +TEST_F(JsonArray_PrintTo_Tests, OneTrue) { array.add(true); outputMustBe("[true]"); } -TEST_F(JsonArray_Serialization_Tests, OneFalse) +TEST_F(JsonArray_PrintTo_Tests, OneFalse) { array.add(false); outputMustBe("[false]"); } -TEST_F(JsonArray_Serialization_Tests, TwoBooleans) +TEST_F(JsonArray_PrintTo_Tests, TwoBooleans) { array.add(false); array.add(true); @@ -124,7 +124,7 @@ TEST_F(JsonArray_Serialization_Tests, TwoBooleans) outputMustBe("[false,true]"); } -TEST_F(JsonArray_Serialization_Tests, OneBooleanOverCapacity) +TEST_F(JsonArray_PrintTo_Tests, OneBooleanOverCapacity) { array.add(false); array.add(true); @@ -133,7 +133,7 @@ TEST_F(JsonArray_Serialization_Tests, OneBooleanOverCapacity) outputMustBe("[false,true]"); } -TEST_F(JsonArray_Serialization_Tests, OneEmptyNestedArray) +TEST_F(JsonArray_PrintTo_Tests, OneEmptyNestedArray) { JsonArray nestedArray = json.createArray(); @@ -142,7 +142,7 @@ TEST_F(JsonArray_Serialization_Tests, OneEmptyNestedArray) outputMustBe("[[]]"); } -TEST_F(JsonArray_Serialization_Tests, OneEmptyNestedHash) +TEST_F(JsonArray_PrintTo_Tests, OneEmptyNestedHash) { JsonObject nestedObject = json.createObject(); diff --git a/tests/tests.vcxproj b/tests/tests.vcxproj index d8a5b23e..1242bd17 100644 --- a/tests/tests.vcxproj +++ b/tests/tests.vcxproj @@ -87,7 +87,8 @@ - + + diff --git a/tests/tests.vcxproj.filters b/tests/tests.vcxproj.filters index 19a88c17..3b1e3201 100644 --- a/tests/tests.vcxproj.filters +++ b/tests/tests.vcxproj.filters @@ -36,9 +36,6 @@ Source Files - - Source Files - Source Files @@ -48,5 +45,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file