From d9b1e7e810e678a6ca7198bf36f4ce78689c6659 Mon Sep 17 00:00:00 2001 From: Benoit Blanchon Date: Fri, 16 Feb 2018 11:04:07 +0100 Subject: [PATCH] Allowed non-quoted key to contain underscores (fixes #665) --- CHANGELOG.md | 1 + .../Deserialization/JsonParser.hpp | 12 +- .../Deserialization/JsonParserImpl.hpp | 2 +- test/JsonBuffer/parseObject.cpp | 269 +++++++++--------- 4 files changed, 150 insertions(+), 134 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 276beb93..52189081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ HEAD ---- * Fixed `JsonVariant::operator|(int)` that returned the default value if the variant contained a double (issue #675) +* Allowed non-quoted key to contain underscores (issue #665) v5.13.0 ------- diff --git a/src/ArduinoJson/Deserialization/JsonParser.hpp b/src/ArduinoJson/Deserialization/JsonParser.hpp index 2f6e6de1..c348e759 100644 --- a/src/ArduinoJson/Deserialization/JsonParser.hpp +++ b/src/ArduinoJson/Deserialization/JsonParser.hpp @@ -50,13 +50,13 @@ class JsonParser { inline bool parseObjectTo(JsonVariant *destination); inline bool parseStringTo(JsonVariant *destination); - static inline bool isInRange(char c, char min, char max) { + static inline bool isBetween(char c, char min, char max) { return min <= c && c <= max; } - static inline bool isLetterOrNumber(char c) { - return isInRange(c, '0', '9') || isInRange(c, 'a', 'z') || - isInRange(c, 'A', 'Z') || c == '+' || c == '-' || c == '.'; + static inline bool canBeInNonQuotedString(char c) { + return isBetween(c, '0', '9') || isBetween(c, '_', 'z') || + isBetween(c, 'A', 'Z') || c == '+' || c == '-' || c == '.'; } static inline bool isQuote(char c) { @@ -99,5 +99,5 @@ inline typename JsonParserBuilder::TParser makeParser( return JsonParserBuilder::makeParser(buffer, json, nestingLimit); } -} -} +} // namespace Internals +} // namespace ArduinoJson diff --git a/src/ArduinoJson/Deserialization/JsonParserImpl.hpp b/src/ArduinoJson/Deserialization/JsonParserImpl.hpp index 76253b25..33ad42e9 100644 --- a/src/ArduinoJson/Deserialization/JsonParserImpl.hpp +++ b/src/ArduinoJson/Deserialization/JsonParserImpl.hpp @@ -167,7 +167,7 @@ ArduinoJson::Internals::JsonParser::parseString() { } } else { // no quotes for (;;) { - if (!isLetterOrNumber(c)) break; + if (!canBeInNonQuotedString(c)) break; _reader.move(); str.append(c); c = _reader.current(); diff --git a/test/JsonBuffer/parseObject.cpp b/test/JsonBuffer/parseObject.cpp index 1af6bde1..3a4067c5 100644 --- a/test/JsonBuffer/parseObject.cpp +++ b/test/JsonBuffer/parseObject.cpp @@ -8,148 +8,163 @@ TEST_CASE("JsonBuffer::parseObject()") { DynamicJsonBuffer jb; - SECTION("EmptyObject") { + SECTION("An empty object") { JsonObject& obj = jb.parseObject("{}"); REQUIRE(obj.success()); REQUIRE(obj.size() == 0); } - SECTION("MissingOpeningBrace") { - JsonObject& obj = jb.parseObject("}"); - REQUIRE_FALSE(obj.success()); + SECTION("Quotes") { + SECTION("Double quotes") { + JsonObject& obj = jb.parseObject("{\"key\":\"value\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("Single quotes") { + JsonObject& obj = jb.parseObject("{'key':'value'}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("No quotes") { + JsonObject& obj = jb.parseObject("{key:value}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("No quotes, allow underscore in key") { + JsonObject& obj = jb.parseObject("{_k_e_y_:42}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["_k_e_y_"] == 42); + } } - SECTION("MissingClosingBrace") { - JsonObject& obj = jb.parseObject("{"); - REQUIRE_FALSE(obj.success()); + SECTION("Spaces") { + SECTION("Before the key") { + JsonObject& obj = jb.parseObject("{ \"key\":\"value\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("After the key") { + JsonObject& obj = jb.parseObject("{\"key\" :\"value\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("Before the value") { + JsonObject& obj = jb.parseObject("{\"key\": \"value\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("After the value") { + JsonObject& obj = jb.parseObject("{\"key\":\"value\" }"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 1); + REQUIRE(obj["key"] == "value"); + } + + SECTION("Before the colon") { + JsonObject& obj = + jb.parseObject("{\"key1\":\"value1\" ,\"key2\":\"value2\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == "value1"); + REQUIRE(obj["key2"] == "value2"); + } + + SECTION("After the colon") { + JsonObject& obj = + jb.parseObject("{\"key1\":\"value1\" ,\"key2\":\"value2\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == "value1"); + REQUIRE(obj["key2"] == "value2"); + } } - SECTION("MissingColonAndValue") { - JsonObject& obj = jb.parseObject("{\"key\"}"); - REQUIRE_FALSE(obj.success()); + SECTION("Values types") { + SECTION("String") { + JsonObject& obj = + jb.parseObject("{\"key1\":\"value1\",\"key2\":\"value2\"}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == "value1"); + REQUIRE(obj["key2"] == "value2"); + } + + SECTION("Integer") { + JsonObject& obj = jb.parseObject("{\"key1\":42,\"key2\":-42}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == 42); + REQUIRE(obj["key2"] == -42); + } + + SECTION("Double") { + JsonObject& obj = jb.parseObject("{\"key1\":12.345,\"key2\":-7E89}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == 12.345); + REQUIRE(obj["key2"] == -7E89); + } + + SECTION("Booleans") { + JsonObject& obj = jb.parseObject("{\"key1\":true,\"key2\":false}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"] == true); + REQUIRE(obj["key2"] == false); + } + + SECTION("Null") { + JsonObject& obj = jb.parseObject("{\"key1\":null,\"key2\":null}"); + REQUIRE(obj.success()); + REQUIRE(obj.size() == 2); + REQUIRE(obj["key1"].as() == 0); + REQUIRE(obj["key2"].as() == 0); + } } - SECTION("MissingQuotesAndColonAndValue") { - JsonObject& obj = jb.parseObject("{key}"); - REQUIRE_FALSE(obj.success()); - } + SECTION("Misc") { + SECTION("The opening brace is missing") { + JsonObject& obj = jb.parseObject("}"); + REQUIRE_FALSE(obj.success()); + } - SECTION("OneString") { - JsonObject& obj = jb.parseObject("{\"key\":\"value\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } + SECTION("The closing brace is missing") { + JsonObject& obj = jb.parseObject("{"); + REQUIRE_FALSE(obj.success()); + } - SECTION("OneStringSingleQuotes") { - JsonObject& obj = jb.parseObject("{'key':'value'}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } + SECTION("A quoted key without value") { + JsonObject& obj = jb.parseObject("{\"key\"}"); + REQUIRE_FALSE(obj.success()); + } - SECTION("OneStringNoQuotes") { - JsonObject& obj = jb.parseObject("{key:value}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } + SECTION("A non-quoted key without value") { + JsonObject& obj = jb.parseObject("{key}"); + REQUIRE_FALSE(obj.success()); + } - SECTION("OneStringSpaceBeforeKey") { - JsonObject& obj = jb.parseObject("{ \"key\":\"value\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } + SECTION("A dangling comma") { + JsonObject& obj = jb.parseObject("{\"key1\":\"value1\",}"); + REQUIRE_FALSE(obj.success()); + REQUIRE(obj.size() == 0); + } - SECTION("OneStringSpaceAfterKey") { - JsonObject& obj = jb.parseObject("{\"key\" :\"value\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } - - SECTION("OneStringSpaceBeforeValue") { - JsonObject& obj = jb.parseObject("{\"key\": \"value\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } - - SECTION("OneStringSpaceAfterValue") { - JsonObject& obj = jb.parseObject("{\"key\":\"value\" }"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 1); - REQUIRE(obj["key"] == "value"); - } - - SECTION("TwoStrings") { - JsonObject& obj = - jb.parseObject("{\"key1\":\"value1\",\"key2\":\"value2\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"] == "value1"); - REQUIRE(obj["key2"] == "value2"); - } - - SECTION("TwoStringsSpaceBeforeComma") { - JsonObject& obj = - jb.parseObject("{\"key1\":\"value1\" ,\"key2\":\"value2\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"] == "value1"); - REQUIRE(obj["key2"] == "value2"); - } - - SECTION("TwoStringsSpaceAfterComma") { - JsonObject& obj = - jb.parseObject("{\"key1\":\"value1\" ,\"key2\":\"value2\"}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"] == "value1"); - REQUIRE(obj["key2"] == "value2"); - } - - SECTION("EndingWithAComma") { - JsonObject& obj = jb.parseObject("{\"key1\":\"value1\",}"); - REQUIRE_FALSE(obj.success()); - REQUIRE(obj.size() == 0); - } - - SECTION("TwoIntergers") { - JsonObject& obj = jb.parseObject("{\"key1\":42,\"key2\":-42}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"] == 42); - REQUIRE(obj["key2"] == -42); - } - - SECTION("TwoDoubles") { - JsonObject& obj = jb.parseObject("{\"key1\":12.345,\"key2\":-7E89}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"] == 12.345); - REQUIRE(obj["key2"] == -7E89); - } - - SECTION("TwoBooleans") { - JsonObject& obj = jb.parseObject("{\"key1\":true,\"key2\":false}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"] == true); - REQUIRE(obj["key2"] == false); - } - - SECTION("TwoNulls") { - JsonObject& obj = jb.parseObject("{\"key1\":null,\"key2\":null}"); - REQUIRE(obj.success()); - REQUIRE(obj.size() == 2); - REQUIRE(obj["key1"].as() == 0); - REQUIRE(obj["key2"].as() == 0); - } - - SECTION("NullForKey") { - JsonObject& obj = jb.parseObject("null:\"value\"}"); - REQUIRE_FALSE(obj.success()); + SECTION("null as a key") { + JsonObject& obj = jb.parseObject("null:\"value\"}"); + REQUIRE_FALSE(obj.success()); + } } }