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: diff --git a/JsonParser/JsonToken.cpp b/JsonParser/JsonToken.cpp index 0d60dfcc4..27685e3fa 100644 --- a/JsonParser/JsonToken.cpp +++ b/JsonParser/JsonToken.cpp @@ -7,6 +7,50 @@ using namespace ArduinoJson::Parser; +char* JsonToken::getText() +{ + char* s = json + token->start; + json[token->end] = 0; + + unescapeString(s); + + return s; +} + +inline void JsonToken::unescapeString(char* s) +{ + char* readPtr = s; + char* writePtr = s; + char c; + + do + { + c = *readPtr++; + + if (c == '\\') + { + c = unescapeChar(*readPtr++); + } + + *writePtr++ = c; + + } while (c != 0); +} + +inline char JsonToken::unescapeChar(char c) +{ + // Optimized for code size on a 8-bit AVR + + const char* p = "b\bf\fn\nr\rt\t"; + + while (true) + { + if (p[0] == 0) return c; + if (p[0] == c) return p[1]; + p += 2; + } +} + JsonToken JsonToken::nextSibling() const { // start with current token diff --git a/JsonParser/JsonToken.h b/JsonParser/JsonToken.h index 2646541bb..3733a7cb8 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() @@ -95,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 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..55df13a0e --- /dev/null +++ b/JsonParserTests/JsonStringTests.cpp @@ -0,0 +1,107 @@ +/* +* 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(SimpleString) + { + whenInputIs("\"Hi!\""); + outputMustBe("Hi!"); + } + + TEST_METHOD(EscapedQuote) + { + 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: + + 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 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).