summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2011-12-06 15:14:26 +0100
committerHicks James <jamey.hicks@nokia.com>2011-12-08 22:57:47 +0100
commit1207e28982d4da6c75a70ef1da14090e32d1053c (patch)
tree7198dc1fad620594e26f32596b3e104f213403d4
parentccaad46ec8852d65fcff4d90bd2f4de90b97f403 (diff)
Add a Json parser to the code base
The parser is a hand-written recursive parser and should be very fast. Some performance numbers comparing to other parsers are still missing, but it should beat most of them. Change-Id: I7b7c96e49dbdf439eb03519a19556be5d817353f Reviewed-by: Hicks James <jamey.hicks@nokia.com>
-rw-r--r--src/qjsondocument.cpp7
-rw-r--r--src/qjsondocument.h1
-rw-r--r--src/qjsonglobal.h1
-rw-r--r--src/qjsonobject.h1
-rw-r--r--src/qjsonparser.cpp673
-rw-r--r--src/qjsonparser_p.h44
-rw-r--r--src/src.pro6
-rw-r--r--tests/auto/tst_qtjson.cpp225
8 files changed, 956 insertions, 2 deletions
diff --git a/src/qjsondocument.cpp b/src/qjsondocument.cpp
index 23aef2a..64c6f0f 100644
--- a/src/qjsondocument.cpp
+++ b/src/qjsondocument.cpp
@@ -4,6 +4,7 @@
#include <qjsonarray.h>
#include <qjson_p.h>
#include <qjsonwriter_p.h>
+#include <qjsonparser_p.h>
#include <qstringlist.h>
using namespace QtJson;
@@ -111,6 +112,12 @@ QByteArray JsonDocument::toJson() const
return json;
}
+JsonDocument JsonDocument::fromJson(const QByteArray &json)
+{
+ QJsonParser parser(json.constData(), json.length());
+ return parser.parse();
+}
+
bool JsonDocument::isEmpty() const
{
if (!d)
diff --git a/src/qjsondocument.h b/src/qjsondocument.h
index deb783f..e8b75f6 100644
--- a/src/qjsondocument.h
+++ b/src/qjsondocument.h
@@ -22,6 +22,7 @@ public:
QVariant toVariant() const;
QByteArray toJson() const;
+ static JsonDocument fromJson(const QByteArray &json);
bool isEmpty() const;
diff --git a/src/qjsonglobal.h b/src/qjsonglobal.h
index 970a8f2..25bbade 100644
--- a/src/qjsonglobal.h
+++ b/src/qjsonglobal.h
@@ -20,6 +20,7 @@ namespace QtJson
class JsonObject;
class JsonArray;
class JsonDocument;
+ class QJsonParser;
enum ValueType {
NullValue = 0x0,
diff --git a/src/qjsonobject.h b/src/qjsonobject.h
index 32e92c5..138adec 100644
--- a/src/qjsonobject.h
+++ b/src/qjsonobject.h
@@ -47,6 +47,7 @@ private:
friend class Data;
friend class JsonValue;
friend class JsonDocument;
+ friend class QJsonParser;
JsonObject(Data *data, Object *object);
Data *d;
diff --git a/src/qjsonparser.cpp b/src/qjsonparser.cpp
new file mode 100644
index 0000000..e2bf661
--- /dev/null
+++ b/src/qjsonparser.cpp
@@ -0,0 +1,673 @@
+#include <qjsonparser_p.h>
+#include <qjson_p.h>
+#include <qvarlengtharray.h>
+#include <qdebug.h>
+
+//#define PARSER_DEBUG
+#ifdef PARSER_DEBUG
+static int indent = 0;
+#define BEGIN qDebug() << QByteArray(4*indent++, ' ').constData() << "pos=" << current
+#define END --indent
+#define DEBUG qDebug() << QByteArray(4*indent, ' ').constData()
+#else
+#define BEGIN if (1) ; else qDebug()
+#define END do {} while (0)
+#define DEBUG if (1) ; else qDebug()
+#endif
+
+
+using namespace QtJson;
+
+QJsonParser::QJsonParser(const char *json, int length)
+ : json(json), data(0), dataLength(0), current(0)
+{
+ end = json + length;
+}
+
+
+
+/*
+
+begin-array = ws %x5B ws ; [ left square bracket
+
+begin-object = ws %x7B ws ; { left curly bracket
+
+end-array = ws %x5D ws ; ] right square bracket
+
+end-object = ws %x7D ws ; } right curly bracket
+
+name-separator = ws %x3A ws ; : colon
+
+value-separator = ws %x2C ws ; , comma
+
+Insignificant whitespace is allowed before or after any of the six
+structural characters.
+
+ws = *(
+ %x20 / ; Space
+ %x09 / ; Horizontal tab
+ %x0A / ; Line feed or New line
+ %x0D ; Carriage return
+ )
+
+*/
+
+enum {
+ Space = 0x20,
+ Tab = 0x09,
+ LineFeed = 0x0a,
+ Return = 0x0d,
+ BeginArray = 0x5b,
+ BeginObject = 0x7b,
+ EndArray = 0x5d,
+ EndObject = 0x7d,
+ NameSeparator = 0x3a,
+ ValueSeparator = 0x2c,
+ Quote = 0x22
+};
+
+
+
+bool QJsonParser::eatSpace()
+{
+ while (json < end) {
+ if (*json > Space)
+ break;
+ if (*json != Space &&
+ *json != Tab &&
+ *json != LineFeed &&
+ *json != Return)
+ break;
+ ++json;
+ }
+ return (json < end);
+}
+
+char QJsonParser::nextToken()
+{
+ if (!eatSpace())
+ return 0;
+ char token = *json++;
+ switch (token) {
+ case BeginArray:
+ case BeginObject:
+ case NameSeparator:
+ case ValueSeparator:
+ if (!eatSpace())
+ return 0;
+ case EndArray:
+ case EndObject:
+ eatSpace();
+ case Quote:
+ break;
+ default:
+ token = 0;
+ break;
+ }
+ return token;
+}
+
+/*
+ JSON-text = object / array
+*/
+QtJson::JsonDocument QJsonParser::parse()
+{
+#ifdef PARSER_DEBUG
+ indent = 0;
+ qDebug() << ">>>>> parser begin";
+#endif
+ // allocate some space
+ dataLength = sizeof(Header);//qMin(end - json, 256);
+ data = (char *)malloc(dataLength);
+ current = sizeof(Header);
+
+ char token = nextToken();
+ DEBUG << token;
+ if (token == BeginArray) {
+ if (!parseArray())
+ goto error;
+ } else if (token == BeginObject) {
+ if (!parseObject())
+ goto error;
+ } else {
+ goto error;
+ }
+
+ END;
+ {
+ // fill in Header data
+ Header *h = (Header *)data;
+ h->size = current;
+ h->tag = QBJS_Tag;
+ h->type = (token == BeginArray ? ArrayValue : ObjectValue);
+ h->unused = 0;
+
+ Data *d = new Data(data, current);
+ return JsonDocument(d);
+ }
+
+error:
+#ifdef PARSER_DEBUG
+ qDebug() << ">>>>> parser error";
+#endif
+ free(data);
+ return JsonDocument();
+}
+
+/*
+ object = begin-object [ member *( value-separator member ) ]
+ end-object
+*/
+
+bool QJsonParser::parseObject()
+{
+ int objectOffset = reserveSpace(sizeof(Object));
+ BEGIN << "parseObject pos=" << objectOffset << current << json;
+
+ QVarLengthArray<uint> offsets;
+
+ char token = nextToken();
+ while (token == Quote) {
+ int off = current - objectOffset;
+ if (!parseMember(objectOffset))
+ return false;
+ offsets.append(off);
+ token = nextToken();
+ if (token != ValueSeparator)
+ break;
+ token = nextToken();
+ }
+
+ DEBUG << "end token=" << token;
+ if (token != EndObject)
+ return false;
+
+ DEBUG << "numEntries" << offsets.size();
+ int table = objectOffset;
+ // finalize the object
+ if (offsets.size()) {
+ int tableSize = offsets.size()*sizeof(uint);
+ table = reserveSpace(tableSize);
+ memcpy(data + table, offsets.constData(), tableSize);
+ }
+
+ Object *o = (Object *)(data + objectOffset);
+ o->tableOffset = table - objectOffset;
+ o->size = current - objectOffset;
+ o->length = offsets.size();
+
+ DEBUG << "current=" << current;
+ END;
+ return true;
+}
+
+/*
+ member = string name-separator value
+*/
+bool QJsonParser::parseMember(int baseOffset)
+{
+ int entryOffset = reserveSpace(sizeof(Entry));
+ BEGIN << "parseMember pos=" << entryOffset;
+
+ bool latin1;
+ if (!parseString(&latin1))
+ return false;
+ char token = nextToken();
+ if (token != NameSeparator)
+ return false;
+ Value val;
+ if (!parseValue(&val, baseOffset))
+ return false;
+
+ // finalize the entry
+ Entry *e = (Entry *)(data + entryOffset);
+ e->value = val;
+ e->value.latinKey = latin1;
+
+ END;
+ return true;
+}
+
+/*
+ array = begin-array [ value *( value-separator value ) ] end-array
+*/
+bool QJsonParser::parseArray()
+{
+ BEGIN << "parseArray";
+ int arrayOffset = reserveSpace(sizeof(Array));
+
+ QVarLengthArray<Value> values;
+
+ if (!eatSpace())
+ return false;
+ if (*json == EndArray) {
+ nextToken();
+ } else {
+ while (1) {
+ Value val;
+ if (!parseValue(&val, arrayOffset))
+ return false;
+ values.append(val);
+ char token = nextToken();
+ if (token == EndArray)
+ break;
+ else if (token != ValueSeparator)
+ return false;
+ }
+ }
+
+ DEBUG << "size =" << values.size();
+ int table = arrayOffset;
+ // finalize the object
+ if (values.size()) {
+ int tableSize = values.size()*sizeof(Value);
+ table = reserveSpace(tableSize);
+ memcpy(data + table, values.constData(), tableSize);
+ }
+
+ Array *a = (Array *)(data + arrayOffset);
+ a->tableOffset = table - arrayOffset;
+ a->size = current - arrayOffset;
+ a->length = values.size();
+
+ DEBUG << "current=" << current;
+ END;
+ return true;
+}
+
+/*
+value = false / null / true / object / array / number / string
+
+*/
+
+bool QJsonParser::parseValue(Value *val, int baseOffset)
+{
+ BEGIN << "parse Value" << json;
+ val->int_val = 0;
+ val->unused = 0;
+
+ switch (*json++) {
+ case 'n':
+ if (end - json < 4)
+ return false;
+ if (*json++ == 'u' &&
+ *json++ == 'l' &&
+ *json++ == 'l') {
+ val->type = NullValue;
+ DEBUG << "value: null";
+ END;
+ return true;
+ }
+ return false;
+ case 't':
+ if (end - json < 4)
+ return false;
+ if (*json++ == 'r' &&
+ *json++ == 'u' &&
+ *json++ == 'e') {
+ val->type = BooleanValue;
+ val->val = true;
+ DEBUG << "value: true";
+ END;
+ return true;
+ }
+ return false;
+ case 'f':
+ if (end - json < 5)
+ return false;
+ if (*json++ == 'a' &&
+ *json++ == 'l' &&
+ *json++ == 's' &&
+ *json++ == 'e') {
+ val->type = BooleanValue;
+ val->val = false;
+ DEBUG << "value: false";
+ END;
+ return true;
+ }
+ return false;
+ case Quote: {
+ val->type = StringValue;
+ val->val = current - baseOffset;
+ bool latin1;
+ if (!parseString(&latin1))
+ return false;
+ val->latinOrIntValue = latin1;
+ DEBUG << "value: string";
+ END;
+ return true;
+ }
+ case BeginArray:
+ val->type = ArrayValue;
+ val->val = current - baseOffset;
+ if (!parseArray())
+ return false;
+ DEBUG << "value: array";
+ END;
+ return true;
+ case BeginObject:
+ val->type = ObjectValue;
+ val->val = current - baseOffset;
+ if (!parseObject())
+ return false;
+ DEBUG << "value: object";
+ END;
+ return true;
+ default:
+ --json;
+ if (!parseNumber(val, baseOffset))
+ return false;
+ DEBUG << "value: number";
+ END;
+ }
+
+ return true;
+}
+
+
+
+
+
+/*
+ number = [ minus ] int [ frac ] [ exp ]
+ decimal-point = %x2E ; .
+ digit1-9 = %x31-39 ; 1-9
+ e = %x65 / %x45 ; e E
+ exp = e [ minus / plus ] 1*DIGIT
+ frac = decimal-point 1*DIGIT
+ int = zero / ( digit1-9 *DIGIT )
+ minus = %x2D ; -
+ plus = %x2B ; +
+ zero = %x30 ; 0
+
+*/
+
+bool QJsonParser::parseNumber(Value *val, int baseOffset)
+{
+ BEGIN << "parseNumber" << json;
+ val->type = NumberValue;
+
+ const char *start = json;
+ bool isInt = true;
+
+ // minus
+ if (json < end && *json == '-')
+ ++json;
+
+ // int = zero / ( digit1-9 *DIGIT )
+ if (json < end && *json == '0') {
+ ++json;
+ } else {
+ while (json < end && *json >= '0' && *json <= '9')
+ ++json;
+ }
+
+ // frac = decimal-point 1*DIGIT
+ if (json < end && *json == '.') {
+ isInt = false;
+ ++json;
+ while (json < end && *json >= '0' && *json <= '9')
+ ++json;
+ }
+
+ // exp = e [ minus / plus ] 1*DIGIT
+ if (json < end && (*json == 'e' || *json == 'E')) {
+ isInt = false;
+ ++json;
+ if (json < end && (*json == '-' || *json == '+'))
+ ++json;
+ while (json < end && *json >= '0' && *json <= '9')
+ ++json;
+ }
+
+ if (json >= end)
+ return false;
+
+ QByteArray number(start, json - start);
+ DEBUG << "numberstring" << number;
+
+ if (isInt) {
+ bool ok;
+ int n = number.toInt(&ok);
+ if (ok && n < (1<<25) && n > -(1<<25)) {
+ val->int_val = n;
+ val->latinOrIntValue = true;
+ END;
+ return true;
+ }
+ }
+
+ bool ok;
+ union {
+ char raw[sizeof(double)];
+ double d;
+ };
+ d = number.toDouble(&ok);
+
+ if (!ok)
+ return false;
+
+ int pos = reserveSpace(sizeof(double));
+ memcpy(data + pos, raw, sizeof(double));
+ val->val = pos - baseOffset;
+ val->latinOrIntValue = false;
+
+ END;
+ return true;
+}
+
+/*
+
+ string = quotation-mark *char quotation-mark
+
+ char = unescaped /
+ escape (
+ %x22 / ; " quotation mark U+0022
+ %x5C / ; \ reverse solidus U+005C
+ %x2F / ; / solidus U+002F
+ %x62 / ; b backspace U+0008
+ %x66 / ; f form feed U+000C
+ %x6E / ; n line feed U+000A
+ %x72 / ; r carriage return U+000D
+ %x74 / ; t tab U+0009
+ %x75 4HEXDIG ) ; uXXXX U+XXXX
+
+ escape = %x5C ; \
+
+ quotation-mark = %x22 ; "
+
+ unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
+ */
+static inline bool addHexDigit(char digit, uint *result)
+{
+ *result <<= 4;
+ if (digit >= '0' && digit <= '9')
+ *result |= (digit - '0');
+ else if (digit >= 'a' && digit <= 'f')
+ *result |= (digit - 'a');
+ else if (digit >= 'A' && digit <= 'F')
+ *result |= (digit - 'A');
+ else
+ return false;
+ return true;
+}
+
+static inline bool scanEscapeSequence(const char *&json, const char *end, uint *ch)
+{
+ ++json;
+ if (json >= end)
+ return false;
+
+ DEBUG << "scan escape" << (char)*json;
+ switch (*json++) {
+ case '"':
+ *ch = '"'; break;
+ case '\\':
+ *ch = '\\'; break;
+ case '/':
+ *ch = '/'; break;
+ case 'b':
+ *ch = 0x8; break;
+ case 'f':
+ *ch = 0xc; break;
+ case 'n':
+ *ch = 0xa; break;
+ case 'r':
+ *ch = 0xd; break;
+ case 't':
+ *ch = 0x9; break;
+ case 'u': {
+ *ch = 0;
+ if (json > end - 4)
+ return false;
+ for (int i = 0; i < 4; ++i) {
+ if (!addHexDigit(*json, ch))
+ return false;
+ ++json;
+ }
+ return true;
+ }
+ default:
+ return false;
+ }
+ return true;
+}
+
+static inline bool isUnicodeNonCharacter(uint ucs4)
+{
+ // Unicode has a couple of "non-characters" that one can use internally,
+ // but are not allowed to be used for text interchange.
+ //
+ // Those are the last two entries each Unicode Plane (U+FFFE, U+FFFF,
+ // U+1FFFE, U+1FFFF, etc.) as well as the entries between U+FDD0 and
+ // U+FDEF (inclusive)
+
+ return (ucs4 & 0xfffe) == 0xfffe
+ || (ucs4 - 0xfdd0U) < 16;
+}
+
+static inline bool scanUtf8Char(const char *&json, const char *end, uint *result)
+{
+ int need;
+ uint min_uc;
+ uint uc;
+ uchar ch = *json++;
+ if (ch < 128) {
+ *result = ch;
+ return true;
+ } else if ((ch & 0xe0) == 0xc0) {
+ uc = ch & 0x1f;
+ need = 1;
+ min_uc = 0x80;
+ } else if ((ch & 0xf0) == 0xe0) {
+ uc = ch & 0x0f;
+ need = 2;
+ min_uc = 0x800;
+ } else if ((ch&0xf8) == 0xf0) {
+ uc = ch & 0x07;
+ need = 3;
+ min_uc = 0x10000;
+ } else {
+ return false;
+ }
+
+ if (json >= end - need)
+ return false;
+
+ for (int i = 0; i < need; ++i) {
+ ch = *json++;
+ if ((ch&0xc0) != 0x80)
+ return false;
+ uc = (uc << 6) | (ch & 0x3f);
+ }
+
+ if (isUnicodeNonCharacter(uc) || uc >= 0x110000 ||
+ (uc < min_uc) || (uc >= 0xd800 && uc <= 0xdfff))
+ return false;
+
+ *result = uc;
+ return true;
+}
+
+bool QJsonParser::parseString(bool *latin1)
+{
+ *latin1 = true;
+
+ const char *start = json;
+ int outStart = current;
+
+ // try to write out a latin1 string
+
+ int stringPos = reserveSpace(2);
+ BEGIN << "parse string stringPos=" << stringPos << json;
+ while (json < end) {
+ uint ch = 0;
+ if (*json == '"')
+ break;
+ else if (*json == '\\') {
+ if (!scanEscapeSequence(json, end, &ch))
+ return false;
+ } else {
+ if (!scanUtf8Char(json, end, &ch))
+ return false;
+ }
+ if (ch > 0xff) {
+ *latin1 = false;
+ break;
+ }
+ int pos = reserveSpace(1);
+ DEBUG << " " << ch << (char)ch;
+ data[pos] = (uchar)ch;
+ }
+ ++json;
+ DEBUG << "end of string";
+ if (json >= end)
+ return false;
+
+ // no unicode string, we are done
+ if (*latin1) {
+ // write string length
+ *(ushort *)(data + stringPos) = current - outStart - sizeof(ushort);
+ reserveSpace((4 - current) & 3);
+ END;
+ return true;
+ }
+
+ *latin1 = false;
+ DEBUG << "not latin";
+
+ json = start;
+ current = outStart + sizeof(int);
+
+ while (json < end) {
+ uint ch = 0;
+ if (*json == '"')
+ break;
+ else if (*json == '\\') {
+ if (!scanEscapeSequence(json, end, &ch))
+ return false;
+ } else {
+ if (!scanUtf8Char(json, end, &ch))
+ return false;
+ }
+ if (ch > 0xffff) {
+ int pos = reserveSpace(4);
+ *(ushort *)(data + pos) = QChar::highSurrogate(ch);
+ *(ushort *)(data + pos + 2) = QChar::lowSurrogate(ch);
+ } else {
+ int pos = reserveSpace(2);
+ *(ushort *)(data + pos) = (ushort)ch;
+ }
+ }
+ ++json;
+
+ if (json >= end)
+ return false;
+
+ // write string length
+ *(int *)(data + stringPos) = (current - outStart - sizeof(int))/2;
+ reserveSpace((4 - current) & 3);
+ END;
+ return true;
+}
+
diff --git a/src/qjsonparser_p.h b/src/qjsonparser_p.h
new file mode 100644
index 0000000..2770d15
--- /dev/null
+++ b/src/qjsonparser_p.h
@@ -0,0 +1,44 @@
+#ifndef QJSONPARSER_P_H
+#define QJSONPARSER_P_H
+
+#include <qjsondocument.h>
+
+namespace QtJson {
+
+class QJsonParser
+{
+public:
+ QJsonParser(const char *json, int length);
+
+ QtJson::JsonDocument parse();
+
+private:
+ inline bool eatSpace();
+ inline char nextToken();
+
+ bool parseObject();
+ bool parseArray();
+ bool parseMember(int baseOffset);
+ bool parseString(bool *latin1);
+ bool parseValue(Value *val, int baseOffset);
+ bool parseNumber(Value *val, int baseOffset);
+ const char *json;
+ const char *end;
+
+ char *data;
+ int dataLength;
+ int current;
+
+ inline int reserveSpace(int space) {
+ if (current + space >= dataLength) {
+ dataLength = 2*dataLength + space;
+ data = (char *)realloc(data, dataLength);
+ }
+ int pos = current;
+ current += space;
+ return pos;
+ }
+};
+
+}
+#endif
diff --git a/src/src.pro b/src/src.pro
index ed34e65..e921b4a 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -12,14 +12,16 @@ HEADERS += \
qjsonglobal.h \
qjsonvalue.h \
qjsonarray.h \
- qjsonwriter_p.h
+ qjsonwriter_p.h \
+ qjsonparser_p.h
SOURCES += \
qjsondocument.cpp \
qjsonobject.cpp \
qjsonarray.cpp \
qjsonvalue.cpp \
- qjsonwriter.cpp
+ qjsonwriter.cpp \
+ qjsonparser.cpp
diff --git a/tests/auto/tst_qtjson.cpp b/tests/auto/tst_qtjson.cpp
index f13dbee..215fed9 100644
--- a/tests/auto/tst_qtjson.cpp
+++ b/tests/auto/tst_qtjson.cpp
@@ -81,6 +81,9 @@ private Q_SLOTS:
void toVariantMap();
void toJson();
+ void fromJson();
+ void parseNumbers();
+ void parseStrings();
};
TestQtJson::TestQtJson(QObject *parent) : QObject(parent)
@@ -682,6 +685,228 @@ void TestQtJson::toJson()
QCOMPARE(json, expected);
}
+void TestQtJson::fromJson()
+{
+ {
+ QByteArray json = "[\n true\n]\n";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 1);
+ QCOMPARE(array.at(0).type(), BooleanValue);
+ QCOMPARE(array.at(0).toBool(), true);
+ QCOMPARE(doc.toJson(), json);
+ }
+ {
+ QByteArray json = "[]";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 0);
+ }
+ {
+ QByteArray json = "{}";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ObjectValue);
+ JsonObject object = doc.object();
+ QCOMPARE(object.numKeys(), 0);
+ }
+ {
+ QByteArray json = "{\n \"Key\": true\n}\n";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ObjectValue);
+ JsonObject object = doc.object();
+ QCOMPARE(object.numKeys(), 1);
+ QCOMPARE(object.value("Key"), JsonValue(true));
+ QCOMPARE(doc.toJson(), json);
+ }
+ {
+ QByteArray json = "[ null, true, false, \"Foo\", 1, [], {} ]";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 7);
+ QCOMPARE(array.at(0).type(), NullValue);
+ QCOMPARE(array.at(1).type(), BooleanValue);
+ QCOMPARE(array.at(1).toBool(), true);
+ QCOMPARE(array.at(2).type(), BooleanValue);
+ QCOMPARE(array.at(2).toBool(), false);
+ QCOMPARE(array.at(3).type(), StringValue);
+ QCOMPARE(array.at(3).toString(), QLatin1String("Foo"));
+ QCOMPARE(array.at(4).type(), NumberValue);
+ QCOMPARE(array.at(4).toInt(), 1);
+ QCOMPARE(array.at(5).type(), ArrayValue);
+ QCOMPARE(array.at(5).toArray().size(), 0);
+ QCOMPARE(array.at(6).type(), ObjectValue);
+ QCOMPARE(array.at(6).toObject().numKeys(), 0);
+ }
+ {
+ QByteArray json = "{ \"0\": null, \"1\": true, \"2\": false, \"3\": \"Foo\", \"4\": 1, \"5\": [], \"6\": {} }";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ObjectValue);
+ JsonObject object = doc.object();
+ QCOMPARE(object.numKeys(), 7);
+ QCOMPARE(object.value("0").type(), NullValue);
+ QCOMPARE(object.value("1").type(), BooleanValue);
+ QCOMPARE(object.value("1").toBool(), true);
+ QCOMPARE(object.value("2").type(), BooleanValue);
+ QCOMPARE(object.value("2").toBool(), false);
+ QCOMPARE(object.value("3").type(), StringValue);
+ QCOMPARE(object.value("3").toString(), QLatin1String("Foo"));
+ QCOMPARE(object.value("4").type(), NumberValue);
+ QCOMPARE(object.value("4").toInt(), 1);
+ QCOMPARE(object.value("5").type(), ArrayValue);
+ QCOMPARE(object.value("5").toArray().size(), 0);
+ QCOMPARE(object.value("6").type(), ObjectValue);
+ QCOMPARE(object.value("6").toObject().numKeys(), 0);
+ }
+}
+
+void TestQtJson::parseNumbers()
+{
+ {
+ // test number parsing
+ struct Numbers {
+ const char *str;
+ int n;
+ };
+ Numbers numbers [] = {
+ { "0", 0 },
+ { "1", 1 },
+ { "10", 10 },
+ { "-1", -1 },
+ { "100000", 100000 },
+ { "-999", -999 }
+ };
+ int size = sizeof(numbers)/sizeof(Numbers);
+ for (int i = 0; i < size; ++i) {
+ QByteArray json = "[ ";
+ json += numbers[i].str;
+ json += " ]";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 1);
+ JsonValue val = array.at(0);
+ QCOMPARE(val.type(), NumberValue);
+ QCOMPARE(val.toInt(), numbers[i].n);
+ }
+ }
+ {
+ // test number parsing
+ struct Numbers {
+ const char *str;
+ double n;
+ };
+ Numbers numbers [] = {
+ { "0", 0 },
+ { "1", 1 },
+ { "10", 10 },
+ { "-1", -1 },
+ { "100000", 100000 },
+ { "-999", -999 },
+ { "1.1", 1.1 },
+ { "1e10", 1e10 },
+ { "-1.1", -1.1 },
+ { "-1e10", -1e10 },
+ { "-1E10", -1e10 },
+ { "1.1e10", 1.1e10 },
+ { "1.1e308", 1.1e308 },
+ { "-1.1e308", -1.1e308 },
+ { "1.1e-308", 1.1e-308 },
+ { "-1.1e-308", -1.1e-308 },
+ { "1.1e+308", 1.1e+308 },
+ { "-1.1e+308", -1.1e+308 },
+ { "1.e+308", 1.e+308 },
+ { "-1.e+308", -1.e+308 }
+ };
+ int size = sizeof(numbers)/sizeof(Numbers);
+ for (int i = 0; i < size; ++i) {
+ QByteArray json = "[ ";
+ json += numbers[i].str;
+ json += " ]";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 1);
+ JsonValue val = array.at(0);
+ QCOMPARE(val.type(), NumberValue);
+ QCOMPARE(val.toNumber(), numbers[i].n);
+ }
+ }
+}
+
+void TestQtJson::parseStrings()
+{
+ const char *strings [] =
+ {
+ "Foo",
+ "abc\\\"abc",
+ "abc\\\\abc",
+ "abc\\babc",
+ "abc\\fabc",
+ "abc\\nabc",
+ "abc\\rabc",
+ "abc\\tabc",
+ "abc\\u0019abc",
+ "abcЂabc",
+ };
+ int size = sizeof(strings)/sizeof(const char *);
+
+ for (int i = 0; i < size; ++i) {
+ QByteArray json = "[\n \"";
+ json += strings[i];
+ json += "\"\n]\n";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 1);
+ JsonValue val = array.at(0);
+ QCOMPARE(val.type(), StringValue);
+
+ QCOMPARE(doc.toJson(), json);
+ }
+
+ struct Pairs {
+ const char *in;
+ const char *out;
+ };
+ Pairs pairs [] = {
+ { "abc\\/abc", "abc/abc" },
+ { "abc\\u0402abc", "abcЂabc" },
+ { "abc\\u0065abc", "abceabc" }
+ };
+ size = sizeof(pairs)/sizeof(Pairs);
+
+ for (int i = 0; i < size; ++i) {
+ QByteArray json = "[\n \"";
+ json += pairs[i].in;
+ json += "\"\n]\n";
+ QByteArray out = "[\n \"";
+ out += pairs[i].out;
+ out += "\"\n]\n";
+ JsonDocument doc = JsonDocument::fromJson(json);
+ QVERIFY(!doc.isEmpty());
+ QCOMPARE(doc.type(), ArrayValue);
+ JsonArray array = doc.array();
+ QCOMPARE(array.size(), 1);
+ JsonValue val = array.at(0);
+ QCOMPARE(val.type(), StringValue);
+
+ QCOMPARE(doc.toJson(), out);
+ }
+
+}
+
QTEST_MAIN(TestQtJson)