8.1 KiB
JSMN lightweight JSON parser for Tasmota
Intro: this library uses the JSMN in-place JSON parser. See https://github.com/zserge/jsmn and https://zserge.com/jsmn/
It is proposed as a replacement for ArduinoJson. It has less features, does only parsing but does it in a very efficient way.
Benefits
First, the memory impact is very low: 4 bytes per token and no need to add an extra buffer for values. Second, the code is much smaller than ArduinoJson by 5-7KB.
How to use
{"Device":"0x1234","Power":true,"Temperature":25.5}
The JSON above is split into 8 tokens, and requires 32 bytes of dynamic memory.
- Token 0:
OBJECT
, size=3:{"Device":"0x1234","Power":true,"Temperature":25.5}
- Token 1:
KEY
:Device
- Token 2:
STRING
:0x1234
- Token 3:
KEY
:Power
- Token 4:
BOOL_TRUE
:true
- Token 5:
KEY
:Temperature
- Token 6:
FLOAT
:25.5
- Token 7:
INVALID
If what you need is to parse a JSON Object for values with default values:
#include "JsonParser.h"
char json_buffer[] = "{\"Device\":\"0x1234\",\"Power\":true,\"Temperature\":25.6}";
JsonParser parser(json_buffer);
JsonParserObject root = parser.getRootObject();
if (!root) { ResponseCmndChar_P(PSTR("Invalid JSON")); return; }
uint16_t d = root.getUInt(PSTR("DEVICE"), 0xFFFF); // case insensitive
bool b = root.getBool(PSTR("Power"), false);
float f = root.getFloat(PSTR("Temperature), -100);
Alternative pattern, if you want to test the existence of the attribute first:
#include "JsonParser.h"
char json_buffer[] = "{\"Device\":\"0x1234\",\"Power\":true,\"Temperature\":25.6}";
JsonParser parser(json_buffer);
JsonParserObject root = parser.getRootObject();
if (!root) { ResponseCmndChar_P(PSTR("Invalid JSON")); return; }
JsonParserToken val = root[PSTR("DEVICE")];
if (val) {
d = val.getUInt();
}
val = root[PSTR("Power")];
if (val) {
b = val.getBool();
}
val = root[PSTR("Temperature)];
if (val) {
f = val.getFloat();
}
WARNING: never use the following form:
JsonParserObject root = JsonParser(json_buffer).getRootObject();
In this case, the JsonParser
object is temporary and destroyed at the end of the expression. Setting the JsonParser to a local variable ensures that the lifetime of the object is extended to the end of the scope.
Types and conversion
JSMN relies on the concept of JSON Tokens JsonParserToken
. Tokens do not hold any data, but point to token boundaries in JSON string instead. Every jsmn token has a type, which indicates the type of corresponding JSON token. JSMN for Tasmota extends the type from JSMN to ease further parsing.
Types are:
INVALID
invalid token or end of stream, see Error Handling belowOBJECT
a JSON sub-object,size()
contains the number of key/values of the objectARRAY
a JSON array,size()
contains the number of sub valuesSTRING
a JSON string, return the sub-string, unescaped, without surrounding quotes. UTF-8 is supported.PRIMITIVE
an unrecognized JSON token, consider as an errorKEY
a JSON key in an object as a stringNULL
a JSONnull
value, automatically converted to0
or""
BOOL_FALSE
a JSONfalse
value, automatically converted to0
or""
BOOL_TRUE
a JSONtrue
value, automatically converted to1
or""
UINT
a JSON unsigned intINT
a JSON negative intFLOAT
a JSON floating point value, i.e. a numerical value containing a decimal ot.
Note: values are converted in this priority: 1/ UINT
, 2/ INT
, 3/ FLOAT
.
JsonParserToken
support the following getters:
getBool()
: returns true if 'true' or non-zero int (default false)getUInt()
: converts to unsigned int (default 0), boolean true is 1getInt()
: converts to signed int (default 0), boolean true is 1getULong()
: converts to unsigned 64 bits (default 0), boolean is 1getStr()
: converts to string (default "")
There are variants of the same function for which you can choose the default values. Remember that using a getter if the token type is INVALID returns the default value.
Conversion to OBJECT
or ARRAY
:
getObject()
: converts token toOBJECT
orINVALID
getArray()
: converts token toARRAY
orINVALID
For JsonParserKey
:
getValue()
: returns the value token associated to the key
Checking Token types
Type checking for JsonParserToken
:
isSingleToken()
returnstrue
for a single level token,false
forOBJECT
,ARRAY
andINVALID
isKey()
returnstrue
for aKEY
within anOBJECT
isStr()
returnstrue
forSTRING
(note: other types can still be read as strings withgetStr()
)isNull()
returnstrue
forNULL
isBool()
returnstrue
forBOOL_FALSE
orBOOL_TRUE
isUInt()
returnstrue
forUINT
(see below for number conversions)isInt()
returnstrue
forINT
(see below for number conversions)isFloat()
returnstrue
forFLOAT
(see below for number conversions)isNum()
returnstrue
for any numerical value, i.e.UINT
,INT
orFLOAT
isObject()
returnstrue
forOBJECT
isArray()
returnstrue
forARRAY
isValid()
returnstrue
for any type exceptINVALID
JSMN for Tasmota provides sub-classes:
JsonParserKey
of typeKEY
orINVALID
, used as a return value for Object iteratorsJsonParserObject
of typeOBJECT
orINVALID
, providing iteratorsJsonParserArray
of typeARRAY
orINVALID
, providing iterators
Converting from Token to Object or Array is done with token.getObject()
or token.getArray()
. If the conversion is invalid, the resulting object has type INVALID
(see Error Handling).
Iterators and accessors for Objects and Arrays
The JsonParserObject
provides an easy to use iterator:
JsonParserToken obj = <...>
for (auto key : obj) {
// key is of type JsonParserKey
JsonParserToken valie = key.getValue(); // retrieve the value associated to key
}
If the object contains only one element, you can retrieve it with obj.getFirstElement()
.
You can access any key with obj["Key"]
. Note: the search is on purpose case insensitive as it is the norm in Tasmota. The search string can be in PROGMEM. If the token is not found, it is of type INVALID
.
The JsonParserArray
provides an easy to use iterator:
JsonParserArray arr = <...>
for (auto elt : arr) {
// elt is of type JsonParserToken
}
You can access any element in the array with the []
operator. Ex: arr[0]
fof first element. If the index is invalid, the token has type INVALID
.
Memory
The JsonParserToken
fits in 32 bits, so it can be freely returned or copied without any penalty of object copying. Hence it doesn't need the const
modifier either, since it is always passed by value.
Error handling
This library uses a zero error
pattern. This means that calls never report any error nor throw exceptions. If something wrong happens (bad JSON, token not found...), function return an Invalid Token. You can call any function on an Invalid Token, they will always return the same Invalid Token (aka fixed point).
You can easily test if the current token is invalid with the following:
Short version:
if (token) { /* token is valid */ }
Long version:
if (token->isValiid()) { /* token is valid */ }
This pattern allows to cascade calls and check only the final result:
char json_buffer[] = "<json>";
JsonParserObject json = JsonParser(json_buffer).getRootObject();
JsonParserToken zbstatus_tok = json["ZbStatus"];
JsonParserObject zbstatus = zbstatus_tok.getObject();
if (zbstatus) { /* do some stuff */
// reaching this point means: JSON is valid, there is a root object, there is a `ZbStatus` key and it contains a sub-object
}
Warning: there is an explicit convert to bool
to allow the short version. Be careful, (bool)token
is equivalent to token->isValid()
, it is NOT equivalent to token->getBool()
.
Limits
Please keep in mind the current limits for this library:
- Maximum JSON buffer size 2047 bytes
- Maximum 63 JSON tokens
- No support for exponent in floats (i.e.
1.0e3
is invalid)
These limits shouldn't be a problem since buffers in Tasmota are limited to 1.2KB. The support for exponent in floats is commented out and can be added if needed (slight increase in Flash size)