From d4c1b6f2c243347c10257c216d3674bfaa760a7d Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 4 Sep 2014 21:17:51 +0200 Subject: [PATCH 1/8] Extracted a class to test strings --- JsonParserTests/JsonArrayTests.cpp | 44 +++++++-------- JsonParserTests/JsonObjectTests.cpp | 6 --- JsonParserTests/JsonParserTests.vcxproj | 1 + .../JsonParserTests.vcxproj.filters | 9 ++-- JsonParserTests/JsonStringTests.cpp | 53 +++++++++++++++++++ 5 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 JsonParserTests/JsonStringTests.cpp diff --git a/JsonParserTests/JsonArrayTests.cpp b/JsonParserTests/JsonArrayTests.cpp index 8ce9878f1..a9c7721f0 100644 --- a/JsonParserTests/JsonArrayTests.cpp +++ b/JsonParserTests/JsonArrayTests.cpp @@ -11,33 +11,27 @@ using namespace ArduinoJson::Parser; namespace ArduinoJsonParserTests { - TEST_CLASS(JsonArrayTests) - { + TEST_CLASS(JsonArrayTests) + { JsonArray array; char json[256]; jsmntok_t tokens[32]; JsonParserBase parser = JsonParserBase(tokens, 32); - public: - - TEST_METHOD(EmptyString) - { - whenInputIs(""); - parseMustFail(); - } - - TEST_METHOD(TooFewClosingBrackets) - { + public: + + TEST_METHOD(TooFewClosingBrackets) + { whenInputIs("[[]"); parseMustFail(); - } + } - TEST_METHOD(TooManyClosingBrackets) - { + TEST_METHOD(TooManyClosingBrackets) + { whenInputIs("[]]"); parseMustFail(); - } - + } + TEST_METHOD(EmptyArray) { whenInputIs("[]"); @@ -55,8 +49,8 @@ namespace ArduinoJsonParserTests itemMustNotExist(0); } - TEST_METHOD(TwoIntegers) - { + TEST_METHOD(TwoIntegers) + { setTokenCountTo(3); whenInputIs("[1,2]"); @@ -66,7 +60,7 @@ namespace ArduinoJsonParserTests itemMustBe(0, 1L); itemMustBe(1, 2L); itemMustNotExist(2); - } + } TEST_METHOD(TwoBooleans) { @@ -94,8 +88,8 @@ namespace ArduinoJsonParserTests itemMustNotExist(2); } - TEST_METHOD(TwoDimensionsArray) - { + TEST_METHOD(TwoDimensionsArray) + { setTokenCountTo(7); whenInputIs("[[1,2],[3,4]]"); @@ -107,7 +101,7 @@ namespace ArduinoJsonParserTests itemMustBe(1, 0, 3L); itemMustBe(1, 1, 4L); itemMustNotExist(2); - } + } TEST_METHOD(ThreeDimensionsArray) { @@ -127,7 +121,7 @@ namespace ArduinoJsonParserTests itemMustBe(1, 1, 1, 8L); itemMustNotExist(2); } - + private: void setTokenCountTo(int n) @@ -191,5 +185,5 @@ namespace ArduinoJsonParserTests Assert::AreEqual(0L, array.getLong(index)); Assert::IsNull(array.getString(index)); } - }; + }; } \ No newline at end of file diff --git a/JsonParserTests/JsonObjectTests.cpp b/JsonParserTests/JsonObjectTests.cpp index 1649b032b..6b0e6be8a 100644 --- a/JsonParserTests/JsonObjectTests.cpp +++ b/JsonParserTests/JsonObjectTests.cpp @@ -23,12 +23,6 @@ namespace ArduinoJsonParserTests public: - TEST_METHOD(EmptyString) - { - whenInputIs(""); - parseMustFail(); - } - TEST_METHOD(EmptyHashTable) { whenInputIs("{}"); diff --git a/JsonParserTests/JsonParserTests.vcxproj b/JsonParserTests/JsonParserTests.vcxproj index 9fbfcc882..b755c6bc8 100644 --- a/JsonParserTests/JsonParserTests.vcxproj +++ b/JsonParserTests/JsonParserTests.vcxproj @@ -90,6 +90,7 @@ + diff --git a/JsonParserTests/JsonParserTests.vcxproj.filters b/JsonParserTests/JsonParserTests.vcxproj.filters index d15e6ea3e..0034267a9 100644 --- a/JsonParserTests/JsonParserTests.vcxproj.filters +++ b/JsonParserTests/JsonParserTests.vcxproj.filters @@ -15,9 +15,6 @@ - - Source Files - Source Files @@ -30,5 +27,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/JsonParserTests/JsonStringTests.cpp b/JsonParserTests/JsonStringTests.cpp new file mode 100644 index 000000000..7c22b5bc8 --- /dev/null +++ b/JsonParserTests/JsonStringTests.cpp @@ -0,0 +1,53 @@ +/* +* Arduino JSON library +* Benoit Blanchon 2014 - MIT License +*/ + +#include "CppUnitTest.h" +#include "JsonParser.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace ArduinoJson::Parser; + +namespace ArduinoJsonParserTests +{ + TEST_CLASS(JsonStringTests) + { + const char* actual; + char json[256]; + JsonParser<32> parser; + + public: + + TEST_METHOD(EmptyString) + { + whenInputIs(""); + outputMustBe(0); + } + + TEST_METHOD(JustOneQuote) + { + whenInputIs("\""); + outputMustBe(0); + } + + TEST_METHOD(EscapedQuote) + { + whenInputIs("\\\""); + outputMustBe("\""); + } + + private: + + void whenInputIs(const char* input) + { + strcpy(json, input); + actual = parser.parse(json); + } + + void outputMustBe(const char* expected) + { + Assert::AreEqual(expected, actual); + } + }; +} \ No newline at end of file From 24d173c3b97b61e05b825a88fd018f753a99d457 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 4 Sep 2014 21:30:50 +0200 Subject: [PATCH 2/8] Added tests of escaped chars --- JsonParserTests/JsonStringTests.cpp | 52 +++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/JsonParserTests/JsonStringTests.cpp b/JsonParserTests/JsonStringTests.cpp index 7c22b5bc8..d19126ef9 100644 --- a/JsonParserTests/JsonStringTests.cpp +++ b/JsonParserTests/JsonStringTests.cpp @@ -33,8 +33,56 @@ namespace ArduinoJsonParserTests TEST_METHOD(EscapedQuote) { - whenInputIs("\\\""); - outputMustBe("\""); + whenInputIs("\"12\\\"34\""); // ie 12\"34 + outputMustBe("12\"34"); + } + + TEST_METHOD(EscapedReverseSolidus) + { + whenInputIs("\"12\\\\34\""); // ie 12\\34 + outputMustBe("12\\34"); + } + + TEST_METHOD(EscapedSolidus) + { + whenInputIs("\"12\\/34\""); + outputMustBe("12/34"); + } + + TEST_METHOD(EscapedBackspace) + { + whenInputIs("\"12\\b34\""); + outputMustBe("12\b34"); + } + + TEST_METHOD(EscapedFormfeed) + { + whenInputIs("\"12\\f34\""); + outputMustBe("12\f34"); + } + + TEST_METHOD(EscapedNewline) + { + whenInputIs("\"12\\n34\""); + outputMustBe("12\n34"); + } + + TEST_METHOD(EscapedCarriageReturn) + { + whenInputIs("\"12\\r34\""); + outputMustBe("12\r34"); + } + + TEST_METHOD(EscapedTab) + { + whenInputIs("\"12\\t34\""); + outputMustBe("12\t34"); + } + + TEST_METHOD(AllEscapedCharsTogether) + { + whenInputIs("1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\""); + outputMustBe("1\"2\\3/4\b5\f6\n7\r8\t9"); } private: From 0154fc15cbd023232e5294ba1cc12428b439c1a2 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Thu, 4 Sep 2014 21:45:41 +0200 Subject: [PATCH 3/8] Added escaped char replacement --- JsonParser/JsonToken.cpp | 47 +++++++++++++++++++++++++++++ JsonParser/JsonToken.h | 6 +--- JsonParserTests/JsonStringTests.cpp | 8 ++++- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/JsonParser/JsonToken.cpp b/JsonParser/JsonToken.cpp index 0d60dfcc4..4a8c16322 100644 --- a/JsonParser/JsonToken.cpp +++ b/JsonParser/JsonToken.cpp @@ -7,6 +7,53 @@ using namespace ArduinoJson::Parser; +static char unescapeChar(char c) +{ + switch (c) + { + case 'b': return '\b'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 't': return '\t'; + default: return c; + } +} + +static void unescapeString(char* s) +{ + char* readPtr = s; + char* writePtr = s; + + while (true) + { + if (*readPtr == '\\') + { + readPtr++; + *writePtr = unescapeChar(*readPtr); + } + else + { + *writePtr = *readPtr; + } + + if (*writePtr == 0) break; + + readPtr++; + writePtr++; + } +} + +char* JsonToken::getText() +{ + char* s = json + token->start; + json[token->end] = 0; + + unescapeString(s); + + return s; +} + JsonToken JsonToken::nextSibling() const { // start with current token diff --git a/JsonParser/JsonToken.h b/JsonParser/JsonToken.h index 2646541bb..3d0686d9e 100644 --- a/JsonParser/JsonToken.h +++ b/JsonParser/JsonToken.h @@ -29,11 +29,7 @@ namespace ArduinoJson } // Get content of the JSON token - char* getText() - { - json[token->end] = 0; - return json + token->start; - } + char* getText(); // Get the number of children tokens int childrenCount() diff --git a/JsonParserTests/JsonStringTests.cpp b/JsonParserTests/JsonStringTests.cpp index d19126ef9..55df13a0e 100644 --- a/JsonParserTests/JsonStringTests.cpp +++ b/JsonParserTests/JsonStringTests.cpp @@ -31,6 +31,12 @@ namespace ArduinoJsonParserTests outputMustBe(0); } + TEST_METHOD(SimpleString) + { + whenInputIs("\"Hi!\""); + outputMustBe("Hi!"); + } + TEST_METHOD(EscapedQuote) { whenInputIs("\"12\\\"34\""); // ie 12\"34 @@ -81,7 +87,7 @@ namespace ArduinoJsonParserTests TEST_METHOD(AllEscapedCharsTogether) { - whenInputIs("1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\""); + whenInputIs("\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\""); outputMustBe("1\"2\\3/4\b5\f6\n7\r8\t9"); } From c32642e1306553f588b6fb68c41abcca5a058c32 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Sun, 7 Sep 2014 19:55:54 +0200 Subject: [PATCH 4/8] Reduced code size (-6 bytes) --- JsonParser/JsonToken.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/JsonParser/JsonToken.cpp b/JsonParser/JsonToken.cpp index 4a8c16322..fdbc3af47 100644 --- a/JsonParser/JsonToken.cpp +++ b/JsonParser/JsonToken.cpp @@ -24,24 +24,20 @@ static void unescapeString(char* s) { char* readPtr = s; char* writePtr = s; + char c; - while (true) + do { - if (*readPtr == '\\') - { - readPtr++; - *writePtr = unescapeChar(*readPtr); - } - else + c = *readPtr++; + + if (c == '\\') { - *writePtr = *readPtr; + c = unescapeChar(*readPtr++); } - if (*writePtr == 0) break; + *writePtr++ = c; - readPtr++; - writePtr++; - } + } while (c != 0); } char* JsonToken::getText() From 9d3b522e7b97b9b9446a33fb22cac21bb680ac80 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Sun, 7 Sep 2014 20:11:33 +0200 Subject: [PATCH 5/8] Reduced code size (-6 bytes) --- JsonParser/JsonToken.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/JsonParser/JsonToken.cpp b/JsonParser/JsonToken.cpp index fdbc3af47..a10412878 100644 --- a/JsonParser/JsonToken.cpp +++ b/JsonParser/JsonToken.cpp @@ -9,14 +9,15 @@ using namespace ArduinoJson::Parser; static char unescapeChar(char c) { - switch (c) + // Optimized for code size on a 8-bit AVR + + const char* p = "b\bf\fn\nr\rt\t"; + + while (true) { - case 'b': return '\b'; - case 'f': return '\f'; - case 'n': return '\n'; - case 'r': return '\r'; - case 't': return '\t'; - default: return c; + if (p[0] == 0) return c; + if (p[0] == c) return p[1]; + p += 2; } } From 286a514fbe058b18b92c43f82e93824f9702b78c Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Tue, 9 Sep 2014 21:23:37 +0200 Subject: [PATCH 6/8] Minor clean up --- JsonParser/JsonToken.cpp | 32 ++++++++++++++++---------------- JsonParser/JsonToken.h | 3 +++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/JsonParser/JsonToken.cpp b/JsonParser/JsonToken.cpp index a10412878..27685e3fa 100644 --- a/JsonParser/JsonToken.cpp +++ b/JsonParser/JsonToken.cpp @@ -7,21 +7,17 @@ using namespace ArduinoJson::Parser; -static char unescapeChar(char c) +char* JsonToken::getText() { - // Optimized for code size on a 8-bit AVR + char* s = json + token->start; + json[token->end] = 0; - const char* p = "b\bf\fn\nr\rt\t"; + unescapeString(s); - while (true) - { - if (p[0] == 0) return c; - if (p[0] == c) return p[1]; - p += 2; - } + return s; } -static void unescapeString(char* s) +inline void JsonToken::unescapeString(char* s) { char* readPtr = s; char* writePtr = s; @@ -29,7 +25,7 @@ static void unescapeString(char* s) do { - c = *readPtr++; + c = *readPtr++; if (c == '\\') { @@ -41,14 +37,18 @@ static void unescapeString(char* s) } while (c != 0); } -char* JsonToken::getText() +inline char JsonToken::unescapeChar(char c) { - char* s = json + token->start; - json[token->end] = 0; + // Optimized for code size on a 8-bit AVR - unescapeString(s); + const char* p = "b\bf\fn\nr\rt\t"; - return s; + while (true) + { + if (p[0] == 0) return c; + if (p[0] == c) return p[1]; + p += 2; + } } JsonToken JsonToken::nextSibling() const diff --git a/JsonParser/JsonToken.h b/JsonParser/JsonToken.h index 3d0686d9e..3733a7cb8 100644 --- a/JsonParser/JsonToken.h +++ b/JsonParser/JsonToken.h @@ -91,6 +91,9 @@ namespace ArduinoJson private: char* json; jsmntok_t* token; + + static char unescapeChar(char c); + static void unescapeString(char* s); }; } } \ No newline at end of file From 49d2b4b2a2ee43d61a8991c2520d36231eaf0436 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Tue, 9 Sep 2014 21:25:25 +0200 Subject: [PATCH 7/8] Updated code size --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ee5221a6f..c1a50eb54 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ It has been written with Arduino in mind, but it isn't linked to Arduino librari Features -------- -* JSON decoding: [more details here](/JsonParser/) -* JSON encoding: [more details here](/JsonGenerator/) +* JSON decoding: [see documentation here](/JsonParser/) +* JSON encoding: [see documentation here](/JsonGenerator/) * Elegant API, very easy to use * Fixed memory allocation (no malloc) * Small footprint @@ -22,9 +22,9 @@ Feature comparison | Library | Memory allocation | Nested objects | Parser size | Encoder size | | ------------ | ----------------- | -------------- | ----------- | ------------- | -| Arduino JSON | static | yes | 2642 Bytes | 862 bytes | -| json-arduino | dynamic | no | 3348 (+27%) | not supported | -| aJson | dynamic | yes | 5088 (+93%) | 4678 (+540%) | +| Arduino JSON | static | yes | 2760 Bytes | 862 bytes | +| json-arduino | dynamic | no | 3348 (+21%) | not supported | +| aJson | dynamic | yes | 5088 (+84%) | 4678 (+540%) | "Parser size" was measured with a program parsing `{"sensor":"outdoor","value":25.6}`. For each library, I wrote a program that extracts a string and a float. I subtracted the size of a program doing the same without any JSON parsing involved. [Source files are here](https://gist.github.com/bblanchon/e8ba914a7109f3642c0f). From feb6060887a36ecae8ea0358c240a123f1d5f647 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Tue, 9 Sep 2014 21:32:27 +0200 Subject: [PATCH 8/8] Updated change-log --- CHANGELOG.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e67baa5..e0817055e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ Arduino JSON: change log ======================== +v3.4 +---- + +* Fixed escaped char parsing (issue #16) + + v3.3 ---- -* Added indented output for the JSON generator, see example bellow. +* Added indented output for the JSON generator (issue #11), see example bellow. * Added `IndentedPrint`, a decorator for `Print` to allow indented output Example: @@ -23,7 +29,7 @@ v3.1 * Calling `Generator::JsonObject::add()` twice with the same `key` now replaces the `value` * Added `Generator::JsonObject::operator[]`, see bellow the new API -* Added `Generator::JsonObject::remove()` +* Added `Generator::JsonObject::remove()` (issue #9) Old generator API: @@ -44,7 +50,7 @@ v3.0 * New parser API, see bellow * Renamed `JsonHashTable` into `JsonObject` -* Added iterators for `JsonArray` and `JsonObject` +* Added iterators for `JsonArray` and `JsonObject` (issue #4) Old parser API: