Tasmota/tasmota/support_json.ino

116 lines
3.6 KiB
C++

/*
support_json.ino - JSON support functions
Copyright (C) 2020 Theo Arends and 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/>.
*/
/*********************************************************************************************\
* JSON parsing
\*********************************************************************************************/
// does the character needs to be escaped, and if so with which character
char EscapeJSONChar(char c) {
if ((c == '\"') || (c == '\\')) {
return c;
}
if (c == '\n') { return 'n'; }
if (c == '\t') { return 't'; }
if (c == '\r') { return 'r'; }
if (c == '\f') { return 'f'; }
if (c == '\b') { return 'b'; }
return 0;
}
String EscapeJSONString(const char *str) {
// As this function is used in ResponseCmndChar() and ResponseCmndIdxChar()
// it needs to be PROGMEM safe!
String r("");
if (nullptr == str) { return r; }
bool needs_escape = false;
size_t len_out = 1;
const char* c = str;
char ch = '.';
while (ch != '\0') {
ch = pgm_read_byte(c++);
if (EscapeJSONChar(ch)) {
len_out++;
needs_escape = true;
}
len_out++;
}
if (needs_escape) {
// we need to escape some chars
// allocate target buffer
r.reserve(len_out);
c = str;
char *d = r.begin();
char ch = '.';
while (ch != '\0') {
ch = pgm_read_byte(c++);
char c2 = EscapeJSONChar(ch);
if (c2) {
*d++ = '\\';
*d++ = c2;
} else {
*d++ = ch;
}
}
*d = 0; // add NULL terminator
r = (char*) r.begin(); // assign the buffer to the string
} else {
r = FPSTR(str);
}
return r;
}
/*********************************************************************************************\
* Find key - case insensitive
\*********************************************************************************************/
// Given a JsonObject, finds the value as JsonVariant for the key needle.
// The search is case-insensitive, and will find the first match in the order of keys in JSON
//
// If the key is not found, returns a nullptr
// Input: needle cannot be NULL but may be PROGMEM
const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle) {
// key can be in PROGMEM
// if needle == "?" then we return the first valid key
bool wildcard = strcmp_P("?", needle) == 0;
if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle)) || (!json.success())) {
return *(JsonVariant*)nullptr;
}
for (JsonObject::const_iterator it=json.begin(); it!=json.end(); ++it) {
const char *key = it->key;
const JsonVariant &value = it->value;
if (wildcard || (0 == strcasecmp_P(key, needle))) {
return value;
}
}
// if not found
return *(JsonVariant*)nullptr;
}
// This function returns true if the JsonObject contains the specified key
// It's just a wrapper to the previous function but it can be tricky to test nullptr on an object ref
bool HasKeyCaseInsensitive(const JsonObject &json, const char *needle) {
return &GetCaseInsensitive(json, needle) != nullptr;
}