diff --git a/CHANGELOG.md b/CHANGELOG.md index ea26e05d..9ffaeaf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ v5.0 (currently in beta) * Implicitly call `strdup()` for `String` but not for `char*` (issues #84, #87) * Added support of non standard JSON input (issue #44) * Added support of comments in JSON input (issue #88) +* Added implicit cast between numerical types (issues #64, #69, #93) +* Added ability to read number values as string (issue #90) * Redesigned `JsonVariant` to leverage converting constructors instead of assignment operators (issue #66) * Switched to new the library layout (requires Arduino 1.0.6 or above) diff --git a/README.md b/README.md index 336233ee..83c90e8e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ It has been written with Arduino in mind, but it isn't linked to Arduino librari Features -------- -* JSON decoding +* JSON decoding (comments are supported) * JSON encoding (with optional indentation) * Elegant API, very easy to use * Efficient (no malloc, nor copy) @@ -80,4 +80,4 @@ From GitHub user `zacsketches`: --- -Found this library useful? [Help me back with a donation!](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=donate%40benoitblanchon%2efr&lc=GB&item_name=Benoit%20Blanchon&item_number=Arduino%20JSON¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted) :smile: \ No newline at end of file +Found this library useful? [Help me back with a donation!](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=donate%40benoitblanchon%2efr&lc=GB&item_name=Benoit%20Blanchon&item_number=Arduino%20JSON¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted) :smile: diff --git a/include/ArduinoJson/Arduino/Print.hpp b/include/ArduinoJson/Arduino/Print.hpp index 3e933cf8..acdc1a19 100644 --- a/include/ArduinoJson/Arduino/Print.hpp +++ b/include/ArduinoJson/Arduino/Print.hpp @@ -11,7 +11,7 @@ #include #include -// This class reproduces Arduino's Print +// This class reproduces Arduino's Print class class Print { public: virtual ~Print() {} diff --git a/include/ArduinoJson/Arduino/String.hpp b/include/ArduinoJson/Arduino/String.hpp index 3ddf246d..206750bf 100644 --- a/include/ArduinoJson/Arduino/String.hpp +++ b/include/ArduinoJson/Arduino/String.hpp @@ -10,7 +10,20 @@ #include -typedef std::string String; +// This class reproduces Arduino's String class +class String : public std::string { + public: + String(const char *cstr = "") : std::string(cstr) {} + String(const String &str) : std::string(str) {} + explicit String(char c); + explicit String(unsigned char); + explicit String(int); + explicit String(unsigned int); + explicit String(long); + explicit String(unsigned long); + explicit String(float, unsigned char decimalPlaces = 2); + explicit String(double, unsigned char decimalPlaces = 2); +}; #else diff --git a/include/ArduinoJson/Internals/DynamicStringBuilder.hpp b/include/ArduinoJson/Internals/DynamicStringBuilder.hpp new file mode 100644 index 00000000..0a4946e0 --- /dev/null +++ b/include/ArduinoJson/Internals/DynamicStringBuilder.hpp @@ -0,0 +1,29 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#pragma once + +#include "../Arduino/Print.hpp" +#include "../Arduino/String.hpp" + +namespace ArduinoJson { +namespace Internals { + +// A Print implementation that allows to write in a String +class DynamicStringBuilder : public Print { + public: + DynamicStringBuilder(String &str) : _str(str) {} + + virtual size_t write(uint8_t c) { + _str += c; + return 1; + } + + private: + String &_str; +}; +} +} diff --git a/include/ArduinoJson/Internals/JsonParser.hpp b/include/ArduinoJson/Internals/JsonParser.hpp index c6730ef0..085b75df 100644 --- a/include/ArduinoJson/Internals/JsonParser.hpp +++ b/include/ArduinoJson/Internals/JsonParser.hpp @@ -28,16 +28,12 @@ class JsonParser { private: bool skip(char charToSkip); - bool skip(const char *wordToSkip); const char *parseString(); bool parseAnythingTo(JsonVariant *destination); FORCE_INLINE bool parseAnythingToUnsafe(JsonVariant *destination); inline bool parseArrayTo(JsonVariant *destination); - inline bool parseBooleanTo(JsonVariant *destination); - inline bool parseNullTo(JsonVariant *destination); - inline bool parseNumberTo(JsonVariant *destination); inline bool parseObjectTo(JsonVariant *destination); inline bool parseStringTo(JsonVariant *destination); diff --git a/include/ArduinoJson/Internals/JsonPrintable.hpp b/include/ArduinoJson/Internals/JsonPrintable.hpp index 24bbd6c3..120ca21f 100644 --- a/include/ArduinoJson/Internals/JsonPrintable.hpp +++ b/include/ArduinoJson/Internals/JsonPrintable.hpp @@ -10,7 +10,8 @@ #include "IndentedPrint.hpp" #include "JsonWriter.hpp" #include "Prettyfier.hpp" -#include "StringBuilder.hpp" +#include "StaticStringBuilder.hpp" +#include "DynamicStringBuilder.hpp" #ifdef ARDUINOJSON_ENABLE_STD_STREAM #include "StreamPrintAdapter.hpp" @@ -33,15 +34,20 @@ class JsonPrintable { } #ifdef ARDUINOJSON_ENABLE_STD_STREAM - std::ostream& printTo(std::ostream &os) const { + std::ostream &printTo(std::ostream &os) const { StreamPrintAdapter adapter(os); printTo(adapter); return os; } -#endif +#endif size_t printTo(char *buffer, size_t bufferSize) const { - StringBuilder sb(buffer, bufferSize); + StaticStringBuilder sb(buffer, bufferSize); + return printTo(sb); + } + + size_t printTo(String &str) const { + DynamicStringBuilder sb(str); return printTo(sb); } @@ -51,7 +57,7 @@ class JsonPrintable { } size_t prettyPrintTo(char *buffer, size_t bufferSize) const { - StringBuilder sb(buffer, bufferSize); + StaticStringBuilder sb(buffer, bufferSize); return prettyPrintTo(sb); } @@ -60,6 +66,11 @@ class JsonPrintable { return prettyPrintTo(indentedPrint); } + size_t prettyPrintTo(String &str) const { + DynamicStringBuilder sb(str); + return prettyPrintTo(sb); + } + size_t measureLength() const { DummyPrint dp; return printTo(dp); @@ -75,11 +86,10 @@ class JsonPrintable { }; #ifdef ARDUINOJSON_ENABLE_STD_STREAM -template -inline std::ostream& operator<<(std::ostream& os, const JsonPrintable& v) { +template +inline std::ostream &operator<<(std::ostream &os, const JsonPrintable &v) { return v.printTo(os); } #endif - } } diff --git a/include/ArduinoJson/Internals/JsonVariantContent.hpp b/include/ArduinoJson/Internals/JsonVariantContent.hpp index a0330424..05cbf1e3 100644 --- a/include/ArduinoJson/Internals/JsonVariantContent.hpp +++ b/include/ArduinoJson/Internals/JsonVariantContent.hpp @@ -17,17 +17,11 @@ namespace Internals { // A union that defines the actual content of a JsonVariant. // The enum JsonVariantType determines which member is in use. union JsonVariantContent { - bool asBoolean; double asDouble; // asDouble is also used for float - long asLong; // asLong is also used for char, short and int + long asLong; // asLong is also used for bool, char, short and int const char* asString; // asString can be null JsonArray* asArray; // asArray cannot be null JsonObject* asObject; // asObject cannot be null - - template - T as() const; }; } } - -#include "JsonVariantContent.ipp" diff --git a/include/ArduinoJson/Internals/JsonVariantContent.ipp b/include/ArduinoJson/Internals/JsonVariantContent.ipp deleted file mode 100644 index 2c5a9ae6..00000000 --- a/include/ArduinoJson/Internals/JsonVariantContent.ipp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright Benoit Blanchon 2014-2015 -// MIT License -// -// Arduino JSON library -// https://github.com/bblanchon/ArduinoJson - -#pragma once - -namespace ArduinoJson { - -// Forward declarations -class JsonArray; -class JsonObject; - -namespace Internals { -template <> -inline bool JsonVariantContent::as() const { - return asBoolean; -} - -template <> -inline char const* JsonVariantContent::as() const { - return asString; -} - -template <> -inline double JsonVariantContent::as() const { - return asDouble; -} - -template <> -inline float JsonVariantContent::as() const { - return static_cast(asDouble); -} - -template <> -inline JsonArray& JsonVariantContent::as() const { - return *asArray; -} - -template <> -inline const JsonArray& JsonVariantContent::as() const { - return *asArray; -} - -template <> -inline JsonObject& JsonVariantContent::as() const { - return *asObject; -} - -template <> -inline const JsonObject& JsonVariantContent::as() const { - return *asObject; -} - -template <> -inline signed char JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline signed int JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline signed long JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline signed short JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline unsigned char JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline unsigned int JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline unsigned long JsonVariantContent::as() const { - return static_cast(asLong); -} - -template <> -inline unsigned short JsonVariantContent::as() const { - return static_cast(asLong); -} -} -} diff --git a/include/ArduinoJson/Internals/JsonVariantType.hpp b/include/ArduinoJson/Internals/JsonVariantType.hpp index 0f49f958..600e65db 100644 --- a/include/ArduinoJson/Internals/JsonVariantType.hpp +++ b/include/ArduinoJson/Internals/JsonVariantType.hpp @@ -16,11 +16,12 @@ namespace Internals { // The value determines which member of JsonVariantContent is used. enum JsonVariantType { JSON_UNDEFINED, // the JsonVariant has not been initialized + JSON_UNPARSED, // the JsonVariant contains an unparsed string + JSON_STRING, // the JsonVariant stores a const char* + JSON_BOOLEAN, // the JsonVariant stores a bool + JSON_LONG, // the JsonVariant stores a long JSON_ARRAY, // the JsonVariant stores a pointer to a JsonArray JSON_OBJECT, // the JsonVariant stores a pointer to a JsonObject - JSON_BOOLEAN, // the JsonVariant stores a bool - JSON_STRING, // the JsonVariant stores a const char* - JSON_LONG, // the JsonVariant stores a long // The following values are reserved for double values // Multiple values are used for double, depending on the number of decimal diff --git a/include/ArduinoJson/Internals/JsonWriter.hpp b/include/ArduinoJson/Internals/JsonWriter.hpp index d9983a3b..b14bfe8f 100644 --- a/include/ArduinoJson/Internals/JsonWriter.hpp +++ b/include/ArduinoJson/Internals/JsonWriter.hpp @@ -37,10 +37,8 @@ class JsonWriter { void writeColon() { write(':'); } void writeComma() { write(','); } - void writeBoolean(bool value) { - write(value ? "true" : "false"); - } - + void writeBoolean(bool value) { write(value ? "true" : "false"); } + void writeString(const char *value) { if (!value) { write("null"); @@ -67,6 +65,8 @@ class JsonWriter { _length += _sink.print(value, decimals); } + void writeRaw(const char *s) { return write(s); } + protected: void write(char c) { _length += _sink.write(c); } void write(const char *s) { _length += _sink.print(s); } diff --git a/include/ArduinoJson/Internals/StringBuilder.hpp b/include/ArduinoJson/Internals/StaticStringBuilder.hpp similarity index 84% rename from include/ArduinoJson/Internals/StringBuilder.hpp rename to include/ArduinoJson/Internals/StaticStringBuilder.hpp index 9c56794d..86889941 100644 --- a/include/ArduinoJson/Internals/StringBuilder.hpp +++ b/include/ArduinoJson/Internals/StaticStringBuilder.hpp @@ -12,9 +12,9 @@ namespace ArduinoJson { namespace Internals { // A Print implementation that allows to write in a char[] -class StringBuilder : public Print { +class StaticStringBuilder : public Print { public: - StringBuilder(char *buf, int size) + StaticStringBuilder(char *buf, int size) : buffer(buf), capacity(size - 1), length(0) { buffer[0] = '\0'; } diff --git a/include/ArduinoJson/Internals/Unparsed.hpp b/include/ArduinoJson/Internals/Unparsed.hpp new file mode 100644 index 00000000..91db48b1 --- /dev/null +++ b/include/ArduinoJson/Internals/Unparsed.hpp @@ -0,0 +1,20 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#pragma once + +namespace ArduinoJson { +namespace Internals { +class Unparsed { + public: + explicit Unparsed(const char* str) : _str(str) {} + operator const char*() const { return _str; } + + private: + const char* _str; +}; +} +} diff --git a/include/ArduinoJson/JsonArray.ipp b/include/ArduinoJson/JsonArray.ipp index 424a8391..99185281 100644 --- a/include/ArduinoJson/JsonArray.ipp +++ b/include/ArduinoJson/JsonArray.ipp @@ -202,4 +202,16 @@ template <> inline JsonArray const &JsonVariant::invalid() { return JsonArray::invalid(); } + +template <> +inline JsonArray &JsonVariant::as() const { + if (_type == Internals::JSON_ARRAY) return *_content.asArray; + return JsonArray::invalid(); +} + +template <> +inline const JsonArray &JsonVariant::as() const { + if (_type == Internals::JSON_ARRAY) return *_content.asArray; + return JsonArray::invalid(); +} } diff --git a/include/ArduinoJson/JsonObject.ipp b/include/ArduinoJson/JsonObject.ipp index 4db04d2e..d6d92f57 100644 --- a/include/ArduinoJson/JsonObject.ipp +++ b/include/ArduinoJson/JsonObject.ipp @@ -217,4 +217,16 @@ template <> inline JsonObject &JsonVariant::invalid() { return JsonObject::invalid(); } + +template <> +inline JsonObject &JsonVariant::as() const { + if (_type == Internals::JSON_OBJECT) return *_content.asObject; + return JsonObject::invalid(); +} + +template <> +inline const JsonObject &JsonVariant::as() const { + if (_type == Internals::JSON_OBJECT) return *_content.asObject; + return JsonObject::invalid(); +} } diff --git a/include/ArduinoJson/JsonVariant.hpp b/include/ArduinoJson/JsonVariant.hpp index 9258abcd..914a4c74 100644 --- a/include/ArduinoJson/JsonVariant.hpp +++ b/include/ArduinoJson/JsonVariant.hpp @@ -12,6 +12,7 @@ #include "Internals/JsonPrintable.hpp" #include "Internals/JsonVariantContent.hpp" #include "Internals/JsonVariantType.hpp" +#include "Internals/Unparsed.hpp" #include "JsonVariantBase.hpp" namespace ArduinoJson { @@ -55,6 +56,9 @@ class JsonVariant : public JsonVariantBase { // Create a JsonVariant containing a string. FORCE_INLINE JsonVariant(const char *value); + // Create a JsonVariant containing an unparsed string + FORCE_INLINE JsonVariant(Internals::Unparsed value); + // Create a JsonVariant containing a reference to an array. FORCE_INLINE JsonVariant(JsonArray &array); @@ -64,12 +68,12 @@ class JsonVariant : public JsonVariantBase { // Get the variant as the specified type. // See cast operators for details. template - FORCE_INLINE T as() const; + T as() const; // Tells weither the variant has the specified type. // Returns true if the variant has type type T, false otherwise. template - FORCE_INLINE bool is() const; + bool is() const; // Serialize the variant to a JsonWriter void writeTo(Internals::JsonWriter &writer) const; diff --git a/include/ArduinoJson/JsonVariant.ipp b/include/ArduinoJson/JsonVariant.ipp index ec347ba3..ae81a720 100644 --- a/include/ArduinoJson/JsonVariant.ipp +++ b/include/ArduinoJson/JsonVariant.ipp @@ -12,7 +12,7 @@ namespace ArduinoJson { inline JsonVariant::JsonVariant(bool value) { _type = Internals::JSON_BOOLEAN; - _content.asBoolean = value; + _content.asLong = value; } inline JsonVariant::JsonVariant(const char *value) { @@ -20,6 +20,11 @@ inline JsonVariant::JsonVariant(const char *value) { _content.asString = value; } +inline JsonVariant::JsonVariant(Internals::Unparsed value) { + _type = Internals::JSON_UNPARSED; + _content.asString = value; +} + inline JsonVariant::JsonVariant(double value, uint8_t decimals) { _type = static_cast( Internals::JSON_DOUBLE_0_DECIMALS + decimals); @@ -82,9 +87,61 @@ inline JsonVariant::JsonVariant(unsigned short value) { _content.asLong = value; } -template -inline T JsonVariant::as() const { - return is() ? _content.as() : invalid(); +template <> +double JsonVariant::as() const; + +template <> +long JsonVariant::as() const; + +template <> +String JsonVariant::as() const; + +template <> +const char *JsonVariant::as() const; + +template <> +inline bool JsonVariant::as() const { + return as(); +} + +template <> +inline signed char JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline unsigned char JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline signed short JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline unsigned short JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline signed int JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline unsigned int JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline unsigned long JsonVariant::as() const { + return static_cast(as()); +} + +template <> +inline float JsonVariant::as() const { + return static_cast(as()); } template @@ -97,6 +154,12 @@ inline bool JsonVariant::is() const { return false; } +template <> // in .cpp +bool JsonVariant::is() const; + +template <> // in .cpp +bool JsonVariant::is() const; + template <> inline bool JsonVariant::is() const { return _type == Internals::JSON_BOOLEAN; @@ -107,14 +170,9 @@ inline bool JsonVariant::is() const { return _type == Internals::JSON_STRING; } -template <> -inline bool JsonVariant::is() const { - return _type >= Internals::JSON_DOUBLE_0_DECIMALS; -} - template <> inline bool JsonVariant::is() const { - return _type >= Internals::JSON_DOUBLE_0_DECIMALS; + return is(); } template <> @@ -139,42 +197,37 @@ inline bool JsonVariant::is() const { template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; -} - -template <> -inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } template <> inline bool JsonVariant::is() const { - return _type == Internals::JSON_LONG; + return is(); } #ifdef ARDUINOJSON_ENABLE_STD_STREAM diff --git a/src/Arduino/String.cpp b/src/Arduino/String.cpp new file mode 100644 index 00000000..1f851fac --- /dev/null +++ b/src/Arduino/String.cpp @@ -0,0 +1,31 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#ifndef ARDUINO + +#include "../../include/ArduinoJson/Arduino/String.hpp" + +#include // for sprintf() + +String::String(double value, unsigned char digits) { + char tmp[32]; + sprintf(tmp, "%.*f", digits, value); + *this = tmp; +} + +String::String(int value) { + char tmp[32]; + sprintf(tmp, "%d", value); + *this = tmp; +} + +String::String(long value) { + char tmp[32]; + sprintf(tmp, "%ld", value); + *this = tmp; +} + +#endif diff --git a/src/Internals/JsonParser.cpp b/src/Internals/JsonParser.cpp index 5ae4a23c..6ac0012a 100644 --- a/src/Internals/JsonParser.cpp +++ b/src/Internals/JsonParser.cpp @@ -6,9 +6,6 @@ #include "../../include/ArduinoJson/Internals/JsonParser.hpp" -#include // for strtol, strtod -#include - #include "../../include/ArduinoJson/Internals/Comments.hpp" #include "../../include/ArduinoJson/Internals/Encoding.hpp" #include "../../include/ArduinoJson/JsonArray.hpp" @@ -26,17 +23,6 @@ bool JsonParser::skip(char charToSkip) { return true; } -bool JsonParser::skip(const char *wordToSkip) { - register const char *ptr = _readPtr; - while (*wordToSkip && *ptr == *wordToSkip) { - wordToSkip++; - ptr++; - } - if (*wordToSkip != '\0') return false; - _readPtr = ptr; - return true; -} - bool JsonParser::parseAnythingTo(JsonVariant *destination) { if (_nestingLimit == 0) return false; _nestingLimit--; @@ -55,27 +41,6 @@ inline bool JsonParser::parseAnythingToUnsafe(JsonVariant *destination) { case '{': return parseObjectTo(destination); - case 't': - case 'f': - return parseBooleanTo(destination); - - case '-': - case '.': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return parseNumberTo(destination); - - case 'n': - return parseNullTo(destination); - default: return parseStringTo(destination); } @@ -166,64 +131,24 @@ bool JsonParser::parseObjectTo(JsonVariant *destination) { return true; } -bool JsonParser::parseBooleanTo(JsonVariant *destination) { - if (skip("true")) { - *destination = true; - return true; - } else if (skip("false")) { - *destination = false; - return true; - } else { - return false; - } -} - -bool JsonParser::parseNumberTo(JsonVariant *destination) { - char *endOfLong; - long longValue = strtol(_readPtr, &endOfLong, 10); - char stopChar = *endOfLong; - - // Could it be a floating point value? - bool couldBeFloat = stopChar == '.' || stopChar == 'e' || stopChar == 'E'; - - if (couldBeFloat) { - // Yes => parse it as a double - double doubleValue = strtod(_readPtr, const_cast(&_readPtr)); - // Count the decimal digits - uint8_t decimals = static_cast(_readPtr - endOfLong - 1); - // Set the variant as a double - *destination = JsonVariant(doubleValue, decimals); - } else { - // No => set the variant as a long - _readPtr = endOfLong; - *destination = longValue; - } - return true; -} - -bool JsonParser::parseNullTo(JsonVariant *destination) { - const char *NULL_STRING = NULL; - if (!skip("null")) return false; - *destination = NULL_STRING; - return true; -} - static inline bool isInRange(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'); + isInRange(c, 'A', 'Z') || c == '-' || c == '.'; } +static inline bool isQuote(char c) { return c == '\'' || c == '\"'; } + const char *JsonParser::parseString() { const char *readPtr = _readPtr; char *writePtr = _writePtr; char c = *readPtr; - if (c == '\'' || c == '\"') { // quotes + if (isQuote(c)) { // quotes char stopChar = c; for (;;) { c = *++readPtr; @@ -263,7 +188,13 @@ const char *JsonParser::parseString() { } bool JsonParser::parseStringTo(JsonVariant *destination) { + bool hasQuotes = isQuote(_readPtr[0]); const char *value = parseString(); - *destination = value; - return value != NULL; + if (value == NULL) return false; + if (hasQuotes) { + *destination = value; + } else { + *destination = Unparsed(value); + } + return true; } diff --git a/src/Internals/StringBuilder.cpp b/src/Internals/StaticStringBuilder.cpp similarity index 69% rename from src/Internals/StringBuilder.cpp rename to src/Internals/StaticStringBuilder.cpp index 440e4a50..c172c2cc 100644 --- a/src/Internals/StringBuilder.cpp +++ b/src/Internals/StaticStringBuilder.cpp @@ -4,11 +4,11 @@ // Arduino JSON library // https://github.com/bblanchon/ArduinoJson -#include "../../include/ArduinoJson/Internals/StringBuilder.hpp" +#include "../../include/ArduinoJson/Internals/StaticStringBuilder.hpp" using namespace ArduinoJson::Internals; -size_t StringBuilder::write(uint8_t c) { +size_t StaticStringBuilder::write(uint8_t c) { if (length >= capacity) return 0; buffer[length++] = c; diff --git a/src/JsonObject.cpp b/src/JsonObject.cpp index 81f74563..8b64023a 100644 --- a/src/JsonObject.cpp +++ b/src/JsonObject.cpp @@ -8,7 +8,7 @@ #include // for strcmp -#include "../include/ArduinoJson/Internals/StringBuilder.hpp" +#include "../include/ArduinoJson/Internals/StaticStringBuilder.hpp" #include "../include/ArduinoJson/JsonArray.hpp" #include "../include/ArduinoJson/JsonBuffer.hpp" diff --git a/src/JsonVariant.cpp b/src/JsonVariant.cpp index 92d8dd74..c3251912 100644 --- a/src/JsonVariant.cpp +++ b/src/JsonVariant.cpp @@ -9,22 +9,111 @@ #include "../include/ArduinoJson/JsonArray.hpp" #include "../include/ArduinoJson/JsonObject.hpp" -using namespace ArduinoJson; +#include // for errno +#include // for strtol, strtod + using namespace ArduinoJson::Internals; -void JsonVariant::writeTo(JsonWriter &writer) const { - if (is()) - as().writeTo(writer); - else if (is()) - as().writeTo(writer); - else if (is()) - writer.writeString(as()); - else if (is()) - writer.writeLong(as()); - else if (is()) - writer.writeBoolean(as()); - else if (is()) { +namespace ArduinoJson { + +template <> +const char *JsonVariant::as() const { + if (_type == JSON_UNPARSED && _content.asString && + !strcmp("null", _content.asString)) + return NULL; + if (_type == JSON_STRING || _type == JSON_UNPARSED) return _content.asString; + return NULL; +} + +template <> +double JsonVariant::as() const { + if (_type >= JSON_DOUBLE_0_DECIMALS) return _content.asDouble; + + if (_type == JSON_LONG || _type == JSON_BOOLEAN) + return static_cast(_content.asLong); + + if ((_type == JSON_STRING || _type == JSON_UNPARSED) && _content.asString) + return strtod(_content.asString, NULL); + + return 0.0; +} + +template <> +long JsonVariant::as() const { + if (_type == JSON_LONG || _type == JSON_BOOLEAN) return _content.asLong; + + if (_type >= JSON_DOUBLE_0_DECIMALS) + return static_cast(_content.asDouble); + + if ((_type == JSON_STRING || _type == JSON_UNPARSED) && _content.asString) { + if (!strcmp("true", _content.asString)) return 1; + return strtol(_content.asString, NULL, 10); + } + + return 0L; +} + +template <> +String JsonVariant::as() const { + if ((_type == JSON_STRING || _type == JSON_UNPARSED) && + _content.asString != NULL) + return String(_content.asString); + + if (_type == JSON_LONG || _type == JSON_BOOLEAN) + return String(_content.asLong); + + if (_type >= JSON_DOUBLE_0_DECIMALS) { uint8_t decimals = static_cast(_type - JSON_DOUBLE_0_DECIMALS); - writer.writeDouble(as(), decimals); + return String(_content.asDouble, decimals); + } + + String s; + printTo(s); + return s; +} + +template <> +bool JsonVariant::is() const { + if (_type == JSON_LONG) return true; + + if (_type != JSON_UNPARSED || _content.asString == NULL) return false; + + char *end; + errno = 0; + strtol(_content.asString, &end, 10); + + return *end == '\0' && errno == 0; +} + +template <> +bool JsonVariant::is() const { + if (_type >= JSON_DOUBLE_0_DECIMALS) return true; + + if (_type != JSON_UNPARSED || _content.asString == NULL) return false; + + char *end; + errno = 0; + strtod(_content.asString, &end); + + return *end == '\0' && errno == 0 && !is(); +} + +void JsonVariant::writeTo(JsonWriter &writer) const { + if (_type == JSON_ARRAY) _content.asArray->writeTo(writer); + + if (_type == JSON_OBJECT) _content.asObject->writeTo(writer); + + if (_type == JSON_STRING) writer.writeString(_content.asString); + + if (_type == JSON_UNPARSED) writer.writeRaw(_content.asString); + + if (_type == JSON_LONG) writer.writeLong(_content.asLong); + + if (_type == JSON_BOOLEAN) writer.writeBoolean(_content.asLong); + + if (_type >= JSON_DOUBLE_0_DECIMALS) { + uint8_t decimals = static_cast(_type - JSON_DOUBLE_0_DECIMALS); + writer.writeDouble(_content.asDouble, decimals); } } +} diff --git a/test/ArduinoString_Tests.cpp b/test/ArduinoString_Tests.cpp index ed0899c6..c4d7349c 100644 --- a/test/ArduinoString_Tests.cpp +++ b/test/ArduinoString_Tests.cpp @@ -164,3 +164,37 @@ TEST_F(ArduinoStringTests, JsonArraySubscript) { eraseString(value); ASSERT_STREQ("world", array[0]); } + +TEST_F(ArduinoStringTests, JsonArray_PrintTo) { + JsonArray &array = _jsonBuffer.createArray(); + array.add(4); + array.add(2); + String json; + array.printTo(json); + ASSERT_EQ(String("[4,2]"), json); +} + +TEST_F(ArduinoStringTests, JsonArray_PrettyPrintTo) { + JsonArray &array = _jsonBuffer.createArray(); + array.add(4); + array.add(2); + String json; + array.prettyPrintTo(json); + ASSERT_EQ(String("[\r\n 4,\r\n 2\r\n]"), json); +} + +TEST_F(ArduinoStringTests, JsonObject_PrintTo) { + JsonObject &object = _jsonBuffer.createObject(); + object["key"] = "value"; + String json; + object.printTo(json); + ASSERT_EQ(String("{\"key\":\"value\"}"), json); +} + +TEST_F(ArduinoStringTests, JsonObject_PrettyPrintTo) { + JsonObject &object = _jsonBuffer.createObject(); + object["key"] = "value"; + String json; + object.prettyPrintTo(json); + ASSERT_EQ(String("{\r\n \"key\": \"value\"\r\n}"), json); +} diff --git a/test/Issue90.cpp b/test/Issue90.cpp new file mode 100644 index 00000000..cfffefac --- /dev/null +++ b/test/Issue90.cpp @@ -0,0 +1,28 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include +#include // for LONG_MAX +#define ARDUINOJSON_ENABLE_STD_STREAM +#include + +#define SUITE Issue90 + +using namespace ArduinoJson::Internals; + +static const char* superLong = + "12345678901234567890123456789012345678901234567890123456789012345678901234" + "5678901234567890123456789012345678901234567890123456789012345678901234567"; + +static const JsonVariant variant = Unparsed(superLong); + +TEST(SUITE, IsNotALong) { ASSERT_FALSE(variant.is()); } + +TEST(SUITE, AsLong) { ASSERT_EQ(LONG_MAX, variant.as()); } + +TEST(SUITE, IsAString) { ASSERT_FALSE(variant.is()); } + +TEST(SUITE, AsString) { ASSERT_STREQ(superLong, variant.as()); } diff --git a/test/JsonParser_Array_Tests.cpp b/test/JsonParser_Array_Tests.cpp index fa2a51f4..f773cfca 100644 --- a/test/JsonParser_Array_Tests.cpp +++ b/test/JsonParser_Array_Tests.cpp @@ -144,26 +144,6 @@ TEST_F(JsonParser_Array_Tests, TwoNulls) { secondElementMustBe(nullCharPtr); } -TEST_F(JsonParser_Array_Tests, IncompleteNull) { - whenInputIs("[nul!]"); - parseMustFail(); -} - -TEST_F(JsonParser_Array_Tests, IncompleteTrue) { - whenInputIs("[tru!]"); - parseMustFail(); -} - -TEST_F(JsonParser_Array_Tests, IncompleteFalse) { - whenInputIs("[fals!]"); - parseMustFail(); -} - -TEST_F(JsonParser_Array_Tests, MixedTrueFalse) { - whenInputIs("[trufalse]"); - parseMustFail(); -} - TEST_F(JsonParser_Array_Tests, TwoStringsDoubleQuotes) { whenInputIs("[ \"hello\" , \"world\" ]"); diff --git a/test/JsonVariant_As_Tests.cpp b/test/JsonVariant_As_Tests.cpp new file mode 100644 index 00000000..0461455f --- /dev/null +++ b/test/JsonVariant_As_Tests.cpp @@ -0,0 +1,177 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include +#define ARDUINOJSON_ENABLE_STD_STREAM +#include + +static const char* null = 0; + +TEST(JsonVariant_As_Tests, DoubleAsBool) { + JsonVariant variant = 4.2; + ASSERT_TRUE(variant.as()); +} + +TEST(JsonVariant_As_Tests, DoubleAsCstr) { + JsonVariant variant = 4.2; + ASSERT_FALSE(variant.as()); +} + +TEST(JsonVariant_As_Tests, DoubleAsString) { + JsonVariant variant = 4.2; + ASSERT_EQ(String("4.20"), variant.as()); +} + +TEST(JsonVariant_As_Tests, DoubleAsLong) { + JsonVariant variant = 4.2; + ASSERT_EQ(4L, variant.as()); +} + +TEST(JsonVariant_As_Tests, DoubleZeroAsBool) { + JsonVariant variant = 0.0; + ASSERT_FALSE(variant.as()); +} + +TEST(JsonVariant_As_Tests, DoubleZeroAsLong) { + JsonVariant variant = 0.0; + ASSERT_EQ(0L, variant.as()); +} + +TEST(JsonVariant_As_Tests, FalseAsBool) { + JsonVariant variant = false; + ASSERT_FALSE(variant.as()); +} + +TEST(JsonVariant_As_Tests, FalseAsDouble) { + JsonVariant variant = false; + ASSERT_EQ(0.0, variant.as()); +} + +TEST(JsonVariant_As_Tests, FalseAsLong) { + JsonVariant variant = false; + ASSERT_EQ(0L, variant.as()); +} + +TEST(JsonVariant_As_Tests, FalseAsString) { + JsonVariant variant = false; + ASSERT_EQ(String("0"), variant.as()); +} + +TEST(JsonVariant_As_Tests, TrueAsBool) { + JsonVariant variant = true; + ASSERT_TRUE(variant.as()); +} + +TEST(JsonVariant_As_Tests, TrueAsDouble) { + JsonVariant variant = true; + ASSERT_EQ(1.0, variant.as()); +} + +TEST(JsonVariant_As_Tests, TrueAsLong) { + JsonVariant variant = true; + ASSERT_EQ(1L, variant.as()); +} + +TEST(JsonVariant_As_Tests, TrueAsString) { + JsonVariant variant = true; + ASSERT_EQ(String("1"), variant.as()); +} + +TEST(JsonVariant_As_Tests, LongAsBool) { + JsonVariant variant = 42L; + ASSERT_TRUE(variant.as()); +} + +TEST(JsonVariant_As_Tests, LongZeroAsBool) { + JsonVariant variant = 0L; + ASSERT_FALSE(variant.as()); +} + +TEST(JsonVariant_As_Tests, LongAsDouble) { + JsonVariant variant = 42L; + ASSERT_EQ(42.0, variant.as()); +} + +TEST(JsonVariant_As_Tests, LongAsString) { + JsonVariant variant = 42L; + ASSERT_EQ(String("42"), variant.as()); +} + +TEST(JsonVariant_As_Tests, LongZeroAsDouble) { + JsonVariant variant = 0L; + ASSERT_EQ(0.0, variant.as()); +} + +TEST(JsonVariant_As_Tests, NullAsBool) { + JsonVariant variant = null; + ASSERT_FALSE(variant.as()); +} + +TEST(JsonVariant_As_Tests, NullAsDouble) { + JsonVariant variant = null; + ASSERT_EQ(0.0, variant.as()); +} + +TEST(JsonVariant_As_Tests, NullAsLong) { + JsonVariant variant = null; + ASSERT_EQ(0L, variant.as()); +} + +TEST(JsonVariant_As_Tests, NullAsString) { + JsonVariant variant = null; + ASSERT_EQ(String("null"), variant.as()); +} + +TEST(JsonVariant_As_Tests, NumberStringAsBool) { + JsonVariant variant = "42"; + ASSERT_TRUE(variant.as()); +} + +TEST(JsonVariant_As_Tests, NumberStringAsLong) { + JsonVariant variant = "42"; + ASSERT_EQ(42L, variant.as()); +} + +TEST(JsonVariant_As_Tests, RandomStringAsBool) { + JsonVariant variant = "hello"; + ASSERT_FALSE(variant.as()); +} + +TEST(JsonVariant_As_Tests, RandomStringAsLong) { + JsonVariant variant = "hello"; + ASSERT_EQ(0L, variant.as()); +} + +TEST(JsonVariant_As_Tests, TrueStringAsBool) { + JsonVariant variant = "true"; + ASSERT_TRUE(variant.as()); +} + +TEST(JsonVariant_As_Tests, TrueStringAsLong) { + JsonVariant variant = "true"; + ASSERT_EQ(1L, variant.as()); +} + +TEST(JsonVariant_As_Tests, ObjectAsString) { + DynamicJsonBuffer buffer; + + JsonObject& obj = buffer.createObject(); + obj["key"] = "value"; + + JsonVariant variant = obj; + ASSERT_EQ(String("{\"key\":\"value\"}"), variant.as()); +} + +TEST(JsonVariant_As_Tests, ArrayAsString) { + DynamicJsonBuffer buffer; + + JsonArray& arr = buffer.createArray(); + arr.add(4); + arr.add(2); + + JsonVariant variant = arr; + ASSERT_EQ(String("[4,2]"), variant.as()); +} diff --git a/test/JsonVariant_Is_Tests.cpp b/test/JsonVariant_Is_Tests.cpp new file mode 100644 index 00000000..1cc19136 --- /dev/null +++ b/test/JsonVariant_Is_Tests.cpp @@ -0,0 +1,92 @@ +// Copyright Benoit Blanchon 2014-2015 +// MIT License +// +// Arduino JSON library +// https://github.com/bblanchon/ArduinoJson + +#include +#define ARDUINOJSON_ENABLE_STD_STREAM +#include + +#define SUITE JsonVariant_Is_Tests + +using namespace ArduinoJson::Internals; + +template +void assertIsNot(TFrom value) { + JsonVariant variant = value; + ASSERT_FALSE(variant.is()); +} + +template +void assertIsNot(JsonArray& value) { + JsonVariant variant = value; + ASSERT_FALSE(variant.is()); +} + +template +void assertIs(TFrom value) { + JsonVariant variant = value; + ASSERT_TRUE(variant.is()); +} + +template +void assertIs(JsonArray& value) { + JsonVariant variant = value; + ASSERT_TRUE(variant.is()); +} + +TEST(SUITE, ArrayIsArry) { assertIs(JsonArray::invalid()); } +TEST(SUITE, ArrayIsBool) { assertIsNot(JsonArray::invalid()); } +TEST(SUITE, ArrayIsDouble) { assertIsNot(JsonArray::invalid()); } +TEST(SUITE, ArrayIsFloat) { assertIsNot(JsonArray::invalid()); } +TEST(SUITE, ArrayIsInt) { assertIsNot(JsonArray::invalid()); } +TEST(SUITE, ArrayIsLong) { assertIsNot(JsonArray::invalid()); } +TEST(SUITE, ArrayIsString) { assertIsNot(JsonArray::invalid()); } + +TEST(SUITE, BoolIsArray) { assertIsNot(true); } +TEST(SUITE, BoolIsBool) { assertIs(true); } +TEST(SUITE, BoolIsDouble) { assertIsNot(true); } +TEST(SUITE, BoolIsFloat) { assertIsNot(true); } +TEST(SUITE, BoolIsInt) { assertIsNot(true); } +TEST(SUITE, BoolIsLong) { assertIsNot(true); } +TEST(SUITE, BoolIsString) { assertIsNot(true); } + +TEST(SUITE, DoubleIsArray) { assertIsNot(4.2); } +TEST(SUITE, DoubleIsBool) { assertIsNot(4.2); } +TEST(SUITE, DoubleIsDouble) { assertIs(4.2); } +TEST(SUITE, DoubleIsFloat) { assertIs(4.2); } +TEST(SUITE, DoubleIsInt) { assertIsNot(4.2); } +TEST(SUITE, DoubleIsLong) { assertIsNot(4.2); } +TEST(SUITE, DoubleIsString) { assertIsNot(4.2); } + +TEST(SUITE, LongIsArray) { assertIsNot(42L); } +TEST(SUITE, LongIsBool) { assertIsNot(42L); } +TEST(SUITE, LongIsDouble) { assertIsNot(42L); } +TEST(SUITE, LongIsFloat) { assertIsNot(42L); } +TEST(SUITE, LongIsInt) { assertIs(42L); } +TEST(SUITE, LongIsLong) { assertIs(42L); } +TEST(SUITE, LongIsString) { assertIsNot(42L); } + +TEST(SUITE, StringIsArray) { assertIsNot("42"); } +TEST(SUITE, StringIsBool) { assertIsNot("42"); } +TEST(SUITE, StringIsDouble) { assertIsNot("42"); } +TEST(SUITE, StringIsFloat) { assertIsNot("42"); } +TEST(SUITE, StringIsInt) { assertIsNot("42"); } +TEST(SUITE, StringIsLong) { assertIsNot("42"); } +TEST(SUITE, StringIsString) { assertIs("42"); } + +TEST(SUITE, UnparsedIntIsArra) { assertIsNot(Unparsed("42")); } +TEST(SUITE, UnparsedIntIsBool) { assertIsNot(Unparsed("42")); } +TEST(SUITE, UnparsedIntIsDouble) { assertIsNot(Unparsed("42")); } +TEST(SUITE, UnparsedIntIsFloat) { assertIsNot(Unparsed("42")); } +TEST(SUITE, UnparsedIntIsInt) { assertIs(Unparsed("42")); } +TEST(SUITE, UnparsedIntIsLong) { assertIs(Unparsed("42")); } +TEST(SUITE, UnparsedIntIsString) { assertIsNot(Unparsed("42")); } + +TEST(SUITE, UnparsedFloatIsBool) { assertIsNot(Unparsed("4.2e-10")); } +TEST(SUITE, UnparsedFloatIsDouble) { assertIs(Unparsed("4.2e-10")); } +TEST(SUITE, UnparsedFloatIsFloat) { assertIs(Unparsed("4.2e-10")); } +TEST(SUITE, UnparsedFloatIsInt) { assertIsNot(Unparsed("4.2e-10")); } +TEST(SUITE, UnparsedFloatIsLong) { assertIsNot(Unparsed("4.2e-10")); } +TEST(SUITE, UnparsedFloatIsStr) { assertIsNot(Unparsed("4.2")); } diff --git a/test/JsonWriter_WriteString_Tests.cpp b/test/JsonWriter_WriteString_Tests.cpp index b7c8af4b..c9f1bd20 100644 --- a/test/JsonWriter_WriteString_Tests.cpp +++ b/test/JsonWriter_WriteString_Tests.cpp @@ -7,14 +7,14 @@ #include #include -#include +#include using namespace ArduinoJson::Internals; class JsonWriter_WriteString_Tests : public testing::Test { protected: void whenInputIs(const char *input) { - StringBuilder sb(buffer, sizeof(buffer)); + StaticStringBuilder sb(buffer, sizeof(buffer)); JsonWriter writer(sb); writer.writeString(input); returnValue = writer.bytesWritten(); diff --git a/test/StringBuilderTests.cpp b/test/StringBuilderTests.cpp index ebd28342..c00377d1 100644 --- a/test/StringBuilderTests.cpp +++ b/test/StringBuilderTests.cpp @@ -5,14 +5,14 @@ // https://github.com/bblanchon/ArduinoJson #include -#include +#include using namespace ArduinoJson::Internals; class StringBuilderTests : public testing::Test { protected: virtual void SetUp() { - _stringBuilder = new StringBuilder(_buffer, sizeof(_buffer)); + _stringBuilder = new StaticStringBuilder(_buffer, sizeof(_buffer)); } virtual void TearDown() { delete _stringBuilder; }