From fc428249954274f532e40f82eac655d9963ee0c5 Mon Sep 17 00:00:00 2001 From: stechio Date: Sun, 14 Jan 2018 15:00:07 +0100 Subject: [PATCH 1/3] Compatibility-breaking enhancements. Several changes affected existing interfaces: - explicit element path tracking - string flattening - strongly-typed value events --- .project | 11 ++ ElementPath.cpp | 120 ++++++++++++++++++ ElementPath.h | 63 +++++++++ JsonListener.h | 35 +++-- JsonStreamingParser.cpp | 53 ++++---- JsonStreamingParser.h | 7 +- README.md | 20 ++- .../JsonStreamingParser/ExampleParser.cpp | 66 +++++++--- examples/JsonStreamingParser/ExampleParser.h | 26 ++-- 9 files changed, 328 insertions(+), 73 deletions(-) create mode 100644 .project create mode 100644 ElementPath.cpp create mode 100644 ElementPath.h diff --git a/.project b/.project new file mode 100644 index 0000000..6837af0 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + json-streaming-parser + + + + + + + + diff --git a/ElementPath.cpp b/ElementPath.cpp new file mode 100644 index 0000000..f5e2b38 --- /dev/null +++ b/ElementPath.cpp @@ -0,0 +1,120 @@ +/**The MIT License (MIT) + +Contributors: + Stefano Chizzolini + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include "ElementPath.h" + +bool ElementSelector::isObject() { + return index < 0; +} + +void ElementSelector::moveNext() { + index++; +} + +void ElementSelector::reset() { + index = -1; + key[0] = '\0'; +} + +void ElementSelector::set(int index) { + this->index = index; + this->key[0] = '\0'; +} + +void ElementSelector::set(char* key) { + strcpy(this->key, key); + this->index = -1; +} + +/* + * Builds the string representation of this node position within + * its parent. + */ +void ElementSelector::toString(char* buffer) { + if (index >= 0) { + sprintf(buffer, "[%d]", index); + } else { + strcpy(buffer, key); + } +} + +ElementSelector* ElementPath::get(int index) { + if(index >= count) + return NULL; + + return &selectors[index]; +} + +int ElementPath::getCurrentIndex() { + return peek()->index; +} + +char* ElementPath::getCurrentKey() { + return peek()->key; +} + +ElementSelector* ElementPath::peek() { + return currentSelector; +} + +void ElementPath::pop() { + if(count > 0) { + count--; + currentSelector = count > 0 ? &selectors[count - 1] : NULL; + } +} + +ElementSelector ElementPath::push() { + (currentSelector = &selectors[count++])->reset(); +} + +/* + * Builds the full path corresponding to the current node position. + * + * For example, "weather[0].id" corresponds to a 3-level hierarchy: + * { + * "weather" : [ + * { + * "id" : ..., <===== HERE IT IS + * ... : ... + * }, + * { ... } + * ], + * ... + * } + */ +void ElementPath::toString(char* buffer) { + if (count <= 0) + return; + + char elementBuffer[20]; + for(int index = 0; index < count; index++) { + if(index > 0 && selectors[index].isObject()) { + strcat(buffer, "."); + } + selectors[index].toString(elementBuffer); + strcat(buffer, elementBuffer); + } +} diff --git a/ElementPath.h b/ElementPath.h new file mode 100644 index 0000000..325b926 --- /dev/null +++ b/ElementPath.h @@ -0,0 +1,63 @@ +/**The MIT License (MIT) + +Contributors: + Stefano Chizzolini + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* + * Unified element selector. + * Represents the handle associated to an element within either + * an object (key) or an array (index). + */ +class ElementSelector { + public: + int index; + char key[20]; + + bool isObject(); + void moveNext(); + void reset(); + void set(int index); + void set(char* key); + void toString(char* buffer); +}; + +/* + * Hierarchical path to currently parsed element. + * It eases element filtering, keeping track of the current node + * position. + */ +class ElementPath { + private: + ElementSelector* currentSelector; + ElementSelector selectors[20]; + + public: + int count = 0; + + ElementSelector* get(int index); + int getCurrentIndex(); + char* getCurrentKey(); + ElementSelector* peek(); + void pop(); + ElementSelector push(); + void toString(char* buffer); +}; diff --git a/JsonListener.h b/JsonListener.h index 26c0d85..04ebbe0 100644 --- a/JsonListener.h +++ b/JsonListener.h @@ -2,6 +2,9 @@ Copyright (c) 2015 by Daniel Eichhorn +Contributors: + Stefano Chizzolini + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -26,29 +29,35 @@ See more at http://blog.squix.ch and https://github.com/squix78/json-streaming-p #pragma once #include +#include "ElementPath.h" class JsonListener { private: public: - - virtual void whitespace(char c) = 0; - - virtual void startDocument() = 0; - - virtual void key(String key) = 0; - virtual void value(String value) = 0; + virtual void endArray(ElementPath path) = 0; - virtual void endArray() = 0; + virtual void endDocument() = 0; - virtual void endObject() = 0; + virtual void endObject(ElementPath path) = 0; - virtual void endDocument() = 0; + virtual void startArray(ElementPath path) = 0; + + virtual void startDocument() = 0; - virtual void startArray() = 0; + virtual void startObject(ElementPath path) = 0; - virtual void startObject() = 0; - + virtual void value(ElementPath path, char* value) = 0; + + virtual void value(ElementPath path, bool value) = 0; + + virtual void value(ElementPath path, float value) = 0; + + virtual void value(ElementPath path, long value) = 0; + + virtual void value(ElementPath path) = 0; + + virtual void whitespace(char c) = 0; }; diff --git a/JsonStreamingParser.cpp b/JsonStreamingParser.cpp index cfc71d1..7932857 100644 --- a/JsonStreamingParser.cpp +++ b/JsonStreamingParser.cpp @@ -2,6 +2,9 @@ Copyright (c) 2015 by Daniel Eichhorn +Contributors: + Stefano Chizzolini + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -69,6 +72,7 @@ void JsonStreamingParser::parse(char c) { if (c == ']') { endArray(); } else { + path.peek()->moveNext(); startValue(c); } break; @@ -210,11 +214,11 @@ void JsonStreamingParser::endString() { stackPos--; if (popped == STACK_KEY) { buffer[bufferPos] = '\0'; - myListener->key(String(buffer)); + path.peek()->set(buffer); state = STATE_END_KEY; } else if (popped == STACK_STRING) { buffer[bufferPos] = '\0'; - myListener->value(String(buffer)); + myListener->value(path, buffer); state = STATE_AFTER_VALUE; } else { // throw new ParsingError($this->_line_number, $this->_char_number, @@ -257,11 +261,12 @@ boolean JsonStreamingParser::isDigit(char c) { void JsonStreamingParser::endArray() { int popped = stack[stackPos - 1]; stackPos--; + path.pop(); if (popped != STACK_ARRAY) { // throw new ParsingError($this->_line_number, $this->_char_number, // "Unexpected end of array encountered."); } - myListener->endArray(); + myListener->endArray(path); state = STATE_AFTER_VALUE; if (stackPos == 0) { endDocument(); @@ -277,11 +282,12 @@ void JsonStreamingParser::startKey() { void JsonStreamingParser::endObject() { int popped = stack[stackPos]; stackPos--; + path.pop(); if (popped != STACK_OBJECT) { // throw new ParsingError($this->_line_number, $this->_char_number, // "Unexpected end of object encountered."); } - myListener->endObject(); + myListener->endObject(path); state = STATE_AFTER_VALUE; if (stackPos == 0) { endDocument(); @@ -404,15 +410,15 @@ void JsonStreamingParser::endUnicodeSurrogateInterstitial() { void JsonStreamingParser::endNumber() { buffer[bufferPos] = '\0'; - String value = String(buffer); - //float result = 0.0; - //if (doesCharArrayContain(buffer, bufferPos, '.')) { - // result = value.toFloat(); - //} else { - // needed special treatment in php, maybe not in Java and c - // result = value.toFloat(); - //} - myListener->value(value.c_str()); + if (strchr(buffer, '.') != NULL) { + float floatValue; + sscanf(buffer, "%f", &floatValue); + myListener->value(path, floatValue); + } else { + long intValue; + sscanf(buffer, "%d", &intValue); + myListener->value(path, intValue); + } bufferPos = 0; state = STATE_AFTER_VALUE; } @@ -433,9 +439,8 @@ void JsonStreamingParser::endDocument() { void JsonStreamingParser::endTrue() { buffer[bufferPos] = '\0'; - String value = String(buffer); - if (value.equals("true")) { - myListener->value("true"); + if(strcmp(buffer, "true") == 0) { + myListener->value(path, true); } else { // throw new ParsingError($this->_line_number, $this->_char_number, // "Expected 'true'. Got: ".$true); @@ -446,9 +451,8 @@ void JsonStreamingParser::endTrue() { void JsonStreamingParser::endFalse() { buffer[bufferPos] = '\0'; - String value = String(buffer); - if (value.equals("false")) { - myListener->value("false"); + if(strcmp(buffer, "false") == 0) { + myListener->value(path, false); } else { // throw new ParsingError($this->_line_number, $this->_char_number, // "Expected 'true'. Got: ".$true); @@ -459,9 +463,8 @@ void JsonStreamingParser::endFalse() { void JsonStreamingParser::endNull() { buffer[bufferPos] = '\0'; - String value = String(buffer); - if (value.equals("null")) { - myListener->value("null"); + if(strcmp(buffer, "null") == 0) { + myListener->value(path); } else { // throw new ParsingError($this->_line_number, $this->_char_number, // "Expected 'true'. Got: ".$true); @@ -471,16 +474,18 @@ void JsonStreamingParser::endNull() { } void JsonStreamingParser::startArray() { - myListener->startArray(); + myListener->startArray(path); state = STATE_IN_ARRAY; stack[stackPos] = STACK_ARRAY; + path.push(); stackPos++; } void JsonStreamingParser::startObject() { - myListener->startObject(); + myListener->startObject(path); state = STATE_IN_OBJECT; stack[stackPos] = STACK_OBJECT; + path.push(); stackPos++; } diff --git a/JsonStreamingParser.h b/JsonStreamingParser.h index 377e846..a74dfb5 100644 --- a/JsonStreamingParser.h +++ b/JsonStreamingParser.h @@ -2,6 +2,9 @@ Copyright (c) 2015 by Daniel Eichhorn +Contributors: + Stefano Chizzolini + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -54,9 +57,9 @@ See more at http://blog.squix.ch and https://github.com/squix78/json-streaming-p class JsonStreamingParser { private: - int state; int stack[20]; + ElementPath path; int stackPos = 0; JsonListener* myListener; @@ -125,8 +128,6 @@ class JsonStreamingParser { void endObject(); - - public: JsonStreamingParser(); void parse(char c); diff --git a/README.md b/README.md index d951794..7698729 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ Arduino library for parsing potentially huge json streams on devices with scarce This library is a port of Salsify's PHP based json streaming parser (https://github.com/salsify/jsonstreamingparser). +Furthermore, this is a fork by stechio departing from squix78's original library to introduce some key enhancements: + * explicit element path tracking (ElementPath class): object keys and array indices are managed in a robust, unified manner and exposed in each and every event (users are relieved the pain to jury-rig their own custom event-filtering mechanism) + * string flattening: adhering to common best practises, cumbersome instances of std::string have been replaced by plain C-style char arrays. + * strongly-typed values: untypified (string) value event method has been replaced by overloaded, strongly-typed ones corresponding to actual serialization types. + ## Why yet another JSON parser? When working with small (connected) devices you might quickly get to the point where you need to process potentially huge JSON object received from a REST interface. @@ -39,13 +44,16 @@ stream whatever you are interested in. In order to do that you will create a sub implement methods which will be notified in case of certain events in the feed occure. Available events are: * startDocument() - * key(String key) - * value(String value) - * endArray() - * endObject() * endDocument() - * startArray() - * startObject() + * startArray(ElementPath path) + * endArray(ElementPath path) + * startObject(ElementPath path) + * endObject(ElementPath path) + * value(ElementPath path, char* value) + * value(ElementPath path, bool value) + * value(ElementPath path, float value) + * value(ElementPath path, long value) + * value(ElementPath path) In your implementation of these methods you will have to write problem specific code to find the parts of the document that you are interested in. Please see the example to understand what that means. In the example the ExampleListener implements the event methods declared in the JsonListener interface and prints to the serial console when they are called. diff --git a/examples/JsonStreamingParser/ExampleParser.cpp b/examples/JsonStreamingParser/ExampleParser.cpp index 0656fdb..1e9d3b4 100644 --- a/examples/JsonStreamingParser/ExampleParser.cpp +++ b/examples/JsonStreamingParser/ExampleParser.cpp @@ -1,28 +1,65 @@ #include "ExampleParser.h" #include "JsonListener.h" +void ExampleListener::startDocument() { + Serial.println("start document"); +} -void ExampleListener::whitespace(char c) { - Serial.println("whitespace"); +void ExampleListener::startArray(ElementPath path) { + Serial.println("start array. "); } -void ExampleListener::startDocument() { - Serial.println("start document"); +void ExampleListener::startObject(ElementPath path) { + Serial.println("start object. "); +} + +void ExampleListener::value(ElementPath path, char* value) { } -void ExampleListener::key(String key) { - Serial.println("key: " + key); +void ExampleListener::value(ElementPath path, long value) { } -void ExampleListener::value(String value) { - Serial.println("value: " + value); +void ExampleListener::value(ElementPath path, float value) { + char fullPath[200] = ""; + path.toString(fullPath); + Serial.print(fullPath); + Serial.print(" float value: "); + Serial.println(value); + + char* currentKey = path.getCurrentKey(); + // Object entry? + if(currentKey[0] != '\0') { + if(strcmp(currentKey, "temp") == 0) { + //TODO: use the value. + } else if(strcmp(currentKey, "pressure") == 0) { + //TODO: use the value. + } + // else ... + } + // Array item. + else { + int currentIndex = path.getCurrentIndex(); + if(currentIndex == 0) { + //TODO: use the value. + } else if(currentIndex < 5) { + //TODO: use the value. + } + // else ... + } } -void ExampleListener::endArray() { +void ExampleListener::value(ElementPath path, bool value) { +} + +void ExampleListener::value(ElementPath path) { + Serial.println("value: null"); +} + +void ExampleListener::endArray(ElementPath path) { Serial.println("end array. "); } -void ExampleListener::endObject() { +void ExampleListener::endObject(ElementPath path) { Serial.println("end object. "); } @@ -30,11 +67,6 @@ void ExampleListener::endDocument() { Serial.println("end document. "); } -void ExampleListener::startArray() { - Serial.println("start array. "); -} - -void ExampleListener::startObject() { - Serial.println("start object. "); +void ExampleListener::whitespace(char c) { + Serial.println("whitespace"); } - diff --git a/examples/JsonStreamingParser/ExampleParser.h b/examples/JsonStreamingParser/ExampleParser.h index c5f6f79..cc5ae9b 100644 --- a/examples/JsonStreamingParser/ExampleParser.h +++ b/examples/JsonStreamingParser/ExampleParser.h @@ -5,21 +5,27 @@ class ExampleListener: public JsonListener { public: - virtual void whitespace(char c); - virtual void startDocument(); - virtual void key(String key); + virtual void startArray(ElementPath path); - virtual void value(String value); + virtual void startObject(ElementPath path); - virtual void endArray(); + virtual void value(ElementPath path, char* value); + + virtual void value(ElementPath path, long value); + + virtual void value(ElementPath path, float value); + + virtual void value(ElementPath path, bool value); + + virtual void value(ElementPath path); - virtual void endObject(); - - virtual void endDocument(); + virtual void endArray(ElementPath path); - virtual void startArray(); + virtual void endObject(ElementPath path); - virtual void startObject(); + virtual void endDocument(); + + virtual void whitespace(char c); }; From 181bdd1bb50493ed3f1ac7273e2b57e445e5dfe3 Mon Sep 17 00:00:00 2001 From: stechio Date: Tue, 16 Jan 2018 05:41:56 +0100 Subject: [PATCH 2/3] Element path refinements. --- ElementPath.cpp | 95 ++++++++------- ElementPath.h | 109 +++++++++++++++--- JsonStreamingParser.cpp | 4 +- .../JsonStreamingParser/ExampleParser.cpp | 6 +- 4 files changed, 148 insertions(+), 66 deletions(-) diff --git a/ElementPath.cpp b/ElementPath.cpp index f5e2b38..bd432aa 100644 --- a/ElementPath.cpp +++ b/ElementPath.cpp @@ -25,12 +25,16 @@ SOFTWARE. #include #include "ElementPath.h" -bool ElementSelector::isObject() { - return index < 0; +int ElementSelector::getIndex() { + return index; +} + +const char* ElementSelector::getKey() { + return key; } -void ElementSelector::moveNext() { - index++; +bool ElementSelector::isObject() { + return index < 0; } void ElementSelector::reset() { @@ -48,73 +52,80 @@ void ElementSelector::set(char* key) { this->index = -1; } -/* - * Builds the string representation of this node position within - * its parent. - */ +void ElementSelector::step() { + index++; +} + void ElementSelector::toString(char* buffer) { if (index >= 0) { - sprintf(buffer, "[%d]", index); + sprintf(buffer, "%s[%d]", buffer, index); } else { - strcpy(buffer, key); + strcat(buffer, key); } } ElementSelector* ElementPath::get(int index) { - if(index >= count) + if (index >= count + || (index < 0 && (index += count - 1) < 0)) return NULL; - + return &selectors[index]; } -int ElementPath::getCurrentIndex() { - return peek()->index; +int ElementPath::getCount() { + return count; +} + +ElementSelector* ElementPath::getCurrent() { + return current; +} + +int ElementPath::getIndex() { + return getIndex(current); +} + +int ElementPath::getIndex(int index) { + return getIndex(get(index)); +} + +int ElementPath::getIndex(ElementSelector* selector) { + return selector != NULL ? selector->index : -1; } -char* ElementPath::getCurrentKey() { - return peek()->key; +const char* ElementPath::getKey() { + return current != NULL ? current->key : "\0"; } -ElementSelector* ElementPath::peek() { - return currentSelector; +const char* ElementPath::getKey(int index) { + return getKey(get(index)); +} + +const char* ElementPath::getKey(ElementSelector* selector) { + return selector != NULL ? selector->key : "\0"; +} + +ElementSelector* ElementPath::getParent() { + return get(-1); } void ElementPath::pop() { if(count > 0) { - count--; - currentSelector = count > 0 ? &selectors[count - 1] : NULL; + current = --count > 0 ? &selectors[count - 1] : NULL; } } -ElementSelector ElementPath::push() { - (currentSelector = &selectors[count++])->reset(); -} - -/* - * Builds the full path corresponding to the current node position. - * - * For example, "weather[0].id" corresponds to a 3-level hierarchy: - * { - * "weather" : [ - * { - * "id" : ..., <===== HERE IT IS - * ... : ... - * }, - * { ... } - * ], - * ... - * } - */ +void ElementPath::push() { + (current = &selectors[count++])->reset(); +} + void ElementPath::toString(char* buffer) { if (count <= 0) return; - char elementBuffer[20]; for(int index = 0; index < count; index++) { if(index > 0 && selectors[index].isObject()) { strcat(buffer, "."); } - selectors[index].toString(elementBuffer); - strcat(buffer, elementBuffer); + selectors[index].toString(buffer); } } diff --git a/ElementPath.h b/ElementPath.h index 325b926..55a1d81 100644 --- a/ElementPath.h +++ b/ElementPath.h @@ -23,41 +23,112 @@ SOFTWARE. */ /* - * Unified element selector. - * Represents the handle associated to an element within either - * an object (key) or an array (index). - */ + Unified element selector. + Represents the handle associated to an element within either + an object (key) or an array (index). +*/ class ElementSelector { - public: + friend class ElementPath; + friend class JsonStreamingParser; + + private: int index; char key[20]; + + public: + int getIndex(); + + const char* getKey(); bool isObject(); - void moveNext(); + + /* + Builds the string representation of this node position within + its parent. + */ + void toString(char* buffer); + + private: void reset(); + void set(int index); + void set(char* key); - void toString(char* buffer); + + /* + Advances to next index. + */ + void step(); }; /* - * Hierarchical path to currently parsed element. - * It eases element filtering, keeping track of the current node - * position. - */ + Hierarchical path to currently parsed element. + It eases element filtering, keeping track of the current node + position. +*/ class ElementPath { + friend class JsonStreamingParser; + private: - ElementSelector* currentSelector; + int count = 0; + ElementSelector* current; ElementSelector selectors[20]; public: - int count = 0; - + /* + Gets the element selector at the given level. + */ ElementSelector* get(int index); - int getCurrentIndex(); - char* getCurrentKey(); - ElementSelector* peek(); - void pop(); - ElementSelector push(); + + int getCount(); + + /* + Gets current element selector. + */ + ElementSelector* getCurrent(); + + /* + Gets current element's index (in case of array). + */ + int getIndex(); + + int getIndex(int index); + + /* + Gets current element's key (in case of object). + */ + const char* getKey(); + + const char* getKey(int index); + + /* + Gets parent element selector. + */ + ElementSelector* getParent(); + + /* + Builds the full path corresponding to the current node position. + + For example, "weather[0].id" corresponds to a 3-level hierarchy: + { + "weather" : [ + { + "id" : ..., <===== HERE IT IS + ... : ... + }, + { ... } + ], + ... + } + */ void toString(char* buffer); + + private: + int getIndex(ElementSelector* selector); + + const char* getKey(ElementSelector* selector); + + void pop(); + + void push(); }; diff --git a/JsonStreamingParser.cpp b/JsonStreamingParser.cpp index 7932857..df19bc5 100644 --- a/JsonStreamingParser.cpp +++ b/JsonStreamingParser.cpp @@ -72,7 +72,7 @@ void JsonStreamingParser::parse(char c) { if (c == ']') { endArray(); } else { - path.peek()->moveNext(); + path.getCurrent()->step(); startValue(c); } break; @@ -214,7 +214,7 @@ void JsonStreamingParser::endString() { stackPos--; if (popped == STACK_KEY) { buffer[bufferPos] = '\0'; - path.peek()->set(buffer); + path.getCurrent()->set(buffer); state = STATE_END_KEY; } else if (popped == STACK_STRING) { buffer[bufferPos] = '\0'; diff --git a/examples/JsonStreamingParser/ExampleParser.cpp b/examples/JsonStreamingParser/ExampleParser.cpp index 1e9d3b4..1db9979 100644 --- a/examples/JsonStreamingParser/ExampleParser.cpp +++ b/examples/JsonStreamingParser/ExampleParser.cpp @@ -26,7 +26,7 @@ void ExampleListener::value(ElementPath path, float value) { Serial.print(" float value: "); Serial.println(value); - char* currentKey = path.getCurrentKey(); + const char* currentKey = path.getKey(); // Object entry? if(currentKey[0] != '\0') { if(strcmp(currentKey, "temp") == 0) { @@ -38,7 +38,7 @@ void ExampleListener::value(ElementPath path, float value) { } // Array item. else { - int currentIndex = path.getCurrentIndex(); + int currentIndex = path.getIndex(); if(currentIndex == 0) { //TODO: use the value. } else if(currentIndex < 5) { @@ -69,4 +69,4 @@ void ExampleListener::endDocument() { void ExampleListener::whitespace(char c) { Serial.println("whitespace"); -} +} \ No newline at end of file From 1f5e352376c31e59b2f093f10df8096abf3e3840 Mon Sep 17 00:00:00 2001 From: stechio Date: Thu, 18 Jan 2018 06:49:38 +0100 Subject: [PATCH 3/3] Variant-like value event method. --- ElementValue.cpp | 0 ElementValue.h | 102 ++++++++++++++++++ JsonListener.cpp | 1 - JsonListener.h | 14 +-- JsonStreamingParser.cpp | 20 ++-- JsonStreamingParser.h | 5 +- README.md | 8 +- .../JsonStreamingParser/ExampleParser.cpp | 20 +--- examples/JsonStreamingParser/ExampleParser.h | 12 +-- 9 files changed, 127 insertions(+), 55 deletions(-) create mode 100644 ElementValue.cpp create mode 100644 ElementValue.h diff --git a/ElementValue.cpp b/ElementValue.cpp new file mode 100644 index 0000000..e69de29 diff --git a/ElementValue.h b/ElementValue.h new file mode 100644 index 0000000..eaff04e --- /dev/null +++ b/ElementValue.h @@ -0,0 +1,102 @@ +#include + +union Variant { + bool boolValue; + float numValue; + const char* stringValue; +}; + +struct ElementValue { + private: + static const int Type_Null = 0; + static const int Type_Int = 1; + static const int Type_Float = 2; + static const int Type_String = 3; + static const int Type_Bool = 4; + + Variant data; + int type; + + public: + ElementValue with(float value) { + data.numValue = value; + type = Type_Float; + return *this; + } + + ElementValue with(long value) { + data.numValue = value; + type = Type_Int; + return *this; + } + + ElementValue with(bool value) { + data.boolValue = value; + type = Type_Bool; + return *this; + } + + ElementValue with(const char* value) { + data.stringValue = value; + type = Type_String; + return *this; + } + + ElementValue with() { + type = Type_Null; + return *this; + } + + bool getBool() { + return data.boolValue; + } + + const char* getString() { + return data.stringValue; + } + + float getFloat() { + return data.numValue; + } + + long getInt() { + return (long)data.numValue; + } + + bool isInt() { + return type == Type_Int; + } + + bool isFloat() { + return type == Type_Float; + } + + bool isString() { + return type == Type_String; + } + + bool isBool() { + return type == Type_Bool; + } + + bool isNull() { + return type == Type_Null; + } + + char* toString(char* buffer) { + if(isInt()) { + sprintf(buffer, "%d", getInt()); + } else if(isFloat()) { + sprintf(buffer, "%f", getFloat()); + } else if(isString()) { + sprintf(buffer, "\"%s\"", getString()); + } else if(isBool()) { + strcpy(buffer, getBool() ? "true" : "false"); + } else if(isNull()) { + strcpy(buffer, "null"); + } else { + strcpy(buffer, "?"); + } + return buffer; + } +}; \ No newline at end of file diff --git a/JsonListener.cpp b/JsonListener.cpp index 8b13789..e69de29 100644 --- a/JsonListener.cpp +++ b/JsonListener.cpp @@ -1 +0,0 @@ - diff --git a/JsonListener.h b/JsonListener.h index 04ebbe0..f63c0fc 100644 --- a/JsonListener.h +++ b/JsonListener.h @@ -30,6 +30,7 @@ See more at http://blog.squix.ch and https://github.com/squix78/json-streaming-p #include #include "ElementPath.h" +#include "ElementValue.h" class JsonListener { private: @@ -48,16 +49,7 @@ class JsonListener { virtual void startObject(ElementPath path) = 0; - virtual void value(ElementPath path, char* value) = 0; - - virtual void value(ElementPath path, bool value) = 0; - - virtual void value(ElementPath path, float value) = 0; - - virtual void value(ElementPath path, long value) = 0; - - virtual void value(ElementPath path) = 0; + virtual void value(ElementPath path, ElementValue value) = 0; virtual void whitespace(char c) = 0; -}; - +}; \ No newline at end of file diff --git a/JsonStreamingParser.cpp b/JsonStreamingParser.cpp index df19bc5..9525ce9 100644 --- a/JsonStreamingParser.cpp +++ b/JsonStreamingParser.cpp @@ -218,7 +218,7 @@ void JsonStreamingParser::endString() { state = STATE_END_KEY; } else if (popped == STACK_STRING) { buffer[bufferPos] = '\0'; - myListener->value(path, buffer); + myListener->value(path, elementValue.with(buffer)); state = STATE_AFTER_VALUE; } else { // throw new ParsingError($this->_line_number, $this->_char_number, @@ -411,13 +411,13 @@ void JsonStreamingParser::endUnicodeSurrogateInterstitial() { void JsonStreamingParser::endNumber() { buffer[bufferPos] = '\0'; if (strchr(buffer, '.') != NULL) { - float floatValue; - sscanf(buffer, "%f", &floatValue); - myListener->value(path, floatValue); + float floatValue; + sscanf(buffer, "%f", &floatValue); + myListener->value(path, elementValue.with(floatValue)); } else { - long intValue; - sscanf(buffer, "%d", &intValue); - myListener->value(path, intValue); + long intValue; + sscanf(buffer, "%d", &intValue); + myListener->value(path, elementValue.with(intValue)); } bufferPos = 0; state = STATE_AFTER_VALUE; @@ -440,7 +440,7 @@ void JsonStreamingParser::endDocument() { void JsonStreamingParser::endTrue() { buffer[bufferPos] = '\0'; if(strcmp(buffer, "true") == 0) { - myListener->value(path, true); + myListener->value(path, elementValue.with(true)); } else { // throw new ParsingError($this->_line_number, $this->_char_number, // "Expected 'true'. Got: ".$true); @@ -452,7 +452,7 @@ void JsonStreamingParser::endTrue() { void JsonStreamingParser::endFalse() { buffer[bufferPos] = '\0'; if(strcmp(buffer, "false") == 0) { - myListener->value(path, false); + myListener->value(path, elementValue.with(false)); } else { // throw new ParsingError($this->_line_number, $this->_char_number, // "Expected 'true'. Got: ".$true); @@ -464,7 +464,7 @@ void JsonStreamingParser::endFalse() { void JsonStreamingParser::endNull() { buffer[bufferPos] = '\0'; if(strcmp(buffer, "null") == 0) { - myListener->value(path); + myListener->value(path, elementValue.with()); } else { // throw new ParsingError($this->_line_number, $this->_char_number, // "Expected 'true'. Got: ".$true); diff --git a/JsonStreamingParser.h b/JsonStreamingParser.h index a74dfb5..882ffd5 100644 --- a/JsonStreamingParser.h +++ b/JsonStreamingParser.h @@ -59,8 +59,11 @@ class JsonStreamingParser { int state; int stack[20]; - ElementPath path; int stackPos = 0; + + ElementValue elementValue; + ElementPath path; + JsonListener* myListener; boolean doEmitWhitespace = false; diff --git a/README.md b/README.md index 7698729..6243178 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This library is a port of Salsify's PHP based json streaming parser (https://git Furthermore, this is a fork by stechio departing from squix78's original library to introduce some key enhancements: * explicit element path tracking (ElementPath class): object keys and array indices are managed in a robust, unified manner and exposed in each and every event (users are relieved the pain to jury-rig their own custom event-filtering mechanism) * string flattening: adhering to common best practises, cumbersome instances of std::string have been replaced by plain C-style char arrays. - * strongly-typed values: untypified (string) value event method has been replaced by overloaded, strongly-typed ones corresponding to actual serialization types. + * strongly-typed values: untypified (string) value event method has been replaced by a strongly-typed one corresponding to actual serialization types. ## Why yet another JSON parser? @@ -49,11 +49,7 @@ implement methods which will be notified in case of certain events in the feed o * endArray(ElementPath path) * startObject(ElementPath path) * endObject(ElementPath path) - * value(ElementPath path, char* value) - * value(ElementPath path, bool value) - * value(ElementPath path, float value) - * value(ElementPath path, long value) - * value(ElementPath path) + * value(ElementPath path, ElementValue value) In your implementation of these methods you will have to write problem specific code to find the parts of the document that you are interested in. Please see the example to understand what that means. In the example the ExampleListener implements the event methods declared in the JsonListener interface and prints to the serial console when they are called. diff --git a/examples/JsonStreamingParser/ExampleParser.cpp b/examples/JsonStreamingParser/ExampleParser.cpp index 1db9979..e192889 100644 --- a/examples/JsonStreamingParser/ExampleParser.cpp +++ b/examples/JsonStreamingParser/ExampleParser.cpp @@ -13,18 +13,13 @@ void ExampleListener::startObject(ElementPath path) { Serial.println("start object. "); } -void ExampleListener::value(ElementPath path, char* value) { -} - -void ExampleListener::value(ElementPath path, long value) { -} - -void ExampleListener::value(ElementPath path, float value) { +void ExampleListener::value(ElementPath path, ElementValue value) { char fullPath[200] = ""; path.toString(fullPath); Serial.print(fullPath); - Serial.print(" float value: "); - Serial.println(value); + Serial.print("': "); + char valueBuffer[50] = ""; + Serial.println(value.toString(valueBuffer)); const char* currentKey = path.getKey(); // Object entry? @@ -48,13 +43,6 @@ void ExampleListener::value(ElementPath path, float value) { } } -void ExampleListener::value(ElementPath path, bool value) { -} - -void ExampleListener::value(ElementPath path) { - Serial.println("value: null"); -} - void ExampleListener::endArray(ElementPath path) { Serial.println("end array. "); } diff --git a/examples/JsonStreamingParser/ExampleParser.h b/examples/JsonStreamingParser/ExampleParser.h index cc5ae9b..04fc8ce 100644 --- a/examples/JsonStreamingParser/ExampleParser.h +++ b/examples/JsonStreamingParser/ExampleParser.h @@ -11,21 +11,13 @@ class ExampleListener: public JsonListener { virtual void startObject(ElementPath path); - virtual void value(ElementPath path, char* value); - - virtual void value(ElementPath path, long value); - - virtual void value(ElementPath path, float value); - - virtual void value(ElementPath path, bool value); - - virtual void value(ElementPath path); - virtual void endArray(ElementPath path); virtual void endObject(ElementPath path); virtual void endDocument(); + + virtual void value(ElementPath path, ElementValue value); virtual void whitespace(char c); };