Tasmota/lib/default/jsmn-shadinger-1.0/src/JsonParser.cpp
2022-09-06 19:49:12 +02:00

555 lines
16 KiB
C++

/*
JsonParser.cpp - lightweight JSON parser
Copyright (C) 2021 Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "JsonParser.h"
#include <Arduino.h>
/*********************************************************************************************\
* Utilities
\*********************************************************************************************/
const char * k_current_json_buffer = "";
// returns nibble value or -1 if not an hex digit
static int32_t asc2byte(char chr) {
if (chr >= '0' && chr <= '9') { return chr - '0'; }
else if (chr >= 'A' && chr <= 'F') { return chr + 10 - 'A'; }
else if (chr >= 'a' && chr <= 'f') { return chr + 10 - 'a'; }
return -1;
}
/*********************************************************************************************\
* Lightweight String to Float, because atof() or strtof() takes 10KB
*
* To remove code, exponents are not parsed
* (commented out below, just in case we need them after all)
*
* Moved to double to be able to parse 32 bits int as well without loss in accuracy
\*********************************************************************************************/
// Inspired from https://searchcode.com/codesearch/view/22115068/
double JsonParserToken::json_strtof(const char* s) {
const char* p = s;
double value = 0.;
int32_t sign = +1;
double factor;
uint32_t base = 10; // support hex mode if start with Ox or OX
// unsigned int expo;
while (isspace(*p)){ // skip any leading white-spaces
p++;
}
switch (*p) {
case '-': sign = -1; // no break on purpose
case '+': p++;
default : break;
}
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { // detect hex mode
base = 16;
p += 2;
}
int32_t v; // temp nibble value
while ((v = asc2byte(*p)) >= 0) {
value = value * base + v;
p++;
}
if (*p == '.' ) {
factor = 1.0f;
p++;
while ((v = asc2byte(*p)) >= 0) {
factor /= base;
value += v * factor;
p++;
}
}
// if ( (*p | 32) == 'e' ) {
// expo = 0;
// factor = 10.L;
// switch (*++p) { // ja hier weiß ich nicht, was mindestens nach einem 'E' folgenden MUSS.
// case '-': factor = 0.1;
// case '+': p++;
// break;
// case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
// break;
// default : value = 0.L;
// p = s;
// goto done;
// }
// while ( (unsigned int)(*p - '0') < 10u )
// expo = 10 * expo + (*p++ - '0');
// while ( 1 ) {
// if ( expo & 1 )
// value *= factor;
// if ( (expo >>= 1) == 0 )
// break;
// factor *= factor;
// }
// }
// done:
// if ( endptr != NULL )
// *endptr = (char*)p;
return value * sign;
}
/*********************************************************************************************\
* Read-only JSON token object, fits in 32 bits
\*********************************************************************************************/
void JsonParserToken::skipToken(void) {
// printf("skipToken type = %d %s\n", t->type, json_string + t->start);
switch (t->type) {
case JSMN_OBJECT:
skipObject();
break;
case JSMN_ARRAY:
skipArray();
break;
case JSMN_STRING:
case JSMN_PRIMITIVE:
case JSMN_KEY:
case JSMN_NULL:
case JSMN_BOOL_FALSE:
case JSMN_BOOL_TRUE:
case JSMN_FLOAT:
case JSMN_INT:
case JSMN_UINT:
t++; // skip 1 token
break;
case JSMN_INVALID:
default:
break; // end of stream, stop advancing
}
}
void JsonParserToken::skipArray(void) {
if (t->type == JSMN_ARRAY) {
size_t obj_size = t->size;
t++; // array root
if (t->type == JSMN_INVALID) { return; }
for (uint32_t i=0; i<obj_size; i++) {
skipToken();
}
}
}
// skip to right after the current Object
void JsonParserToken::skipObject(void) {
if (t->type == JSMN_OBJECT) {
size_t obj_size = t->size;
t++; // object root
if (t->type == JSMN_INVALID) { return; }
for (uint32_t i=0; i<obj_size; i++) {
t++; // skip Key
if (t->type == JSMN_INVALID) { return; }
skipToken();
}
}
}
/*********************************************************************************************\
* JsonParserArray
\*********************************************************************************************/
JsonParserArray::JsonParserArray(const jsmntok_t * token) : JsonParserToken(token) {
if (t->type != JSMN_ARRAY) {
t = &token_bad;
}
}
JsonParserArray::JsonParserArray(const JsonParserToken token) : JsonParserToken(token.t) {
if (t->type != JSMN_ARRAY) {
t = &token_bad;
}
}
JsonParserArray::const_iterator::const_iterator(const JsonParserArray t): tok(t), remaining(0) {
if (tok.t == &token_bad) { tok.t = nullptr; }
if (nullptr != tok.t) {
// ASSERT type == JSMN_ARRAY by constructor
remaining = tok.t->size;
tok.nextOne(); // skip array root token
}
}
JsonParserArray::const_iterator JsonParserArray::const_iterator::const_iterator::operator++() {
if (remaining <= 1) { tok.t = nullptr; }
else {
remaining--;
tok.skipToken(); // munch value
if (tok.t->type == JSMN_INVALID) { tok.t = nullptr; } // unexpected end of stream
}
return *this;
}
JsonParserToken JsonParserArray::operator[](int32_t i) const {
if ((i >= 0) && (i < t->size)) {
uint32_t index = 0;
for (const auto elt : *this) {
if (i == index) {
return elt;
}
index++;
}
}
// fallback
return JsonParserToken(&token_bad);
}
/*********************************************************************************************\
* JsonParserObject
\*********************************************************************************************/
JsonParserObject::JsonParserObject(const jsmntok_t * token) : JsonParserToken(token) {
if (t->type != JSMN_OBJECT) {
t = &token_bad;
}
}
JsonParserObject::JsonParserObject(const JsonParserToken token) : JsonParserToken(token.t) {
if (t->type != JSMN_OBJECT) {
t = &token_bad;
}
}
JsonParserKey JsonParserObject::getFirstElement(void) const {
if (t->size > 0) {
return JsonParserKey(t+1); // return next element and cast to Key
} else {
return JsonParserKey(&token_bad);
}
}
JsonParserObject::const_iterator::const_iterator(const JsonParserObject t): tok(t), remaining(0) {
if (tok.t == &token_bad) { tok.t = nullptr; }
if (nullptr != tok.t) {
// ASSERT type == JSMN_OBJECT by constructor
remaining = tok.t->size;
tok.nextOne();
}
}
JsonParserObject::const_iterator JsonParserObject::const_iterator::operator++() {
if (remaining <= 1) { tok.t = nullptr; }
else {
remaining--;
tok.nextOne(); // munch key
if (tok.t->type == JSMN_INVALID) { tok.t = nullptr; } // unexpected end of stream
tok.skipToken(); // munch value
if (tok.t->type == JSMN_INVALID) { tok.t = nullptr; } // unexpected end of stream
}
return *this;
}
/*********************************************************************************************\
* JsonParserKey
\*********************************************************************************************/
JsonParserKey::JsonParserKey(const jsmntok_t * token) : JsonParserToken(token) {
if (t->type != JSMN_KEY) {
t = &token_bad;
}
}
JsonParserKey::JsonParserKey(const JsonParserToken token) : JsonParserToken(token.t) {
if (t->type != JSMN_KEY) {
t = &token_bad;
}
}
JsonParserToken JsonParserKey::getValue(void) const {
return JsonParserToken(t+1);
}
/*********************************************************************************************\
* Implementation for JSON Parser
\*********************************************************************************************/
// fall-back token object when parsing failed
const jsmntok_t token_bad = { JSMN_INVALID, 0, 0, 0 };
JsonParser::JsonParser(char * json_in) :
_size(0),
_token_len(0),
_tokens(nullptr),
_json(nullptr)
{
parse(json_in);
}
JsonParser::~JsonParser() {
this->free();
}
const JsonParserObject JsonParser::getRootObject(void) const {
return JsonParserObject(&_tokens[0]);
}
const JsonParserToken JsonParser::operator[](int32_t i) const {
if ((_token_len > 0) && (i < _token_len)) {
return JsonParserToken(&_tokens[i]);
} else {
return JsonParserToken(&token_bad);
}
}
// pointer arithmetic
// ptrdiff_t JsonParser::index(JsonParserToken token) const {
// return token.t - _tokens;
// }
bool JsonParserToken::getBool(bool val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return true;
if (t->type == JSMN_BOOL_FALSE) return false;
if (isSingleToken()) return strtol(&k_current_json_buffer[t->start], nullptr, 0) != 0;
return false;
}
int32_t JsonParserToken::getInt(int32_t val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return strtol(&k_current_json_buffer[t->start], nullptr, 0);
return 0;
}
uint32_t JsonParserToken::getUInt(uint32_t val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return strtoul(&k_current_json_buffer[t->start], nullptr, 0);
return 0;
}
uint64_t JsonParserToken::getULong(uint64_t val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return strtoull(&k_current_json_buffer[t->start], nullptr, 0);
return 0;
}
float JsonParserToken::getFloat(float val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_BOOL_TRUE) return 1;
if (isSingleToken()) return json_strtof(&k_current_json_buffer[t->start]);
return 0;
}
const char * JsonParserToken::getStr(const char * val) const {
if (t->type == JSMN_INVALID) { return val; }
if (t->type == JSMN_NULL) return "null";
return (t->type >= JSMN_STRING) ? &k_current_json_buffer[t->start] : val;
}
JsonParserObject JsonParserToken::getObject(void) const { return JsonParserObject(*this); }
JsonParserArray JsonParserToken::getArray(void) const { return JsonParserArray(*this); }
bool JsonParserToken::getBool(void) const { return getBool(false); }
int32_t JsonParserToken::getInt(void) const { return getInt(0); }
uint32_t JsonParserToken::getUInt(void) const { return getUInt(0); }
uint64_t JsonParserToken::getULong(void) const { return getULong(0); }
float JsonParserToken::getFloat(void) const { return getFloat(0); }
const char * JsonParserToken::getStr(void) const { return getStr(""); }
int32_t JsonParserObject::getInt(const char * needle, int32_t val) const {
return (*this)[needle].getInt(val);
}
uint32_t JsonParserObject::getUInt(const char * needle, uint32_t val) const {
return (*this)[needle].getUInt(val);
}
uint64_t JsonParserObject::getULong(const char * needle, uint64_t val) const {
return (*this)[needle].getULong(val);
}
float JsonParserObject::getFloat(const char * needle, float val) const {
return (*this)[needle].getFloat(val);
}
const char * JsonParserObject::getStr(const char * needle, const char * val) const {
return (*this)[needle].getStr(val);
}
const char * JsonParserObject::getStr(const char * needle) const {
return getStr(needle, "");
}
void JsonParser::parse(char * json_in) {
k_current_json_buffer = "";
if (nullptr == json_in) { return; }
_json = json_in;
k_current_json_buffer = _json;
size_t json_len = strlen(json_in);
if (_size == 0) {
// first run is used to count tokens before allocation
jsmn_init(&this->_parser);
int32_t _token_len = jsmn_parse(&this->_parser, json_in, json_len, nullptr, 0);
if (_token_len <= 0) { return; }
_size = _token_len + 1;
}
allocate();
jsmn_init(&this->_parser);
_token_len = jsmn_parse(&this->_parser, json_in, json_len, _tokens, _size);
// TODO error checking
if (_token_len >= 0) {
postProcess(json_len);
} else {
this->free(); // invalid JSON
}
}
// post process the parsing by pre-munching extended types
void JsonParser::postProcess(size_t json_len) {
// add an end marker
if (_size > _token_len) {
_tokens[_token_len].type = JSMN_INVALID;
_tokens[_token_len].start = json_len;
_tokens[_token_len].len = 0;
_tokens[_token_len].size = 0;
}
for (uint32_t i=0; i<_token_len; i++) {
jsmntok_t & tok = _tokens[i];
if (tok.type >= JSMN_STRING) {
// we modify to null-terminate the primitive
_json[tok.start + tok.len] = 0;
}
if (tok.type == JSMN_STRING) {
if (tok.size == 1) { tok.type = JSMN_KEY; }
else { json_unescape(&_json[tok.start]); }
} else if (tok.type == JSMN_PRIMITIVE) {
if (tok.len >= 0) {
// non-null string
char c0 = _json[tok.start];
switch (c0) {
case 'n':
case 'N':
tok.type = JSMN_NULL;
break;
case 't':
case 'T':
tok.type = JSMN_BOOL_TRUE;
break;
case 'f':
case 'F':
tok.type = JSMN_BOOL_FALSE;
break;
case '-':
case '0'...'9':
// look if there is a '.' in the string
if (nullptr != memchr(&_json[tok.start], '.', tok.len)) {
tok.type = JSMN_FLOAT;
} else if (c0 == '-') {
tok.type = JSMN_INT;
} else {
tok.type = JSMN_UINT;
}
break;
default:
tok.type = JSMN_PRIMITIVE;
break;
}
} else {
tok.type = JSMN_PRIMITIVE;
}
}
}
}
JsonParserToken JsonParserObject::operator[](const char * needle) const {
// key can be in PROGMEM
if ((!this->isValid()) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return JsonParserToken(&token_bad);
}
// if needle == "?" then we return the first valid key
bool wildcard = (strcmp_P("?", needle) == 0);
for (const auto key : *this) {
if (wildcard) { return key.getValue(); }
if (0 == strcasecmp_P(key.getStr(), needle)) { return key.getValue(); }
}
// if not found
return JsonParserToken(&token_bad);
}
JsonParserToken JsonParserObject::operator[](const String & needle) const {
return (*this)[needle.c_str()];
}
JsonParserToken JsonParserObject::findStartsWith(const char * needle) const {
// key can be in PROGMEM
if ((!this->isValid()) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
return JsonParserToken(&token_bad);
}
String needle_s((const __FlashStringHelper *)needle);
needle_s.toLowerCase();
for (const auto key : *this) {
String key_s(key.getStr());
key_s.toLowerCase();
if (key_s.startsWith(needle_s)) {
return key.getValue();
}
}
// if not found
return JsonParserToken(&token_bad);
}
const char * JsonParserObject::findConstCharNull(const char * needle) const {
const char * r = (*this)[needle].getStr();
if (*r == 0) { r = nullptr; } // if empty string
return r;
}
// JsonParserToken JsonParser::find(JsonParserObject obj, const char *needle, bool case_sensitive) const {
// // key can be in PROGMEM
// if ((!obj.isValid()) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
// return JsonParserToken(&token_bad);
// }
// // if needle == "?" then we return the first valid key
// bool wildcard = (strcmp_P("?", needle) == 0);
// for (const auto key : obj) {
// if (wildcard) { return key.getValue(); }
// if (case_sensitive) {
// if (0 == strcmp_P(this->getStr(key), needle)) { return key.getValue(); }
// } else {
// if (0 == strcasecmp_P(this->getStr(key), needle)) { return key.getValue(); }
// }
// }
// // if not found
// return JsonParserToken(&token_bad);
// }
void JsonParser::free(void) {
if (nullptr != _tokens) {
delete[] _tokens; // TODO
_tokens = nullptr;
}
}
void JsonParser::allocate(void) {
this->free();
if (_size != 0) {
_tokens = new jsmntok_t[_size];
}
}