diff --git a/examples/FirebaseDemo_ESP8266/FirebaseDemo_ESP8266.ino b/examples/FirebaseDemo_ESP8266/FirebaseDemo_ESP8266.ino index 93bb3436..215f1c1b 100644 --- a/examples/FirebaseDemo_ESP8266/FirebaseDemo_ESP8266.ino +++ b/examples/FirebaseDemo_ESP8266/FirebaseDemo_ESP8266.ino @@ -56,7 +56,7 @@ void loop() { // get value Serial.print("number: "); - Serial.println((float)Firebase.get("number")); + Serial.println(Firebase.getFloat("number")); delay(1000); // remove value diff --git a/examples/FirebaseRoom_ESP8266/FirebaseRoom_ESP8266.ino b/examples/FirebaseRoom_ESP8266/FirebaseRoom_ESP8266.ino index 631bd20e..4807d90f 100644 --- a/examples/FirebaseRoom_ESP8266/FirebaseRoom_ESP8266.ino +++ b/examples/FirebaseRoom_ESP8266/FirebaseRoom_ESP8266.ino @@ -20,24 +20,24 @@ #include #include -const int pinGrove = 15; -const int pinVibrator = 5; -const int pinLightSensor = A0; -const int pinLed = 12; -const int pinButton = 14; -const int pinFan = 13; +const int grovePowerPin = 15; +const int vibratorPin = 5; +const int lightSensorPin = A0; +const int ledPin = 12; +const int buttonPin = 14; +const int fanPin = 13; void setup() { Serial.begin(9600); - pinMode(pinGrove, OUTPUT); - digitalWrite(pinGrove, HIGH); + pinMode(grovePowerPin, OUTPUT); + digitalWrite(grovePowerPin, HIGH); - pinMode(pinVibrator, OUTPUT); - pinMode(pinLightSensor, INPUT); - pinMode(pinLed, OUTPUT); - pinMode(pinButton, INPUT); - pinMode(pinFan, OUTPUT); + pinMode(vibratorPin, OUTPUT); + pinMode(lightSensorPin, INPUT); + pinMode(ledPin, OUTPUT); + pinMode(buttonPin, INPUT); + pinMode(fanPin, OUTPUT); // connect to wifi. WiFi.begin("SSID", "PASSWORD"); @@ -62,15 +62,15 @@ int button = 0; float light = 0.0; void loop() { - digitalWrite(pinLed, (int)Firebase.get("redlight")); - digitalWrite(pinFan, (int)Firebase.get("cooldown")); - digitalWrite(pinVibrator, (int)Firebase.get("brrr")); - int newButton = digitalRead(pinButton); + digitalWrite(ledPin, Firebase.getInt("redlight")); + digitalWrite(fanPin, Firebase.getInt("cooldown")); + digitalWrite(vibratorPin, Firebase.getInt("brrr")); + int newButton = digitalRead(buttonPin); if (newButton != button) { button = newButton; Firebase.set("pushbutton", button); } - float newLight = analogRead(pinLightSensor); + float newLight = analogRead(lightSensorPin); if (abs(newLight - light) > 100) { light = newLight; Firebase.set("sunlight", light); diff --git a/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino b/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino index 2ab07097..3bdae6c0 100644 --- a/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino +++ b/examples/FirebaseStream_ESP8266/FirebaseStream_ESP8266.ino @@ -55,16 +55,16 @@ void loop() { if (Firebase.available()) { FirebaseObject event = Firebase.readEvent(); - String event_type = event["type"]; - event_type.toLowerCase(); + String eventType = event.getString("type"); + eventType.toLowerCase(); Serial.print("event: "); - Serial.println(event_type); - if (event_type == "put") { + Serial.println(eventType); + if (eventType == "put") { Serial.print("data: "); - Serial.println(event["data"].asString()); - String path = event["path"]; - float data = event["data"]; + Serial.println(event.getString("data")); + String path = event.getString("path"); + float data = event.getFloat("data"); display.clearDisplay(); display.setTextSize(2); diff --git a/src/FirebaseArduino.cpp b/src/FirebaseArduino.cpp index 1c9930fe..05177ba9 100644 --- a/src/FirebaseArduino.cpp +++ b/src/FirebaseArduino.cpp @@ -47,6 +47,43 @@ FirebaseObject FirebaseArduino::get(const char* path) { return FirebaseObject(get.response()); } +int FirebaseArduino::getInt(const char* path) { + auto get = FirebaseGet(host_, auth_, path, http_.get()); + error_ = get.error(); + if (failed()) { + return 0; + } + return FirebaseObject(get.response()).getInt(); +} + + +float FirebaseArduino::getFloat(const char* path) { + auto get = FirebaseGet(host_, auth_, path, http_.get()); + error_ = get.error(); + if (failed()) { + return 0.0f; + } + return FirebaseObject(get.response()).getFloat(); +} + +String FirebaseArduino::getString(const char* path) { + auto get = FirebaseGet(host_, auth_, path, http_.get()); + error_ = get.error(); + if (failed()) { + return ""; + } + return FirebaseObject(get.response()).getString(); +} + +bool FirebaseArduino::getBool(const char* path) { + auto get = FirebaseGet(host_, auth_, path, http_.get()); + error_ = get.error(); + if (failed()) { + return ""; + } + return FirebaseObject(get.response()).getBool(); +} + void FirebaseArduino::remove(const char* path) { auto remove = FirebaseRemove(host_, auth_, path, http_.get()); error_ = remove.error(); @@ -67,7 +104,7 @@ FirebaseObject FirebaseArduino::readEvent() { String event = client->readStringUntil('\n').substring(6); client->readStringUntil('\n'); // consume separator FirebaseObject obj = FirebaseObject(event); - obj["type"] = type; + obj.getJsonVariant().asObject()["type"] = type; return obj; } diff --git a/src/FirebaseArduino.h b/src/FirebaseArduino.h index 376befb3..64204ec9 100644 --- a/src/FirebaseArduino.h +++ b/src/FirebaseArduino.h @@ -58,11 +58,42 @@ class FirebaseArduino { /** - * Gets the value located at path. + * Gets the integer value located at path. * You should check success() after calling. * \param path The path to the node you wish to retrieve. - * \return The data located at that path. This may either be a single element - * or it may be a json structure. Will only be populated if success() is true. + * \return The integer value located at that path. Will only be populated if success() is true. + */ + int getInt(const char* path); + + /** + * Gets the float value located at path. + * You should check success() after calling. + * \param path The path to the node you wish to retrieve. + * \return The float value located at that path. Will only be populated if success() is true. + */ + float getFloat(const char* path); + + /** + * Gets the string value located at path. + * You should check success() after calling. + * \param path The path to the node you wish to retrieve. + * \return The string value located at that path. Will only be populated if success() is true. + */ + String getString(const char* path); + + /** + * Gets the boolean value located at path. + * You should check success() after calling. + * \param path The path to the node you wish to retrieve. + * \return The boolean value located at that path. Will only be populated if success() is true. + */ + bool getBool(const char* path); + + /** + * Gets the json object value located at path. + * You should check success() after calling. + * \param path The path to the node you wish to retrieve. + * \return a FirebaseObject value located at that path. Will only be populated if success() is true. */ FirebaseObject get(const char* path); @@ -94,7 +125,7 @@ class FirebaseArduino { /** * Reads the next event in a stream. This is only meaningful once stream() has * been called. - * \return Object will have ["type"] that describes the event type, ["path"] + * \return FirebaseObject will have ["type"] that describes the event type, ["path"] * that describes the effected path and ["data"] that was updated. */ FirebaseObject readEvent(); diff --git a/src/FirebaseObject.cpp b/src/FirebaseObject.cpp index a9745690..dc1f1d87 100644 --- a/src/FirebaseObject.cpp +++ b/src/FirebaseObject.cpp @@ -16,57 +16,81 @@ #include "FirebaseObject.h" -namespace { -template -T decodeJsonLiteral(const String& json) { - return JsonVariant{ArduinoJson::RawJson{json.c_str()}}; -} - -// ugly workaround to https://github.com/bblanchon/ArduinoJson/issues/265 -template<> -String decodeJsonLiteral(const String& json) { - StaticJsonBuffer buf; - String array = "[" + json + "]"; - return buf.parseArray(&array[0])[0]; -} -} // namespace - FirebaseObject::FirebaseObject(const String& data) : data_{data} { - if (data_[0] == '{') { - json_ = &buffer_.parseObject(&data_[0]); - } else if (data_[0] == '"') { - data_ = decodeJsonLiteral(data_); - } + json_ = buffer_.parse(&data_[0]); + // TODO(proppy): find a way to check decoding error, tricky because + // ArduinoJson doesn't surface error for variant parsing. + // See: https://github.com/bblanchon/ArduinoJson/issues/279 } -FirebaseObject::operator bool() { - return decodeJsonLiteral(data_); +bool FirebaseObject::getBool(const String& path) { + JsonVariant variant = getJsonVariant(path); + if (!variant.is()) { + error_ = "failed to convert to bool"; + return 0; + } + return static_cast(variant); } -FirebaseObject::operator int() { - return decodeJsonLiteral(data_); +int FirebaseObject::getInt(const String& path) { + JsonVariant variant = getJsonVariant(path); + if (!variant.is()) { + error_ = "failed to convert to int"; + return 0; + } + return static_cast(variant); } -FirebaseObject::operator float() { - return decodeJsonLiteral(data_); +float FirebaseObject::getFloat(const String& path) { + JsonVariant variant = getJsonVariant(path); + if (!variant.is()) { + error_ = "failed to convert to float"; + return 0; + } + return static_cast(variant); } -FirebaseObject::operator const String&() { - return data_; +String FirebaseObject::getString(const String& path) { + JsonVariant variant = getJsonVariant(path); + if (!variant.is()) { + error_ = "failed to convert to string"; + return ""; + } + return static_cast(variant); } -FirebaseObject::operator const JsonObject&() { - return *json_; +JsonVariant FirebaseObject::getJsonVariant(const String& path) { + String key(path); + char* start = &key[0]; + char* end = start + key.length(); + // skip first `/`. + if (*start == '/') { + start++; + } + JsonVariant json = json_; + while (start < end) { + // TODO(proppy) split in a separate function. + char* p = start; + // advance to next `/`. + while (*p && (*p != '/')) p++; + // make `start` a C string. + *p = 0; + // return json variant at `start`. + json = json.asObject().get(start); + // advance to next path element. + start = p + 1; + } + return json; } -JsonObjectSubscript FirebaseObject::operator[](const char* key) { - return json_->operator[](key); +bool FirebaseObject::failed() const { + return error_.length() > 0; } -JsonObjectSubscript FirebaseObject::operator[](const String& key) { - return json_->operator[](key); +bool FirebaseObject::success() const { + return error_.length() == 0; } -JsonVariant FirebaseObject::operator[](JsonObjectKey key) const { - return json_->operator[](key); +const String& FirebaseObject::error() const { + return error_; } diff --git a/src/FirebaseObject.h b/src/FirebaseObject.h index 64d2514e..bda3349e 100644 --- a/src/FirebaseObject.h +++ b/src/FirebaseObject.h @@ -31,48 +31,69 @@ class FirebaseObject { public: /** * Construct from json. - * \param data Json formatted string. + * \param data JSON formatted string. */ FirebaseObject(const String& data); /** - * Interpret result as a bool, only applicable if result is a single element - * and not a tree. + * Return the value as a boolean. + * \param optional path in the JSON object. + * \return result as a bool. */ - operator bool(); + bool getBool(const String& path = ""); /** - * Interpret result as a int, only applicable if result is a single element - * and not a tree. + * Return the value as an int. + * \param optional path in the JSON object. + * \return result as an integer. */ - operator int(); + int getInt(const String& path = ""); /** - * Interpret result as a float, only applicable if result is a single element - * and not a tree. + * Return the value as a float. + * \param optional path in the JSON object. + * \return result as a float. */ - operator float(); + float getFloat(const String& path = ""); /** - * Interpret result as a String, only applicable if result is a single element - * and not a tree. + * Return the value as a String. + * \param optional path in the JSON object. + * \return result as a String. */ - operator const String&(); + String getString(const String& path = ""); /** - * Interpret result as a JsonObject, if the result is a tree use this or the - * operator[] methods below. + * Return the value as a JsonVariant. + * \param optional path in the JSON object. + * \return result as a JsonVariant. */ - operator const JsonObject&(); + JsonVariant getJsonVariant(const String& path = ""); + + + /** + * + * \return Whether there was an error decoding or accessing the JSON object. + */ + bool success() const; + + /** + * + * \return Whether there was an error decoding or accessing the JSON object. + */ + bool failed() const; + + /** + * + * \return Error message if failed() is true. + */ + const String& error() const; - //TODO(proppy): Add comments to these. - JsonObjectSubscript operator[](const char* key); - JsonObjectSubscript operator[](const String& key); - JsonVariant operator[](JsonObjectKey key) const; private: String data_; StaticJsonBuffer buffer_; - JsonObject* json_; + JsonVariant json_; + String error_; }; #endif // FIREBASE_OBJECT_H diff --git a/test/FirebaseArduino_test.cpp b/test/FirebaseArduino_test.cpp index 97e4bebe..e8f0ed34 100644 --- a/test/FirebaseArduino_test.cpp +++ b/test/FirebaseArduino_test.cpp @@ -17,26 +17,82 @@ #include "FirebaseObject.h" #include "gtest/gtest.h" -TEST(FirebaseObjectTest, JsonLiteral) { - EXPECT_EQ(bool(FirebaseObject("true")), true); - EXPECT_EQ(bool(FirebaseObject("false")), false); - EXPECT_EQ(int(FirebaseObject("42")), 42); - EXPECT_EQ(float(FirebaseObject("43.0")), 43.0); - EXPECT_EQ(String(FirebaseObject("\"foo\"")), "foo"); +TEST(FirebaseObjectTest, GetBool) { + FirebaseObject obj("true"); + EXPECT_EQ(true, obj.getBool()); + EXPECT_TRUE(obj.success()); + EXPECT_FALSE(obj.failed()); + EXPECT_EQ(obj.error(), ""); } -TEST(FirebaseObjectTest, JsonObject) { +TEST(FirebaseObjectTest, GetInt) { + FirebaseObject obj("42"); + EXPECT_EQ(42, obj.getInt()); + EXPECT_TRUE(obj.success()); + EXPECT_FALSE(obj.failed()); + EXPECT_EQ(obj.error(), ""); +} + +TEST(FirebaseObjectTest, GetFloat) { + FirebaseObject obj("43.0"); + EXPECT_EQ(43, obj.getFloat()); + EXPECT_TRUE(obj.success()); + EXPECT_FALSE(obj.failed()); + EXPECT_EQ(obj.error(), ""); +} + +TEST(FirebaseObjectTest, GetString) { + FirebaseObject obj("\"foo\""); + EXPECT_EQ("foo", obj.getString()); + EXPECT_TRUE(obj.success()); + EXPECT_FALSE(obj.failed()); + EXPECT_EQ(obj.error(), ""); +} + +TEST(FirebaseObjectTest, GetObject) { { - const JsonObject& obj = FirebaseObject("{\"foo\":\"bar\"}"); - String foo = obj["foo"]; - EXPECT_EQ(foo, "bar"); + FirebaseObject obj("{\"foo\":\"bar\"}"); + EXPECT_EQ(obj.getString("/foo"), "bar"); + EXPECT_EQ(obj.getString("foo"), "bar"); } { - String foo = FirebaseObject("{\"foo\":\"bar\"}")["foo"]; - EXPECT_EQ(foo, "bar"); + FirebaseObject obj("{\"foo\": {\"bar\": \"hop\"}}"); + EXPECT_EQ(obj.getString("/foo/bar"), "hop"); } } +TEST(FirebaseObjectTest, GetIntFailure) { + FirebaseObject obj("{\"foo\":\"bar\"}"); + EXPECT_EQ(obj.getInt(), 0); + EXPECT_FALSE(obj.success()); + EXPECT_TRUE(obj.failed()); + EXPECT_EQ(obj.error(), "failed to convert to int"); +} + +TEST(FirebaseObjectTest, GetFloatFailure) { + FirebaseObject obj("{\"foo\":\"bar\"}"); + EXPECT_EQ(obj.getFloat(), 0); + EXPECT_FALSE(obj.success()); + EXPECT_TRUE(obj.failed()); + EXPECT_EQ(obj.error(), "failed to convert to float"); +} + +TEST(FirebaseObjectTest, GetBoolFailure) { + FirebaseObject obj("{\"foo\":\"bar\"}"); + EXPECT_EQ(obj.getBool(), 0); + EXPECT_FALSE(obj.success()); + EXPECT_TRUE(obj.failed()); + EXPECT_EQ(obj.error(), "failed to convert to bool"); +} + +TEST(FirebaseObjectTest, GetStringFailure) { + FirebaseObject obj("{\"foo\":\"bar\"}"); + EXPECT_EQ(obj.getString(), ""); + EXPECT_FALSE(obj.success()); + EXPECT_TRUE(obj.failed()); + EXPECT_EQ(obj.error(), "failed to convert to string"); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();