* Copyright (c) 2013-2024 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/
#include "deconz/u_assert.h"
#include "deconz/u_sstream.h"
#include "utils/scratchmem.h"
#include "json.h"
#include <QLocale>
static QString sanitizeString(QString str)
{
str.replace(QLatin1String("\\"), QLatin1String("\\\\"));
str.replace(QLatin1String("\""), QLatin1String("\\\""));
str.replace(QLatin1String("\b"), QLatin1String("\\b"));
str.replace(QLatin1String("\f"), QLatin1String("\\f"));
str.replace(QLatin1String("\n"), QLatin1String("\\n"));
str.replace(QLatin1String("\r"), QLatin1String("\\r"));
str.replace(QLatin1String("\t"), QLatin1String("\\t"));
return QString(QLatin1String("\"%1\"")).arg(str);
}
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep)
{
QByteArray res;
foreach(const QByteArray &i, list)
{
if(!res.isEmpty())
{
res += sep;
}
res += i;
}
return res;
}
* parse
*/
QVariant Json::parse(const QString &json)
{
bool success = true;
return Json::parse(json, success);
}
* parse
*/
QVariant Json::parse(const QString &json, bool &success)
{
success = true;
if(!json.isNull() || !json.isEmpty())
{
QString data = json;
int index = 0;
QVariant value = Json::parseValue(data, index, success);
return value;
}
else
{
return QVariant();
}
}
* serialize
*/
QByteArray Json::serialize(const QVariant &data)
{
bool success = true;
return Json::serialize(data, success);
}
* serialize
*/
QByteArray Json::serialize(const QVariant &data, bool &success)
{
QByteArray str;
success = true;
if(!data.isValid())
{
str = "null";
}
else if((data.type() == QVariant::List) || (data.type() == QVariant::StringList))
{
QList<QByteArray> values;
const QVariantList list = data.toList();
foreach(const QVariant& v, list)
{
QByteArray serializedValue = serialize(v);
if(serializedValue.isNull())
{
success = false;
break;
}
values << serializedValue;
}
str = "[" + join( values, "," ) + "]";
}
else if(data.type() == QVariant::Map)
{
const QVariantMap vmap = data.toMap();
QMapIterator<QString, QVariant> it( vmap );
str = "{";
QList<QByteArray> pairs;
while(it.hasNext())
{
it.next();
QByteArray serializedValue = serialize(it.value());
if(serializedValue.isNull())
{
success = false;
break;
}
pairs << sanitizeString(it.key()).toUtf8() + ":" + serializedValue;
}
str += join(pairs, ",");
str += "}";
}
else if (data.isNull())
{
if (data.type() == QVariant::String)
{
str = "\"\"";
}
else
{
str = "null";
}
}
else if((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray))
{
str = sanitizeString(data.toString())
#if QT_VERSION >= 0x050000
.toUtf8();
#else
.toAscii();
#endif
}
else if(data.type() == QVariant::Double)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5,7, 0))
str = QByteArray::number(data.toDouble(), 'f', QLocale::FloatingPointShortest);
#else
str = QByteArray::number(data.toDouble());
#endif
}
else if (data.type() == QVariant::Bool)
{
str = data.toBool() ? "true" : "false";
}
else if (data.type() == QVariant::ULongLong)
{
str = QByteArray::number(data.value<qulonglong>());
}
else if ( data.canConvert<qlonglong>() )
{
str = QByteArray::number(data.value<qlonglong>());
}
else if (data.canConvert<QString>())
{
str = sanitizeString(data.toString())
#if QT_VERSION >= 0x050000
.toUtf8();
#else
.toAscii();
#endif
}
else
{
success = false;
}
if (success)
{
return str;
}
else
{
return QByteArray();
}
}
* parseValue
*/
QVariant Json::parseValue(const QString &json, int &index, bool &success)
{
switch(Json::lookAhead(json, index))
{
case JsonTokenString:
return Json::parseString(json, index, success);
case JsonTokenNumber:
return Json::parseNumber(json, index);
case JsonTokenCurlyOpen:
return Json::parseObject(json, index, success);
case JsonTokenSquaredOpen:
return Json::parseArray(json, index, success);
case JsonTokenTrue:
Json::nextToken(json, index);
return QVariant(true);
case JsonTokenFalse:
Json::nextToken(json, index);
return QVariant(false);
case JsonTokenNull:
Json::nextToken(json, index);
return QVariant();
case JsonTokenNone:
break;
}
success = false;
return QVariant();
}
* parseObject
*/
QVariant Json::parseObject(const QString &json, int &index, bool &success)
{
QVariantMap map;
int token;
Json::nextToken(json, index);
bool done = false;
while(!done)
{
token = Json::lookAhead(json, index);
if(token == JsonTokenNone)
{
success = false;
return QVariantMap();
}
else if(token == JsonTokenComma)
{
Json::nextToken(json, index);
}
else if(token == JsonTokenCurlyClose)
{
Json::nextToken(json, index);
return map;
}
else
{
QString name = Json::parseString(json, index, success).toString();
if(!success)
{
return QVariantMap();
}
token = Json::nextToken(json, index);
if(token != JsonTokenColon)
{
success = false;
return QVariant(QVariantMap());
}
QVariant value = Json::parseValue(json, index, success);
if(!success)
{
return QVariantMap();
}
map[name] = value;
}
}
return QVariant(map);
}
* parseArray
*/
QVariant Json::parseArray(const QString &json, int &index, bool &success)
{
QVariantList list;
Json::nextToken(json, index);
bool done = false;
while(!done)
{
int token = Json::lookAhead(json, index);
if(token == JsonTokenNone)
{
success = false;
return QVariantList();
}
else if(token == JsonTokenComma)
{
Json::nextToken(json, index);
}
else if(token == JsonTokenSquaredClose)
{
Json::nextToken(json, index);
break;
}
else
{
QVariant value = Json::parseValue(json, index, success);
if(!success)
{
return QVariantList();
}
list.push_back(value);
}
}
return QVariant(list);
}
* parseString
*/
QVariant Json::parseString(const QString &json, int &index, bool &success)
{
QString s;
QChar c;
Json::eatWhitespace(json, index);
c = json[index++];
bool complete = false;
while(!complete)
{
if(index == json.size())
{
break;
}
c = json[index++];
if(c == '\"')
{
complete = true;
break;
}
else if(c == '\\')
{
if(index == json.size())
{
break;
}
c = json[index++];
if(c == '\"')
{
s.append('\"');
}
else if(c == '\\')
{
s.append('\\');
}
else if(c == '/')
{
s.append('/');
}
else if(c == 'b')
{
s.append('\b');
}
else if(c == 'f')
{
s.append('\f');
}
else if(c == 'n')
{
s.append('\n');
}
else if(c == 'r')
{
s.append('\r');
}
else if(c == 't')
{
s.append('\t');
}
else if(c == 'u')
{
int remainingLength = json.size() - index;
if(remainingLength >= 4)
{
QString unicodeStr = json.mid(index, 4);
int symbol = unicodeStr.toInt(0, 16);
s.append(QChar(symbol));
index += 4;
}
else
{
break;
}
}
}
else
{
s.append(c);
}
}
if(!complete)
{
success = false;
return QVariant();
}
return QVariant(s);
}
* parseNumber
*/
QVariant Json::parseNumber(const QString &json, int &index)
{
Json::eatWhitespace(json, index);
int lastIndex = Json::lastIndexOfNumber(json, index);
int charLength = (lastIndex - index) + 1;
QString numberStr;
numberStr = json.mid(index, charLength);
index = lastIndex + 1;
return QVariant(numberStr.toDouble(NULL));
}
* lastIndexOfNumber
*/
int Json::lastIndexOfNumber(const QString &json, int index)
{
int lastIndex;
for(lastIndex = index; lastIndex < json.size(); lastIndex++)
{
if(QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1)
{
break;
}
}
return lastIndex -1;
}
* eatWhitespace
*/
void Json::eatWhitespace(const QString &json, int &index)
{
for(; index < json.size(); index++)
{
if(QString(" \t\n\r").indexOf(json[index]) == -1)
{
break;
}
}
}
* lookAhead
*/
int Json::lookAhead(const QString &json, int index)
{
int saveIndex = index;
return Json::nextToken(json, saveIndex);
}
* nextToken
*/
int Json::nextToken(const QString &json, int &index)
{
Json::eatWhitespace(json, index);
if(index == json.size())
{
return JsonTokenNone;
}
QChar c = json[index];
index++;
#if QT_VERSION >= 0x050000
switch(c.toLatin1())
#else
switch(c.toAscii())
#endif
{
case '{': return JsonTokenCurlyOpen;
case '}': return JsonTokenCurlyClose;
case '[': return JsonTokenSquaredOpen;
case ']': return JsonTokenSquaredClose;
case ',': return JsonTokenComma;
case '"': return JsonTokenString;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '-': return JsonTokenNumber;
case ':': return JsonTokenColon;
}
index--;
int remainingLength = json.size() - index;
if(remainingLength >= 4)
{
if (json[index] == 't' && json[index + 1] == 'r' &&
json[index + 2] == 'u' && json[index + 3] == 'e')
{
index += 4;
return JsonTokenTrue;
}
}
if (remainingLength >= 5)
{
if (json[index] == 'f' && json[index + 1] == 'a' &&
json[index + 2] == 'l' && json[index + 3] == 's' &&
json[index + 4] == 'e')
{
index += 5;
return JsonTokenFalse;
}
}
if (remainingLength >= 4)
{
if (json[index] == 'n' && json[index + 1] == 'u' &&
json[index + 2] == 'l' && json[index + 3] == 'l')
{
index += 4;
return JsonTokenNull;
}
}
return JsonTokenNone;
}
enum JsonBuilderState
{
JBS_Initial,
JBS_Empty,
JBS_Element,
JBS_Key
};
enum JsonBuilderConstants
{
kJB_None = 0,
kJB_MaxNesting = 16,
kJB_Object = 4,
kJB_Array = 8,
};
class JsonBuilderPrivate
{
public:
U_SStream ss;
JsonBuilderState state;
int needComma;
int err;
int nesting;
uint8_t stack[kJB_MaxNesting];
};
JsonBuilder::JsonBuilder(unsigned bufsize)
{
d = SCRATCH_ALLOC(JsonBuilderPrivate*, sizeof(*d));
U_ASSERT(d && "failed to alloc JsonBuilderPrivate");
if (!d)
return;
d->state = JBS_Initial;
d->nesting = 0;
d->err = 0;
d->stack[0] = kJB_None;
d->ss.str = SCRATCH_ALLOC(char*, bufsize);
U_ASSERT(d->ss.str && "failed to alloc JsonBuilderPrivate.ss.str");
if (!d->ss.str)
{
d = nullptr;
return;
}
U_sstream_init(&d->ss, d->ss.str, bufsize);
}
void JsonBuilder::startArray()
{
U_ASSERT(d->nesting < kJB_MaxNesting);
if (d->nesting == kJB_MaxNesting)
{
d->err = 1;
return;
}
if (d->state == JBS_Element)
{
U_sstream_put_str(&d->ss, ",");
}
d->stack[d->nesting] = kJB_Array;
d->nesting++;
U_sstream_put_str(&d->ss, "[");
d->state = JBS_Empty;
}
void JsonBuilder::endArray()
{
U_ASSERT(d->nesting > 0);
if (d->nesting == 0)
{
d->err = 1;
return;
}
d->nesting--;
U_ASSERT(d->stack[d->nesting] == kJB_Array);
if (d->stack[d->nesting] != kJB_Array)
{
d->err = 1;
return;
}
d->stack[d->nesting] = kJB_None;
U_sstream_put_str(&d->ss, "]");
d->state = JBS_Element;
}
void JsonBuilder::startObject()
{
U_ASSERT(d->nesting < kJB_MaxNesting);
if (d->nesting == kJB_MaxNesting)
{
d->err = 1;
return;
}
if (d->state == JBS_Element)
{
U_sstream_put_str(&d->ss, ",");
}
d->stack[d->nesting] = kJB_Object;
d->nesting++;
U_sstream_put_str(&d->ss, "{");
d->state = JBS_Empty;
}
void JsonBuilder::endObject()
{
U_ASSERT(d->nesting > 0);
if (d->nesting == 0)
{
d->err = 1;
return;
}
d->nesting--;
U_ASSERT(d->stack[d->nesting] == kJB_Object);
if (d->stack[d->nesting] != kJB_Object)
{
d->err = 1;
return;
}
d->stack[d->nesting] = kJB_None;
U_sstream_put_str(&d->ss, "}");
d->state = JBS_Element;
}
void JsonBuilder::addKey(const char *key)
{
if (d->nesting == 0)
{
d->err = 1;
return;
}
bool isObject = d->stack[d->nesting - 1] == kJB_Object;
if (!isObject)
{
d->err = 1;
return;
}
if (d->state == JBS_Empty || d->state == JBS_Element)
{
if (d->state == JBS_Element)
{
U_sstream_put_str(&d->ss, ",");
}
U_sstream_put_str(&d->ss, "\"");
U_sstream_put_str(&d->ss, key);
U_sstream_put_str(&d->ss, "\"");
U_sstream_put_str(&d->ss, ":");
d->state = JBS_Key;
}
else
{
d->err = 1;
return;
}
}
void JsonBuilder::addNumber(double num)
{
if (d->nesting == 0)
{
d->err = 1;
return;
}
bool isObject = d->stack[d->nesting - 1] == kJB_Object;
if (isObject)
{
if (d->state != JBS_Key)
{
d->err = 1;
return;
}
}
else if (d->state == JBS_Element)
{
U_sstream_put_str(&d->ss, ",");
}
U_sstream_put_double(&d->ss, num, 6);
d->state = JBS_Element;
}
void JsonBuilder::addString(const char *str)
{
if (d->nesting == 0)
{
d->err = 1;
return;
}
bool isObject = d->stack[d->nesting - 1] == kJB_Object;
if (isObject)
{
if (d->state != JBS_Key)
{
d->err = 1;
return;
}
}
else if (d->state == JBS_Element)
{
U_sstream_put_str(&d->ss, ",");
}
U_sstream_put_str(&d->ss, "\"");
U_sstream_put_str(&d->ss, str);
U_sstream_put_str(&d->ss, "\"");
d->state = JBS_Element;
}