diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2011-12-06 15:14:26 +0100 |
---|---|---|
committer | Hicks James <jamey.hicks@nokia.com> | 2011-12-08 22:57:47 +0100 |
commit | 1207e28982d4da6c75a70ef1da14090e32d1053c (patch) | |
tree | 7198dc1fad620594e26f32596b3e104f213403d4 | |
parent | ccaad46ec8852d65fcff4d90bd2f4de90b97f403 (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.cpp | 7 | ||||
-rw-r--r-- | src/qjsondocument.h | 1 | ||||
-rw-r--r-- | src/qjsonglobal.h | 1 | ||||
-rw-r--r-- | src/qjsonobject.h | 1 | ||||
-rw-r--r-- | src/qjsonparser.cpp | 673 | ||||
-rw-r--r-- | src/qjsonparser_p.h | 44 | ||||
-rw-r--r-- | src/src.pro | 6 | ||||
-rw-r--r-- | tests/auto/tst_qtjson.cpp | 225 |
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) |