diff options
author | hjk <hjk@theqtcompany.com> | 2015-11-05 08:38:41 +0100 |
---|---|---|
committer | hjk <hjk@theqtcompany.com> | 2015-11-09 08:25:11 +0000 |
commit | b8a8fe34a2a49ba6df23de98837c5ceb665f48c9 (patch) | |
tree | 91a66a79586d9a2115bcae886aaf282da3a84493 | |
parent | 626086e9d1f67f44aab083bea077aa20ebddbbf1 (diff) |
Introduce a Qt-free JSON implementation
This is essentially QJson with Qt replaced by std:: features.
This is useful to have in circumstances where a Qt dependency
is undesirable, e.g. for the Qt Creator debugger protocol
implementation in our CDB extension.
Change-Id: Iec79c6b23b1e717ce1b6f4d38755287d1f479c13
Reviewed-by: David Schulz <david.schulz@theqtcompany.com>
-rw-r--r-- | src/shared/json/README.md | 3 | ||||
-rw-r--r-- | src/shared/json/json.cpp | 4928 | ||||
-rw-r--r-- | src/shared/json/json.h | 582 | ||||
-rw-r--r-- | src/shared/json/json.pri | 2 | ||||
-rw-r--r-- | src/shared/json/json.qbs | 15 | ||||
-rw-r--r-- | src/src.qbs | 1 | ||||
-rw-r--r-- | tests/auto/auto.pro | 1 | ||||
-rw-r--r-- | tests/auto/auto.qbs | 1 | ||||
-rw-r--r-- | tests/auto/json/bom.json | 3 | ||||
-rw-r--r-- | tests/auto/json/json.pro | 13 | ||||
-rw-r--r-- | tests/auto/json/json.qbs | 22 | ||||
-rw-r--r-- | tests/auto/json/test.bjson | bin | 0 -> 39684 bytes | |||
-rw-r--r-- | tests/auto/json/test.json | 66 | ||||
-rw-r--r-- | tests/auto/json/test2.json | 1 | ||||
-rw-r--r-- | tests/auto/json/test3.json | 15 | ||||
-rw-r--r-- | tests/auto/json/tst_json.cpp | 2524 | ||||
-rw-r--r-- | tests/benchmarks/json/json.pro | 14 | ||||
-rw-r--r-- | tests/benchmarks/json/numbers.json | 19 | ||||
-rw-r--r-- | tests/benchmarks/json/test.json | 66 | ||||
-rw-r--r-- | tests/benchmarks/json/tst_bench_json.cpp | 269 |
20 files changed, 8545 insertions, 0 deletions
diff --git a/src/shared/json/README.md b/src/shared/json/README.md new file mode 100644 index 00000000000..e641d8e5fe2 --- /dev/null +++ b/src/shared/json/README.md @@ -0,0 +1,3 @@ +This is QJson without Qt, to be used in circumstances +where a Qt dependency is not desirable, such as +qtcreatorcdbex. diff --git a/src/shared/json/json.cpp b/src/shared/json/json.cpp new file mode 100644 index 00000000000..6cb7502848a --- /dev/null +++ b/src/shared/json/json.cpp @@ -0,0 +1,4928 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <algorithm> +#include <atomic> +#include <iostream> +#include <limits> +#include <string> +#include <utility> +#include <vector> + +#include <limits.h> +#include <string.h> + +#include "json.h" + + +//#define PARSER_DEBUG +#ifdef PARSER_DEBUG +static int indent = 0; +#define BEGIN std::cerr << std::string(4*indent++, ' ').data() << " pos=" << current +#define END --indent +#define DEBUG std::cerr << std::string(4*indent, ' ').data() +#else +#define BEGIN if (1) ; else std::cerr +#define END do {} while (0) +#define DEBUG if (1) ; else std::cerr +#endif + +static const int nestingLimit = 1024; + + +namespace Json { +namespace Internal { + +/* + This defines a binary data structure for Json data. The data structure is optimised for fast reading + and minimum allocations. The whole data structure can be mmap'ed and used directly. + + In most cases the binary structure is not as space efficient as a utf8 encoded text representation, but + much faster to access. + + The size requirements are: + + String: 4 bytes header + 2*(string.length()) + + Values: 4 bytes + size of data (size can be 0 for some data) + bool: 0 bytes + double: 8 bytes (0 if integer with less than 27bits) + string: see above + array: size of array + object: size of object + Array: 12 bytes + 4*length + size of Value data + Object: 12 bytes + 8*length + size of Key Strings + size of Value data + + For an example such as + + { // object: 12 + 5*8 = 52 + "firstName": "John", // key 12, value 8 = 20 + "lastName" : "Smith", // key 12, value 8 = 20 + "age" : 25, // key 8, value 0 = 8 + "address" : // key 12, object below = 140 + { // object: 12 + 4*8 + "streetAddress": "21 2nd Street", // key 16, value 16 + "city" : "New York", // key 8, value 12 + "state" : "NY", // key 8, value 4 + "postalCode" : "10021" // key 12, value 8 + }, // object total: 128 + "phoneNumber": // key: 16, value array below = 172 + [ // array: 12 + 2*4 + values below: 156 + { // object 12 + 2*8 + "type" : "home", // key 8, value 8 + "number": "212 555-1234" // key 8, value 16 + }, // object total: 68 + { // object 12 + 2*8 + "type" : "fax", // key 8, value 8 + "number": "646 555-4567" // key 8, value 16 + } // object total: 68 + ] // array total: 156 + } // great total: 412 bytes + + The uncompressed text file used roughly 500 bytes, so in this case we end up using about + the same space as the text representation. + + Other measurements have shown a slightly bigger binary size than a compact text + representation where all possible whitespace was stripped out. +*/ + +class Array; +class Object; +class Value; +class Entry; + +template<int pos, int width> +class qle_bitfield +{ +public: + uint32_t val; + + enum { + mask = ((1u << width) - 1) << pos + }; + + void operator=(uint32_t t) { + uint32_t i = val; + i &= ~mask; + i |= t << pos; + val = i; + } + operator uint32_t() const { + uint32_t t = val; + t &= mask; + t >>= pos; + return t; + } + bool operator!() const { return !operator uint32_t(); } + bool operator==(uint32_t t) { return uint32_t(*this) == t; } + bool operator!=(uint32_t t) { return uint32_t(*this) != t; } + bool operator<(uint32_t t) { return uint32_t(*this) < t; } + bool operator>(uint32_t t) { return uint32_t(*this) > t; } + bool operator<=(uint32_t t) { return uint32_t(*this) <= t; } + bool operator>=(uint32_t t) { return uint32_t(*this) >= t; } + void operator+=(uint32_t i) { *this = (uint32_t(*this) + i); } + void operator-=(uint32_t i) { *this = (uint32_t(*this) - i); } +}; + +template<int pos, int width> +class qle_signedbitfield +{ +public: + uint32_t val; + + enum { + mask = ((1u << width) - 1) << pos + }; + + void operator=(int t) { + uint32_t i = val; + i &= ~mask; + i |= t << pos; + val = i; + } + operator int() const { + uint32_t i = val; + i <<= 32 - width - pos; + int t = (int) i; + t >>= pos; + return t; + } + + bool operator!() const { return !operator int(); } + bool operator==(int t) { return int(*this) == t; } + bool operator!=(int t) { return int(*this) != t; } + bool operator<(int t) { return int(*this) < t; } + bool operator>(int t) { return int(*this) > t; } + bool operator<=(int t) { return int(*this) <= t; } + bool operator>=(int t) { return int(*this) >= t; } + void operator+=(int i) { *this = (int(*this) + i); } + void operator-=(int i) { *this = (int(*this) - i); } +}; + +typedef uint32_t offset; + +// round the size up to the next 4 byte boundary +int alignedSize(int size) { return (size + 3) & ~3; } + +static int qStringSize(const std::string &ba) +{ + int l = 4 + ba.length(); + return alignedSize(l); +} + +// returns INT_MAX if it can't compress it into 28 bits +static int compressedNumber(double d) +{ + // this relies on details of how ieee floats are represented + const int exponent_off = 52; + const uint64_t fraction_mask = 0x000fffffffffffffull; + const uint64_t exponent_mask = 0x7ff0000000000000ull; + + uint64_t val; + memcpy (&val, &d, sizeof(double)); + int exp = (int)((val & exponent_mask) >> exponent_off) - 1023; + if (exp < 0 || exp > 25) + return INT_MAX; + + uint64_t non_int = val & (fraction_mask >> exp); + if (non_int) + return INT_MAX; + + bool neg = (val >> 63); + val &= fraction_mask; + val |= ((uint64_t)1 << 52); + int res = (int)(val >> (52 - exp)); + return neg ? -res : res; +} + +class String +{ +public: + String(const char *data) { d = (Data *)data; } + + struct Data { + int length; + char utf8[1]; + }; + + Data *d; + + void operator=(const std::string &ba) + { + d->length = ba.length(); + memcpy(d->utf8, ba.data(), ba.length()); + } + + bool operator==(const std::string &ba) const { + return toString() == ba; + } + bool operator!=(const std::string &str) const { + return !operator==(str); + } + bool operator>=(const std::string &str) const { + // ### + return toString() >= str; + } + + bool operator==(const String &str) const { + if (d->length != str.d->length) + return false; + return !memcmp(d->utf8, str.d->utf8, d->length); + } + bool operator<(const String &other) const; + bool operator>=(const String &other) const { return !(*this < other); } + + std::string toString() const { + return std::string(d->utf8, d->length); + } + +}; + +bool String::operator<(const String &other) const +{ + int alen = d->length; + int blen = other.d->length; + int l = std::min(alen, blen); + char *a = d->utf8; + char *b = other.d->utf8; + + while (l-- && *a == *b) + a++,b++; + if (l==-1) + return (alen < blen); + return (unsigned char)(*a) < (unsigned char)(*b); +} + +static void copyString(char *dest, const std::string &str) +{ + String string(dest); + string = str; +} + + +/* + Base is the base class for both Object and Array. Both classe work more or less the same way. + The class starts with a header (defined by the struct below), then followed by data (the data for + values in the Array case and Entry's (see below) for objects. + + After the data a table follows (tableOffset points to it) containing Value objects for Arrays, and + offsets from the beginning of the object to Entry's in the case of Object. + + Entry's in the Object's table are lexicographically sorted by key in the table(). This allows the usage + of a binary search over the keys in an Object. + */ +class Base +{ +public: + uint32_t size; + union { + uint32_t _dummy; + qle_bitfield<0, 1> is_object; + qle_bitfield<1, 31> length; + }; + offset tableOffset; + // content follows here + + bool isObject() const { return is_object; } + bool isArray() const { return !isObject(); } + + offset *table() const { return (offset *) (((char *) this) + tableOffset); } + + int reserveSpace(uint32_t dataSize, int posInTable, uint32_t numItems, bool replace); + void removeItems(int pos, int numItems); +}; + +class Object : public Base +{ +public: + Entry *entryAt(int i) const { + return reinterpret_cast<Entry *>(((char *)this) + table()[i]); + } + int indexOf(const std::string &key, bool *exists); + + bool isValid() const; +}; + + +class Value +{ +public: + enum { + MaxSize = (1<<27) - 1 + }; + union { + uint32_t _dummy; + qle_bitfield<0, 3> type; + qle_bitfield<3, 1> intValue; + qle_bitfield<4, 1> _; // Ex-latin1Key + qle_bitfield<5, 27> value; // Used as offset in case of Entry(?) + qle_signedbitfield<5, 27> int_value; + }; + + char *data(const Base *b) const { return ((char *)b) + value; } + int usedStorage(const Base *b) const; + + bool toBoolean() const { return value != 0; } + double toDouble(const Base *b) const; + std::string toString(const Base *b) const; + Base *base(const Base *b) const; + + bool isValid(const Base *b) const; + + static int requiredStorage(JsonValue &v, bool *compressed); + static uint32_t valueToStore(const JsonValue &v, uint32_t offset); + static void copyData(const JsonValue &v, char *dest, bool compressed); +}; + +class Array : public Base +{ +public: + Value at(int i) const { return *(Value *) (table() + i); } + Value &operator[](int i) { return *(Value *) (table() + i); } + + bool isValid() const; +}; + +class Entry { +public: + Value value; + // key + // value data follows key + + int size() const + { + int s = sizeof(Entry); + s += sizeof(uint32_t) + (*(int *) ((const char *)this + sizeof(Entry))); + return alignedSize(s); + } + + int usedStorage(Base *b) const + { + return size() + value.usedStorage(b); + } + + String shallowKey() const + { + return String((const char *)this + sizeof(Entry)); + } + + std::string key() const + { + return shallowKey().toString(); + } + + bool operator==(const std::string &key) const; + bool operator!=(const std::string &key) const { return !operator==(key); } + bool operator>=(const std::string &key) const { return shallowKey() >= key; } + + bool operator==(const Entry &other) const; + bool operator>=(const Entry &other) const; +}; + +bool operator<(const std::string &key, const Entry &e) +{ + return e >= key; +} + + +class Header +{ +public: + uint32_t tag; // 'qbjs' + uint32_t version; // 1 + Base *root() { return (Base *)(this + 1); } +}; + + +double Value::toDouble(const Base *b) const +{ + // assert(type == JsonValue::Double); + if (intValue) + return int_value; + + double d; + memcpy(&d, (const char *)b + value, 8); + return d; +} + +std::string Value::toString(const Base *b) const +{ + String s(data(b)); + return s.toString(); +} + +Base *Value::base(const Base *b) const +{ + // assert(type == JsonValue::Array || type == JsonValue::Object); + return reinterpret_cast<Base *>(data(b)); +} + +class AtomicInt +{ +public: + bool ref() { return ++x != 0; } + bool deref() { return --x != 0; } + int load() { return x.load(std::memory_order_seq_cst); } +private: + std::atomic<int> x { 0 }; +}; + + +class SharedString +{ +public: + AtomicInt ref; + std::string s; +}; + +class Data { +public: + enum Validation { + Unchecked, + Validated, + Invalid + }; + + AtomicInt ref; + int alloc; + union { + char *rawData; + Header *header; + }; + uint32_t compactionCounter : 31; + uint32_t ownsData : 1; + + Data(char *raw, int a) + : alloc(a), rawData(raw), compactionCounter(0), ownsData(true) + { + } + Data(int reserved, JsonValue::Type valueType) + : rawData(0), compactionCounter(0), ownsData(true) + { + // assert(valueType == JsonValue::Array || valueType == JsonValue::Object); + + alloc = sizeof(Header) + sizeof(Base) + reserved + sizeof(offset); + header = (Header *)malloc(alloc); + header->tag = JsonDocument::BinaryFormatTag; + header->version = 1; + Base *b = header->root(); + b->size = sizeof(Base); + b->is_object = (valueType == JsonValue::Object); + b->tableOffset = sizeof(Base); + b->length = 0; + } + ~Data() + { if (ownsData) free(rawData); } + + uint32_t offsetOf(const void *ptr) const { return (uint32_t)(((char *)ptr - rawData)); } + + JsonObject toObject(Object *o) const + { + return JsonObject(const_cast<Data *>(this), o); + } + + JsonArray toArray(Array *a) const + { + return JsonArray(const_cast<Data *>(this), a); + } + + Data *clone(Base *b, int reserve = 0) + { + int size = sizeof(Header) + b->size; + if (b == header->root() && ref.load() == 1 && alloc >= size + reserve) + return this; + + if (reserve) { + if (reserve < 128) + reserve = 128; + size = std::max(size + reserve, size *2); + } + char *raw = (char *)malloc(size); + memcpy(raw + sizeof(Header), b, b->size); + Header *h = (Header *)raw; + h->tag = JsonDocument::BinaryFormatTag; + h->version = 1; + Data *d = new Data(raw, size); + d->compactionCounter = (b == header->root()) ? compactionCounter : 0; + return d; + } + + void compact(); + bool valid() const; + +private: + Data(const Data &); + void operator=(const Data &); +}; + + +void objectToJson(const Object *o, std::string &json, int indent, bool compact = false); +void arrayToJson(const Array *a, std::string &json, int indent, bool compact = false); + +class Parser +{ +public: + Parser(const char *json, int length); + + JsonDocument parse(JsonParseError *error); + + class ParsedObject + { + public: + ParsedObject(Parser *p, int pos) : parser(p), objectPosition(pos) { + offsets.reserve(64); + } + void insert(uint32_t offset); + + Parser *parser; + int objectPosition; + std::vector<uint32_t> offsets; + + Entry *entryAt(int i) const { + return reinterpret_cast<Entry *>(parser->data + objectPosition + offsets[i]); + } + }; + + +private: + void eatBOM(); + bool eatSpace(); + char nextToken(); + + bool parseObject(); + bool parseArray(); + bool parseMember(int baseOffset); + bool parseString(); + bool parseValue(Value *val, int baseOffset); + bool parseNumber(Value *val, int baseOffset); + const char *head; + const char *json; + const char *end; + + char *data; + int dataLength; + int current; + int nestingLevel; + JsonParseError::ParseError lastError; + + int reserveSpace(int space) { + if (current + space >= dataLength) { + dataLength = 2*dataLength + space; + data = (char *)realloc(data, dataLength); + } + int pos = current; + current += space; + return pos; + } +}; + +} // namespace Internal + +using namespace Internal; + +/*! + \class JsonValue + \inmodule QtCore + \ingroup json + \ingroup shared + \reentrant + \since 5.0 + + \brief The JsonValue class encapsulates a value in JSON. + + A value in JSON can be one of 6 basic types: + + JSON is a format to store structured data. It has 6 basic data types: + + \list + \li bool JsonValue::Bool + \li double JsonValue::Double + \li string JsonValue::String + \li array JsonValue::Array + \li object JsonValue::Object + \li null JsonValue::Null + \endlist + + A value can represent any of the above data types. In addition, JsonValue has one special + flag to represent undefined values. This can be queried with isUndefined(). + + The type of the value can be queried with type() or accessors like isBool(), isString(), and so on. + Likewise, the value can be converted to the type stored in it using the toBool(), toString() and so on. + + Values are strictly typed internally and contrary to QVariant will not attempt to do any implicit type + conversions. This implies that converting to a type that is not stored in the value will return a default + constructed return value. + + \section1 JsonValueRef + + JsonValueRef is a helper class for JsonArray and JsonObject. + When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the element in the JsonArray or JsonObject + from which you got the reference. + + The following methods return JsonValueRef: + \list + \li \l {JsonArray}::operator[](int i) + \li \l {JsonObject}::operator[](const QString & key) const + \endlist + + \sa {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! + Creates a JsonValue of type \a type. + + The default is to create a Null value. + */ +JsonValue::JsonValue(Type type) + : ui(0), d(0), t(type) +{ +} + +/*! + \internal + */ +JsonValue::JsonValue(Internal::Data *data, Internal::Base *base, const Internal::Value &v) + : d(0), t((Type)(uint32_t)v.type) +{ + switch (t) { + case Undefined: + case Null: + dbl = 0; + break; + case Bool: + b = v.toBoolean(); + break; + case Double: + dbl = v.toDouble(base); + break; + case String: { + stringData = new Internal::SharedString; + stringData->s = v.toString(base); + stringData->ref.ref(); + break; + } + case Array: + case Object: + d = data; + this->base = v.base(base); + break; + } + if (d) + d->ref.ref(); +} + +/*! + Creates a value of type Bool, with value \a b. + */ +JsonValue::JsonValue(bool b) + : d(0), t(Bool) +{ + this->b = b; +} + +/*! + Creates a value of type Double, with value \a n. + */ +JsonValue::JsonValue(double n) + : d(0), t(Double) +{ + this->dbl = n; +} + +/*! + \overload + Creates a value of type Double, with value \a n. + */ +JsonValue::JsonValue(int n) + : d(0), t(Double) +{ + this->dbl = n; +} + +/*! + \overload + Creates a value of type Double, with value \a n. + NOTE: the integer limits for IEEE 754 double precision data is 2^53 (-9007199254740992 to +9007199254740992). + If you pass in values outside this range expect a loss of precision to occur. + */ +JsonValue::JsonValue(int64_t n) + : d(0), t(Double) +{ + this->dbl = n; +} + +/*! + Creates a value of type String, with value \a s. + */ +JsonValue::JsonValue(const std::string &s) + : d(0), t(String) +{ + stringData = new Internal::SharedString; + stringData->s = s; + stringData->ref.ref(); +} + +JsonValue::JsonValue(const char *s) + : d(0), t(String) +{ + stringData = new Internal::SharedString; + stringData->s = s; + stringData->ref.ref(); +} + +/*! + Creates a value of type Array, with value \a a. + */ +JsonValue::JsonValue(const JsonArray &a) + : d(a.d), t(Array) +{ + base = a.a; + if (d) + d->ref.ref(); +} + +/*! + Creates a value of type Object, with value \a o. + */ +JsonValue::JsonValue(const JsonObject &o) + : d(o.d), t(Object) +{ + base = o.o; + if (d) + d->ref.ref(); +} + + +/*! + Destroys the value. + */ +JsonValue::~JsonValue() +{ + if (t == String && stringData && !stringData->ref.deref()) + free(stringData); + + if (d && !d->ref.deref()) + delete d; +} + +/*! + Creates a copy of \a other. + */ +JsonValue::JsonValue(const JsonValue &other) + : t(other.t) +{ + d = other.d; + ui = other.ui; + if (d) + d->ref.ref(); + + if (t == String && stringData) + stringData->ref.ref(); +} + +/*! + Assigns the value stored in \a other to this object. + */ +JsonValue &JsonValue::operator=(const JsonValue &other) +{ + if (t == String && stringData && !stringData->ref.deref()) + free(stringData); + + t = other.t; + dbl = other.dbl; + + if (d != other.d) { + + if (d && !d->ref.deref()) + delete d; + d = other.d; + if (d) + d->ref.ref(); + + } + + if (t == String && stringData) + stringData->ref.ref(); + + return *this; +} + +/*! + \fn bool JsonValue::isNull() const + + Returns \c true if the value is null. +*/ + +/*! + \fn bool JsonValue::isBool() const + + Returns \c true if the value contains a boolean. + + \sa toBool() + */ + +/*! + \fn bool JsonValue::isDouble() const + + Returns \c true if the value contains a double. + + \sa toDouble() + */ + +/*! + \fn bool JsonValue::isString() const + + Returns \c true if the value contains a string. + + \sa toString() + */ + +/*! + \fn bool JsonValue::isArray() const + + Returns \c true if the value contains an array. + + \sa toArray() + */ + +/*! + \fn bool JsonValue::isObject() const + + Returns \c true if the value contains an object. + + \sa toObject() + */ + +/*! + \fn bool JsonValue::isUndefined() const + + Returns \c true if the value is undefined. This can happen in certain + error cases as e.g. accessing a non existing key in a JsonObject. + */ + + +/*! + \enum JsonValue::Type + + This enum describes the type of the JSON value. + + \value Null A Null value + \value Bool A boolean value. Use toBool() to convert to a bool. + \value Double A double. Use toDouble() to convert to a double. + \value String A string. Use toString() to convert to a QString. + \value Array An array. Use toArray() to convert to a JsonArray. + \value Object An object. Use toObject() to convert to a JsonObject. + \value Undefined The value is undefined. This is usually returned as an + error condition, when trying to read an out of bounds value + in an array or a non existent key in an object. +*/ + +/*! + Returns the type of the value. + + \sa JsonValue::Type + */ + + +/*! + Converts the value to a bool and returns it. + + If type() is not bool, the \a defaultValue will be returned. + */ +bool JsonValue::toBool(bool defaultValue) const +{ + if (t != Bool) + return defaultValue; + return b; +} + +/*! + Converts the value to an int and returns it. + + If type() is not Double or the value is not a whole number, + the \a defaultValue will be returned. + */ +int JsonValue::toInt(int defaultValue) const +{ + if (t == Double && int(dbl) == dbl) + return dbl; + return defaultValue; +} + +/*! + Converts the value to a double and returns it. + + If type() is not Double, the \a defaultValue will be returned. + */ +double JsonValue::toDouble(double defaultValue) const +{ + if (t != Double) + return defaultValue; + return dbl; +} + +/*! + Converts the value to a QString and returns it. + + If type() is not String, the \a defaultValue will be returned. + */ +std::string JsonValue::toString(const std::string &defaultValue) const +{ + if (t != String) + return defaultValue; + return stringData->s; +} + +/*! + Converts the value to an array and returns it. + + If type() is not Array, the \a defaultValue will be returned. + */ +JsonArray JsonValue::toArray(const JsonArray &defaultValue) const +{ + if (!d || t != Array) + return defaultValue; + + return JsonArray(d, static_cast<Internal::Array *>(base)); +} + +/*! + \overload + + Converts the value to an array and returns it. + + If type() is not Array, a \l{JsonArray::}{JsonArray()} will be returned. + */ +JsonArray JsonValue::toArray() const +{ + return toArray(JsonArray()); +} + +/*! + Converts the value to an object and returns it. + + If type() is not Object, the \a defaultValue will be returned. + */ +JsonObject JsonValue::toObject(const JsonObject &defaultValue) const +{ + if (!d || t != Object) + return defaultValue; + + return JsonObject(d, static_cast<Internal::Object *>(base)); +} + +/*! + \overload + + Converts the value to an object and returns it. + + If type() is not Object, the \l {JsonObject::}{JsonObject()} will be returned. +*/ +JsonObject JsonValue::toObject() const +{ + return toObject(JsonObject()); +} + +/*! + Returns \c true if the value is equal to \a other. + */ +bool JsonValue::operator==(const JsonValue &other) const +{ + if (t != other.t) + return false; + + switch (t) { + case Undefined: + case Null: + break; + case Bool: + return b == other.b; + case Double: + return dbl == other.dbl; + case String: + return toString() == other.toString(); + case Array: + if (base == other.base) + return true; + if (!base) + return !other.base->length; + if (!other.base) + return !base->length; + return JsonArray(d, static_cast<Internal::Array *>(base)) + == JsonArray(other.d, static_cast<Internal::Array *>(other.base)); + case Object: + if (base == other.base) + return true; + if (!base) + return !other.base->length; + if (!other.base) + return !base->length; + return JsonObject(d, static_cast<Internal::Object *>(base)) + == JsonObject(other.d, static_cast<Internal::Object *>(other.base)); + } + return true; +} + +/*! + Returns \c true if the value is not equal to \a other. + */ +bool JsonValue::operator!=(const JsonValue &other) const +{ + return !(*this == other); +} + +/*! + \internal + */ +void JsonValue::detach() +{ + if (!d) + return; + + Internal::Data *x = d->clone(base); + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + base = static_cast<Internal::Object *>(d->header->root()); +} + + +/*! + \class JsonValueRef + \inmodule QtCore + \reentrant + \brief The JsonValueRef class is a helper class for JsonValue. + + \internal + + \ingroup json + + When you get an object of type JsonValueRef, if you can assign to it, + the assignment will apply to the character in the string from + which you got the reference. That is its whole purpose in life. + + You can use it exactly in the same way as a reference to a JsonValue. + + The JsonValueRef becomes invalid once modifications are made to the + string: if you want to keep the character, copy it into a JsonValue. + + Most of the JsonValue member functions also exist in JsonValueRef. + However, they are not explicitly documented here. +*/ + + +JsonValueRef &JsonValueRef::operator=(const JsonValue &val) +{ + if (is_object) + o->setValueAt(index, val); + else + a->replace(index, val); + + return *this; +} + +JsonValueRef &JsonValueRef::operator=(const JsonValueRef &ref) +{ + if (is_object) + o->setValueAt(index, ref); + else + a->replace(index, ref); + + return *this; +} + +JsonArray JsonValueRef::toArray() const +{ + return toValue().toArray(); +} + +JsonObject JsonValueRef::toObject() const +{ + return toValue().toObject(); +} + +JsonValue JsonValueRef::toValue() const +{ + if (!is_object) + return a->at(index); + return o->valueAt(index); +} + +/*! + \class JsonArray + \inmodule QtCore + \ingroup json + \ingroup shared + \reentrant + \since 5.0 + + \brief The JsonArray class encapsulates a JSON array. + + A JSON array is a list of values. The list can be manipulated by inserting and + removing JsonValue's from the array. + + A JsonArray can be converted to and from a QVariantList. You can query the + number of entries with size(), insert(), and removeAt() entries from it + and iterate over its content using the standard C++ iterator pattern. + + JsonArray is an implicitly shared class and shares the data with the document + it has been created from as long as it is not being modified. + + You can convert the array to and from text based JSON through JsonDocument. + + \sa {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! + \typedef JsonArray::Iterator + + Qt-style synonym for JsonArray::iterator. +*/ + +/*! + \typedef JsonArray::ConstIterator + + Qt-style synonym for JsonArray::const_iterator. +*/ + +/*! + \typedef JsonArray::size_type + + Typedef for int. Provided for STL compatibility. +*/ + +/*! + \typedef JsonArray::value_type + + Typedef for JsonValue. Provided for STL compatibility. +*/ + +/*! + \typedef JsonArray::difference_type + + Typedef for int. Provided for STL compatibility. +*/ + +/*! + \typedef JsonArray::pointer + + Typedef for JsonValue *. Provided for STL compatibility. +*/ + +/*! + \typedef JsonArray::const_pointer + + Typedef for const JsonValue *. Provided for STL compatibility. +*/ + +/*! + \typedef JsonArray::reference + + Typedef for JsonValue &. Provided for STL compatibility. +*/ + +/*! + \typedef JsonArray::const_reference + + Typedef for const JsonValue &. Provided for STL compatibility. +*/ + +/*! + Creates an empty array. + */ +JsonArray::JsonArray() + : d(0), a(0) +{ +} + +JsonArray::JsonArray(std::initializer_list<JsonValue> args) + : d(0), a(0) +{ + for (auto i = args.begin(); i != args.end(); ++i) + append(*i); +} + +/*! + \fn JsonArray::JsonArray(std::initializer_list<JsonValue> args) + \since 5.4 + Creates an array initialized from \a args initialization list. + + JsonArray can be constructed in a way similar to JSON notation, + for example: + \code + JsonArray array = { 1, 2.2, QString() }; + \endcode + */ + +/*! + \internal + */ +JsonArray::JsonArray(Internal::Data *data, Internal::Array *array) + : d(data), a(array) +{ + // assert(data); + // assert(array); + d->ref.ref(); +} + +/*! + Deletes the array. + */ +JsonArray::~JsonArray() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Creates a copy of \a other. + + Since JsonArray is implicitly shared, the copy is shallow + as long as the object doesn't get modified. + */ +JsonArray::JsonArray(const JsonArray &other) +{ + d = other.d; + a = other.a; + if (d) + d->ref.ref(); +} + +/*! + Assigns \a other to this array. + */ +JsonArray &JsonArray::operator=(const JsonArray &other) +{ + if (d != other.d) { + if (d && !d->ref.deref()) + delete d; + d = other.d; + if (d) + d->ref.ref(); + } + a = other.a; + + return *this; +} + +/*! \fn JsonArray &JsonArray::operator+=(const JsonValue &value) + + Appends \a value to the array, and returns a reference to the array itself. + + \since 5.3 + \sa append(), operator<<() +*/ + +/*! \fn JsonArray JsonArray::operator+(const JsonValue &value) const + + Returns an array that contains all the items in this array followed + by the provided \a value. + + \since 5.3 + \sa operator+=() +*/ + +/*! \fn JsonArray &JsonArray::operator<<(const JsonValue &value) + + Appends \a value to the array, and returns a reference to the array itself. + + \since 5.3 + \sa operator+=(), append() +*/ + +/*! + Returns the number of values stored in the array. + */ +int JsonArray::size() const +{ + if (!d) + return 0; + + return (int)a->length; +} + +/*! + \fn JsonArray::count() const + + Same as size(). + + \sa size() +*/ + +/*! + Returns \c true if the object is empty. This is the same as size() == 0. + + \sa size() + */ +bool JsonArray::isEmpty() const +{ + if (!d) + return true; + + return !a->length; +} + +/*! + Returns a JsonValue representing the value for index \a i. + + The returned JsonValue is \c Undefined, if \a i is out of bounds. + + */ +JsonValue JsonArray::at(int i) const +{ + if (!a || i < 0 || i >= (int)a->length) + return JsonValue(JsonValue::Undefined); + + return JsonValue(d, a, a->at(i)); +} + +/*! + Returns the first value stored in the array. + + Same as \c at(0). + + \sa at() + */ +JsonValue JsonArray::first() const +{ + return at(0); +} + +/*! + Returns the last value stored in the array. + + Same as \c{at(size() - 1)}. + + \sa at() + */ +JsonValue JsonArray::last() const +{ + return at(a ? (a->length - 1) : 0); +} + +/*! + Inserts \a value at the beginning of the array. + + This is the same as \c{insert(0, value)} and will prepend \a value to the array. + + \sa append(), insert() + */ +void JsonArray::prepend(const JsonValue &value) +{ + insert(0, value); +} + +/*! + Inserts \a value at the end of the array. + + \sa prepend(), insert() + */ +void JsonArray::append(const JsonValue &value) +{ + insert(a ? (int)a->length : 0, value); +} + +/*! + Removes the value at index position \a i. \a i must be a valid + index position in the array (i.e., \c{0 <= i < size()}). + + \sa insert(), replace() + */ +void JsonArray::removeAt(int i) +{ + if (!a || i < 0 || i >= (int)a->length) + return; + + detach(); + a->removeItems(i, 1); + ++d->compactionCounter; + if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u) + compact(); +} + +/*! \fn void JsonArray::removeFirst() + + Removes the first item in the array. Calling this function is + equivalent to calling \c{removeAt(0)}. The array must not be empty. If + the array can be empty, call isEmpty() before calling this + function. + + \sa removeAt(), removeLast() +*/ + +/*! \fn void JsonArray::removeLast() + + Removes the last item in the array. Calling this function is + equivalent to calling \c{removeAt(size() - 1)}. The array must not be + empty. If the array can be empty, call isEmpty() before calling + this function. + + \sa removeAt(), removeFirst() +*/ + +/*! + Removes the item at index position \a i and returns it. \a i must + be a valid index position in the array (i.e., \c{0 <= i < size()}). + + If you don't use the return value, removeAt() is more efficient. + + \sa removeAt() + */ +JsonValue JsonArray::takeAt(int i) +{ + if (!a || i < 0 || i >= (int)a->length) + return JsonValue(JsonValue::Undefined); + + JsonValue v(d, a, a->at(i)); + removeAt(i); // detaches + return v; +} + +/*! + Inserts \a value at index position \a i in the array. If \a i + is \c 0, the value is prepended to the array. If \a i is size(), the + value is appended to the array. + + \sa append(), prepend(), replace(), removeAt() + */ +void JsonArray::insert(int i, const JsonValue &value) +{ + // assert (i >= 0 && i <= (a ? (int)a->length : 0)); + JsonValue val = value; + + bool compressed; + int valueSize = Internal::Value::requiredStorage(val, &compressed); + + detach(valueSize + sizeof(Internal::Value)); + + if (!a->length) + a->tableOffset = sizeof(Internal::Array); + + int valueOffset = a->reserveSpace(valueSize, i, 1, false); + if (!valueOffset) + return; + + Internal::Value &v = (*a)[i]; + v.type = (val.t == JsonValue::Undefined ? JsonValue::Null : val.t); + v.intValue = compressed; + v.value = Internal::Value::valueToStore(val, valueOffset); + if (valueSize) + Internal::Value::copyData(val, (char *)a + valueOffset, compressed); +} + +/*! + \fn JsonArray::iterator JsonArray::insert(iterator before, const JsonValue &value) + + Inserts \a value before the position pointed to by \a before, and returns an iterator + pointing to the newly inserted item. + + \sa erase(), insert() +*/ + +/*! + \fn JsonArray::iterator JsonArray::erase(iterator it) + + Removes the item pointed to by \a it, and returns an iterator pointing to the + next item. + + \sa removeAt() +*/ + +/*! + Replaces the item at index position \a i with \a value. \a i must + be a valid index position in the array (i.e., \c{0 <= i < size()}). + + \sa operator[](), removeAt() + */ +void JsonArray::replace(int i, const JsonValue &value) +{ + // assert (a && i >= 0 && i < (int)(a->length)); + JsonValue val = value; + + bool compressed; + int valueSize = Internal::Value::requiredStorage(val, &compressed); + + detach(valueSize); + + if (!a->length) + a->tableOffset = sizeof(Internal::Array); + + int valueOffset = a->reserveSpace(valueSize, i, 1, true); + if (!valueOffset) + return; + + Internal::Value &v = (*a)[i]; + v.type = (val.t == JsonValue::Undefined ? JsonValue::Null : val.t); + v.intValue = compressed; + v.value = Internal::Value::valueToStore(val, valueOffset); + if (valueSize) + Internal::Value::copyData(val, (char *)a + valueOffset, compressed); + + ++d->compactionCounter; + if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(a->length) / 2u) + compact(); +} + +/*! + Returns \c true if the array contains an occurrence of \a value, otherwise \c false. + + \sa count() + */ +bool JsonArray::contains(const JsonValue &value) const +{ + for (int i = 0; i < size(); i++) { + if (at(i) == value) + return true; + } + return false; +} + +/*! + Returns the value at index position \a i as a modifiable reference. + \a i must be a valid index position in the array (i.e., \c{0 <= i < + size()}). + + The return value is of type JsonValueRef, a helper class for JsonArray + and JsonObject. When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the character in the JsonArray of JsonObject + from which you got the reference. + + \sa at() + */ +JsonValueRef JsonArray::operator[](int i) +{ + // assert(a && i >= 0 && i < (int)a->length); + return JsonValueRef(this, i); +} + +/*! + \overload + + Same as at(). + */ +JsonValue JsonArray::operator[](int i) const +{ + return at(i); +} + +/*! + Returns \c true if this array is equal to \a other. + */ +bool JsonArray::operator==(const JsonArray &other) const +{ + if (a == other.a) + return true; + + if (!a) + return !other.a->length; + if (!other.a) + return !a->length; + if (a->length != other.a->length) + return false; + + for (int i = 0; i < (int)a->length; ++i) { + if (JsonValue(d, a, a->at(i)) != JsonValue(other.d, other.a, other.a->at(i))) + return false; + } + return true; +} + +/*! + Returns \c true if this array is not equal to \a other. + */ +bool JsonArray::operator!=(const JsonArray &other) const +{ + return !(*this == other); +} + +/*! \fn JsonArray::iterator JsonArray::begin() + + Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in + the array. + + \sa constBegin(), end() +*/ + +/*! \fn JsonArray::const_iterator JsonArray::begin() const + + \overload +*/ + +/*! \fn JsonArray::const_iterator JsonArray::constBegin() const + + Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item + in the array. + + \sa begin(), constEnd() +*/ + +/*! \fn JsonArray::iterator JsonArray::end() + + Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item + after the last item in the array. + + \sa begin(), constEnd() +*/ + +/*! \fn const_iterator JsonArray::end() const + + \overload +*/ + +/*! \fn JsonArray::const_iterator JsonArray::constEnd() const + + Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary + item after the last item in the array. + + \sa constBegin(), end() +*/ + +/*! \fn void JsonArray::push_back(const JsonValue &value) + + This function is provided for STL compatibility. It is equivalent + to \l{JsonArray::append()}{append(value)} and will append \a value to the array. +*/ + +/*! \fn void JsonArray::push_front(const JsonValue &value) + + This function is provided for STL compatibility. It is equivalent + to \l{JsonArray::prepend()}{prepend(value)} and will prepend \a value to the array. +*/ + +/*! \fn void JsonArray::pop_front() + + This function is provided for STL compatibility. It is equivalent + to removeFirst(). The array must not be empty. If the array can be + empty, call isEmpty() before calling this function. +*/ + +/*! \fn void JsonArray::pop_back() + + This function is provided for STL compatibility. It is equivalent + to removeLast(). The array must not be empty. If the array can be + empty, call isEmpty() before calling this function. +*/ + +/*! \fn bool JsonArray::empty() const + + This function is provided for STL compatibility. It is equivalent + to isEmpty() and returns \c true if the array is empty. +*/ + +/*! \class JsonArray::iterator + \inmodule QtCore + \brief The JsonArray::iterator class provides an STL-style non-const iterator for JsonArray. + + JsonArray::iterator allows you to iterate over a JsonArray + and to modify the array item associated with the + iterator. If you want to iterate over a const JsonArray, use + JsonArray::const_iterator instead. It is generally a good practice to + use JsonArray::const_iterator on a non-const JsonArray as well, unless + you need to change the JsonArray through the iterator. Const + iterators are slightly faster and improves code readability. + + The default JsonArray::iterator constructor creates an uninitialized + iterator. You must initialize it using a JsonArray function like + JsonArray::begin(), JsonArray::end(), or JsonArray::insert() before you can + start iterating. + + Most JsonArray functions accept an integer index rather than an + iterator. For that reason, iterators are rarely useful in + connection with JsonArray. One place where STL-style iterators do + make sense is as arguments to \l{generic algorithms}. + + Multiple iterators can be used on the same array. However, be + aware that any non-const function call performed on the JsonArray + will render all existing iterators undefined. + + \sa JsonArray::const_iterator +*/ + +/*! \typedef JsonArray::iterator::iterator_category + + A synonym for \e {std::random_access_iterator_tag} indicating + this iterator is a random access iterator. +*/ + +/*! \typedef JsonArray::iterator::difference_type + + \internal +*/ + +/*! \typedef JsonArray::iterator::value_type + + \internal +*/ + +/*! \typedef JsonArray::iterator::reference + + \internal +*/ + +/*! \typedef JsonArray::iterator::pointer + + \internal +*/ + +/*! \fn JsonArray::iterator::iterator() + + Constructs an uninitialized iterator. + + Functions like operator*() and operator++() should not be called + on an uninitialized iterator. Use operator=() to assign a value + to it before using it. + + \sa JsonArray::begin(), JsonArray::end() +*/ + +/*! \fn JsonArray::iterator::iterator(JsonArray *array, int index) + \internal +*/ + +/*! \fn JsonValueRef JsonArray::iterator::operator*() const + + + Returns a modifiable reference to the current item. + + You can change the value of an item by using operator*() on the + left side of an assignment. + + The return value is of type JsonValueRef, a helper class for JsonArray + and JsonObject. When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the character in the JsonArray of JsonObject + from which you got the reference. +*/ + +/*! \fn JsonValueRef *JsonArray::iterator::operator->() const + + Returns a pointer to a modifiable reference to the current item. +*/ + +/*! \fn JsonValueRef JsonArray::iterator::operator[](int j) const + + Returns a modifiable reference to the item at offset \a j from the + item pointed to by this iterator (the item at position \c{*this + j}). + + This function is provided to make JsonArray iterators behave like C++ + pointers. + + The return value is of type JsonValueRef, a helper class for JsonArray + and JsonObject. When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the character in the JsonArray of JsonObject + from which you got the reference. + + \sa operator+() +*/ + +/*! + \fn bool JsonArray::iterator::operator==(const iterator &other) const + \fn bool JsonArray::iterator::operator==(const const_iterator &other) const + + Returns \c true if \a other points to the same item as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! + \fn bool JsonArray::iterator::operator!=(const iterator &other) const + \fn bool JsonArray::iterator::operator!=(const const_iterator &other) const + + Returns \c true if \a other points to a different item than this + iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! + \fn bool JsonArray::iterator::operator<(const iterator& other) const + \fn bool JsonArray::iterator::operator<(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is less than + the item pointed to by the \a other iterator. +*/ + +/*! + \fn bool JsonArray::iterator::operator<=(const iterator& other) const + \fn bool JsonArray::iterator::operator<=(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is less than + or equal to the item pointed to by the \a other iterator. +*/ + +/*! + \fn bool JsonArray::iterator::operator>(const iterator& other) const + \fn bool JsonArray::iterator::operator>(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is greater + than the item pointed to by the \a other iterator. +*/ + +/*! + \fn bool JsonArray::iterator::operator>=(const iterator& other) const + \fn bool JsonArray::iterator::operator>=(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is greater + than or equal to the item pointed to by the \a other iterator. +*/ + +/*! \fn JsonArray::iterator &JsonArray::iterator::operator++() + + The prefix ++ operator, \c{++it}, advances the iterator to the + next item in the array and returns an iterator to the new current + item. + + Calling this function on JsonArray::end() leads to undefined results. + + \sa operator--() +*/ + +/*! \fn JsonArray::iterator JsonArray::iterator::operator++(int) + + \overload + + The postfix ++ operator, \c{it++}, advances the iterator to the + next item in the array and returns an iterator to the previously + current item. +*/ + +/*! \fn JsonArray::iterator &JsonArray::iterator::operator--() + + The prefix -- operator, \c{--it}, makes the preceding item + current and returns an iterator to the new current item. + + Calling this function on JsonArray::begin() leads to undefined results. + + \sa operator++() +*/ + +/*! \fn JsonArray::iterator JsonArray::iterator::operator--(int) + + \overload + + The postfix -- operator, \c{it--}, makes the preceding item + current and returns an iterator to the previously current item. +*/ + +/*! \fn JsonArray::iterator &JsonArray::iterator::operator+=(int j) + + Advances the iterator by \a j items. If \a j is negative, the + iterator goes backward. + + \sa operator-=(), operator+() +*/ + +/*! \fn JsonArray::iterator &JsonArray::iterator::operator-=(int j) + + Makes the iterator go back by \a j items. If \a j is negative, + the iterator goes forward. + + \sa operator+=(), operator-() +*/ + +/*! \fn JsonArray::iterator JsonArray::iterator::operator+(int j) const + + Returns an iterator to the item at \a j positions forward from + this iterator. If \a j is negative, the iterator goes backward. + + \sa operator-(), operator+=() +*/ + +/*! \fn JsonArray::iterator JsonArray::iterator::operator-(int j) const + + Returns an iterator to the item at \a j positions backward from + this iterator. If \a j is negative, the iterator goes forward. + + \sa operator+(), operator-=() +*/ + +/*! \fn int JsonArray::iterator::operator-(iterator other) const + + Returns the number of items between the item pointed to by \a + other and the item pointed to by this iterator. +*/ + +/*! \class JsonArray::const_iterator + \inmodule QtCore + \brief The JsonArray::const_iterator class provides an STL-style const iterator for JsonArray. + + JsonArray::const_iterator allows you to iterate over a + JsonArray. If you want to modify the JsonArray as + you iterate over it, use JsonArray::iterator instead. It is generally a + good practice to use JsonArray::const_iterator on a non-const JsonArray + as well, unless you need to change the JsonArray through the + iterator. Const iterators are slightly faster and improves + code readability. + + The default JsonArray::const_iterator constructor creates an + uninitialized iterator. You must initialize it using a JsonArray + function like JsonArray::constBegin(), JsonArray::constEnd(), or + JsonArray::insert() before you can start iterating. + + Most JsonArray functions accept an integer index rather than an + iterator. For that reason, iterators are rarely useful in + connection with JsonArray. One place where STL-style iterators do + make sense is as arguments to \l{generic algorithms}. + + Multiple iterators can be used on the same array. However, be + aware that any non-const function call performed on the JsonArray + will render all existing iterators undefined. + + \sa JsonArray::iterator +*/ + +/*! \fn JsonArray::const_iterator::const_iterator() + + Constructs an uninitialized iterator. + + Functions like operator*() and operator++() should not be called + on an uninitialized iterator. Use operator=() to assign a value + to it before using it. + + \sa JsonArray::constBegin(), JsonArray::constEnd() +*/ + +/*! \fn JsonArray::const_iterator::const_iterator(const JsonArray *array, int index) + \internal +*/ + +/*! \typedef JsonArray::const_iterator::iterator_category + + A synonym for \e {std::random_access_iterator_tag} indicating + this iterator is a random access iterator. +*/ + +/*! \typedef JsonArray::const_iterator::difference_type + + \internal +*/ + +/*! \typedef JsonArray::const_iterator::value_type + + \internal +*/ + +/*! \typedef JsonArray::const_iterator::reference + + \internal +*/ + +/*! \typedef JsonArray::const_iterator::pointer + + \internal +*/ + +/*! \fn JsonArray::const_iterator::const_iterator(const const_iterator &other) + + Constructs a copy of \a other. +*/ + +/*! \fn JsonArray::const_iterator::const_iterator(const iterator &other) + + Constructs a copy of \a other. +*/ + +/*! \fn JsonValue JsonArray::const_iterator::operator*() const + + Returns the current item. +*/ + +/*! \fn JsonValue *JsonArray::const_iterator::operator->() const + + Returns a pointer to the current item. +*/ + +/*! \fn JsonValue JsonArray::const_iterator::operator[](int j) const + + Returns the item at offset \a j from the item pointed to by this iterator (the item at + position \c{*this + j}). + + This function is provided to make JsonArray iterators behave like C++ + pointers. + + \sa operator+() +*/ + +/*! \fn bool JsonArray::const_iterator::operator==(const const_iterator &other) const + + Returns \c true if \a other points to the same item as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! \fn bool JsonArray::const_iterator::operator!=(const const_iterator &other) const + + Returns \c true if \a other points to a different item than this + iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! + \fn bool JsonArray::const_iterator::operator<(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is less than + the item pointed to by the \a other iterator. +*/ + +/*! + \fn bool JsonArray::const_iterator::operator<=(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is less than + or equal to the item pointed to by the \a other iterator. +*/ + +/*! + \fn bool JsonArray::const_iterator::operator>(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is greater + than the item pointed to by the \a other iterator. +*/ + +/*! + \fn bool JsonArray::const_iterator::operator>=(const const_iterator& other) const + + Returns \c true if the item pointed to by this iterator is greater + than or equal to the item pointed to by the \a other iterator. +*/ + +/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator++() + + The prefix ++ operator, \c{++it}, advances the iterator to the + next item in the array and returns an iterator to the new current + item. + + Calling this function on JsonArray::end() leads to undefined results. + + \sa operator--() +*/ + +/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator++(int) + + \overload + + The postfix ++ operator, \c{it++}, advances the iterator to the + next item in the array and returns an iterator to the previously + current item. +*/ + +/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator--() + + The prefix -- operator, \c{--it}, makes the preceding item + current and returns an iterator to the new current item. + + Calling this function on JsonArray::begin() leads to undefined results. + + \sa operator++() +*/ + +/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator--(int) + + \overload + + The postfix -- operator, \c{it--}, makes the preceding item + current and returns an iterator to the previously current item. +*/ + +/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator+=(int j) + + Advances the iterator by \a j items. If \a j is negative, the + iterator goes backward. + + \sa operator-=(), operator+() +*/ + +/*! \fn JsonArray::const_iterator &JsonArray::const_iterator::operator-=(int j) + + Makes the iterator go back by \a j items. If \a j is negative, + the iterator goes forward. + + \sa operator+=(), operator-() +*/ + +/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator+(int j) const + + Returns an iterator to the item at \a j positions forward from + this iterator. If \a j is negative, the iterator goes backward. + + \sa operator-(), operator+=() +*/ + +/*! \fn JsonArray::const_iterator JsonArray::const_iterator::operator-(int j) const + + Returns an iterator to the item at \a j positions backward from + this iterator. If \a j is negative, the iterator goes forward. + + \sa operator+(), operator-=() +*/ + +/*! \fn int JsonArray::const_iterator::operator-(const_iterator other) const + + Returns the number of items between the item pointed to by \a + other and the item pointed to by this iterator. +*/ + + +/*! + \internal + */ +void JsonArray::detach(uint32_t reserve) +{ + if (!d) { + d = new Internal::Data(reserve, JsonValue::Array); + a = static_cast<Internal::Array *>(d->header->root()); + d->ref.ref(); + return; + } + if (reserve == 0 && d->ref.load() == 1) + return; + + Internal::Data *x = d->clone(a, reserve); + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + a = static_cast<Internal::Array *>(d->header->root()); +} + +/*! + \internal + */ +void JsonArray::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + a = static_cast<Internal::Array *>(d->header->root()); +} + +/*! + \class JsonObject + \inmodule QtCore + \ingroup json + \ingroup shared + \reentrant + \since 5.0 + + \brief The JsonObject class encapsulates a JSON object. + + A JSON object is a list of key value pairs, where the keys are unique strings + and the values are represented by a JsonValue. + + A JsonObject can be converted to and from a QVariantMap. You can query the + number of (key, value) pairs with size(), insert(), and remove() entries from it + and iterate over its content using the standard C++ iterator pattern. + + JsonObject is an implicitly shared class, and shares the data with the document + it has been created from as long as it is not being modified. + + You can convert the object to and from text based JSON through JsonDocument. + + \sa {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! + \typedef JsonObject::Iterator + + Qt-style synonym for JsonObject::iterator. +*/ + +/*! + \typedef JsonObject::ConstIterator + + Qt-style synonym for JsonObject::const_iterator. +*/ + +/*! + \typedef JsonObject::key_type + + Typedef for QString. Provided for STL compatibility. +*/ + +/*! + \typedef JsonObject::mapped_type + + Typedef for JsonValue. Provided for STL compatibility. +*/ + +/*! + \typedef JsonObject::size_type + + Typedef for int. Provided for STL compatibility. +*/ + + +/*! + Constructs an empty JSON object. + + \sa isEmpty() + */ +JsonObject::JsonObject() + : d(0), o(0) +{ +} + +JsonObject::JsonObject(std::initializer_list<std::pair<std::string, JsonValue> > args) + : d(0), o(0) +{ + for (auto i = args.begin(); i != args.end(); ++i) + insert(i->first, i->second); +} + +/*! + \fn JsonObject::JsonObject(std::initializer_list<QPair<QString, JsonValue> > args) + \since 5.4 + Constructs a JsonObject instance initialized from \a args initialization list. + For example: + \code + JsonObject object + { + {"property1", 1}, + {"property2", 2} + }; + \endcode +*/ + +/*! + \internal + */ +JsonObject::JsonObject(Internal::Data *data, Internal::Object *object) + : d(data), o(object) +{ + // assert(d); + // assert(o); + d->ref.ref(); +} + +/*! + This method replaces part of the JsonObject(std::initializer_list<QPair<QString, JsonValue>> args) body. + The constructor needs to be inline, but we do not want to leak implementation details + of this class. + \note this method is called for an uninitialized object + \internal + */ + +/*! + Destroys the object. + */ +JsonObject::~JsonObject() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Creates a copy of \a other. + + Since JsonObject is implicitly shared, the copy is shallow + as long as the object does not get modified. + */ +JsonObject::JsonObject(const JsonObject &other) +{ + d = other.d; + o = other.o; + if (d) + d->ref.ref(); +} + +/*! + Assigns \a other to this object. + */ +JsonObject &JsonObject::operator=(const JsonObject &other) +{ + if (d != other.d) { + if (d && !d->ref.deref()) + delete d; + d = other.d; + if (d) + d->ref.ref(); + } + o = other.o; + + return *this; +} + +/*! + Returns a list of all keys in this object. + + The list is sorted lexographically. + */ +JsonObject::Keys JsonObject::keys() const +{ + Keys keys; + if (!d) + return keys; + + keys.reserve(o->length); + for (uint32_t i = 0; i < o->length; ++i) { + Internal::Entry *e = o->entryAt(i); + keys.push_back(e->key().data()); + } + + return keys; +} + +/*! + Returns the number of (key, value) pairs stored in the object. + */ +int JsonObject::size() const +{ + if (!d) + return 0; + + return o->length; +} + +/*! + Returns \c true if the object is empty. This is the same as size() == 0. + + \sa size() + */ +bool JsonObject::isEmpty() const +{ + if (!d) + return true; + + return !o->length; +} + +/*! + Returns a JsonValue representing the value for the key \a key. + + The returned JsonValue is JsonValue::Undefined if the key does not exist. + + \sa JsonValue, JsonValue::isUndefined() + */ +JsonValue JsonObject::value(const std::string &key) const +{ + if (!d) + return JsonValue(JsonValue::Undefined); + + bool keyExists; + int i = o->indexOf(key, &keyExists); + if (!keyExists) + return JsonValue(JsonValue::Undefined); + return JsonValue(d, o, o->entryAt(i)->value); +} + +/*! + Returns a JsonValue representing the value for the key \a key. + + This does the same as value(). + + The returned JsonValue is JsonValue::Undefined if the key does not exist. + + \sa value(), JsonValue, JsonValue::isUndefined() + */ +JsonValue JsonObject::operator[](const std::string &key) const +{ + return value(key); +} + +/*! + Returns a reference to the value for \a key. + + The return value is of type JsonValueRef, a helper class for JsonArray + and JsonObject. When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the element in the JsonArray or JsonObject + from which you got the reference. + + \sa value() + */ +JsonValueRef JsonObject::operator[](const std::string &key) +{ + // ### somewhat inefficient, as we lookup the key twice if it doesn't yet exist + bool keyExists = false; + int index = o ? o->indexOf(key, &keyExists) : -1; + if (!keyExists) { + iterator i = insert(key, JsonValue()); + index = i.i; + } + return JsonValueRef(this, index); +} + +/*! + Inserts a new item with the key \a key and a value of \a value. + + If there is already an item with the key \a key, then that item's value + is replaced with \a value. + + Returns an iterator pointing to the inserted item. + + If the value is JsonValue::Undefined, it will cause the key to get removed + from the object. The returned iterator will then point to end(). + + \sa remove(), take(), JsonObject::iterator, end() + */ +JsonObject::iterator JsonObject::insert(const std::string &key, const JsonValue &value) +{ + if (value.t == JsonValue::Undefined) { + remove(key); + return end(); + } + JsonValue val = value; + + bool isIntValue; + int valueSize = Internal::Value::requiredStorage(val, &isIntValue); + + int valueOffset = sizeof(Internal::Entry) + Internal::qStringSize(key); + int requiredSize = valueOffset + valueSize; + + detach(requiredSize + sizeof(Internal::offset)); // offset for the new index entry + + if (!o->length) + o->tableOffset = sizeof(Internal::Object); + + bool keyExists = false; + int pos = o->indexOf(key, &keyExists); + if (keyExists) + ++d->compactionCounter; + + uint32_t off = o->reserveSpace(requiredSize, pos, 1, keyExists); + if (!off) + return end(); + + Internal::Entry *e = o->entryAt(pos); + e->value.type = val.t; + e->value.intValue = isIntValue; + e->value.value = Internal::Value::valueToStore(val, (char *)e - (char *)o + valueOffset); + Internal::copyString((char *)(e + 1), key); + if (valueSize) + Internal::Value::copyData(val, (char *)e + valueOffset, isIntValue); + + if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u) + compact(); + + return iterator(this, pos); +} + +/*! + Removes \a key from the object. + + \sa insert(), take() + */ +void JsonObject::remove(const std::string &key) +{ + if (!d) + return; + + bool keyExists; + int index = o->indexOf(key, &keyExists); + if (!keyExists) + return; + + detach(); + o->removeItems(index, 1); + ++d->compactionCounter; + if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u) + compact(); +} + +/*! + Removes \a key from the object. + + Returns a JsonValue containing the value referenced by \a key. + If \a key was not contained in the object, the returned JsonValue + is JsonValue::Undefined. + + \sa insert(), remove(), JsonValue + */ +JsonValue JsonObject::take(const std::string &key) +{ + if (!o) + return JsonValue(JsonValue::Undefined); + + bool keyExists; + int index = o->indexOf(key, &keyExists); + if (!keyExists) + return JsonValue(JsonValue::Undefined); + + JsonValue v(d, o, o->entryAt(index)->value); + detach(); + o->removeItems(index, 1); + ++d->compactionCounter; + if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u) + compact(); + + return v; +} + +/*! + Returns \c true if the object contains key \a key. + + \sa insert(), remove(), take() + */ +bool JsonObject::contains(const std::string &key) const +{ + if (!o) + return false; + + bool keyExists; + o->indexOf(key, &keyExists); + return keyExists; +} + +/*! + Returns \c true if \a other is equal to this object. + */ +bool JsonObject::operator==(const JsonObject &other) const +{ + if (o == other.o) + return true; + + if (!o) + return !other.o->length; + if (!other.o) + return !o->length; + if (o->length != other.o->length) + return false; + + for (uint32_t i = 0; i < o->length; ++i) { + Internal::Entry *e = o->entryAt(i); + JsonValue v(d, o, e->value); + if (other.value(e->key()) != v) + return false; + } + + return true; +} + +/*! + Returns \c true if \a other is not equal to this object. + */ +bool JsonObject::operator!=(const JsonObject &other) const +{ + return !(*this == other); +} + +/*! + Removes the (key, value) pair pointed to by the iterator \a it + from the map, and returns an iterator to the next item in the + map. + + \sa remove() + */ +JsonObject::iterator JsonObject::erase(JsonObject::iterator it) +{ + // assert(d && d->ref.load() == 1); + if (it.o != this || it.i < 0 || it.i >= (int)o->length) + return iterator(this, o->length); + + int index = it.i; + + o->removeItems(index, 1); + ++d->compactionCounter; + if (d->compactionCounter > 32u && d->compactionCounter >= unsigned(o->length) / 2u) + compact(); + + // iterator hasn't changed + return it; +} + +/*! + Returns an iterator pointing to the item with key \a key in the + map. + + If the map contains no item with key \a key, the function + returns end(). + */ +JsonObject::iterator JsonObject::find(const std::string &key) +{ + bool keyExists = false; + int index = o ? o->indexOf(key, &keyExists) : 0; + if (!keyExists) + return end(); + detach(); + return iterator(this, index); +} + +/*! \fn JsonObject::const_iterator JsonObject::find(const QString &key) const + + \overload +*/ + +/*! + Returns a const iterator pointing to the item with key \a key in the + map. + + If the map contains no item with key \a key, the function + returns constEnd(). + */ +JsonObject::const_iterator JsonObject::constFind(const std::string &key) const +{ + bool keyExists = false; + int index = o ? o->indexOf(key, &keyExists) : 0; + if (!keyExists) + return end(); + return const_iterator(this, index); +} + +/*! \fn int JsonObject::count() const + + \overload + + Same as size(). +*/ + +/*! \fn int JsonObject::length() const + + \overload + + Same as size(). +*/ + +/*! \fn JsonObject::iterator JsonObject::begin() + + Returns an \l{STL-style iterators}{STL-style iterator} pointing to the first item in + the object. + + \sa constBegin(), end() +*/ + +/*! \fn JsonObject::const_iterator JsonObject::begin() const + + \overload +*/ + +/*! \fn JsonObject::const_iterator JsonObject::constBegin() const + + Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the first item + in the object. + + \sa begin(), constEnd() +*/ + +/*! \fn JsonObject::iterator JsonObject::end() + + Returns an \l{STL-style iterators}{STL-style iterator} pointing to the imaginary item + after the last item in the object. + + \sa begin(), constEnd() +*/ + +/*! \fn JsonObject::const_iterator JsonObject::end() const + + \overload +*/ + +/*! \fn JsonObject::const_iterator JsonObject::constEnd() const + + Returns a const \l{STL-style iterators}{STL-style iterator} pointing to the imaginary + item after the last item in the object. + + \sa constBegin(), end() +*/ + +/*! + \fn bool JsonObject::empty() const + + This function is provided for STL compatibility. It is equivalent + to isEmpty(), returning \c true if the object is empty; otherwise + returning \c false. +*/ + +/*! \class JsonObject::iterator + \inmodule QtCore + \ingroup json + \reentrant + \since 5.0 + + \brief The JsonObject::iterator class provides an STL-style non-const iterator for JsonObject. + + JsonObject::iterator allows you to iterate over a JsonObject + and to modify the value (but not the key) stored under + a particular key. If you want to iterate over a const JsonObject, you + should use JsonObject::const_iterator. It is generally good practice to + use JsonObject::const_iterator on a non-const JsonObject as well, unless you + need to change the JsonObject through the iterator. Const iterators are + slightly faster, and improve code readability. + + The default JsonObject::iterator constructor creates an uninitialized + iterator. You must initialize it using a JsonObject function like + JsonObject::begin(), JsonObject::end(), or JsonObject::find() before you can + start iterating. + + Multiple iterators can be used on the same object. Existing iterators will however + become dangling once the object gets modified. + + \sa JsonObject::const_iterator, {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! \typedef JsonObject::iterator::difference_type + + \internal +*/ + +/*! \typedef JsonObject::iterator::iterator_category + + A synonym for \e {std::bidirectional_iterator_tag} indicating + this iterator is a bidirectional iterator. +*/ + +/*! \typedef JsonObject::iterator::reference + + \internal +*/ + +/*! \typedef JsonObject::iterator::value_type + + \internal +*/ + +/*! \fn JsonObject::iterator::iterator() + + Constructs an uninitialized iterator. + + Functions like key(), value(), and operator++() must not be + called on an uninitialized iterator. Use operator=() to assign a + value to it before using it. + + \sa JsonObject::begin(), JsonObject::end() +*/ + +/*! \fn JsonObject::iterator::iterator(JsonObject *obj, int index) + \internal +*/ + +/*! \fn QString JsonObject::iterator::key() const + + Returns the current item's key. + + There is no direct way of changing an item's key through an + iterator, although it can be done by calling JsonObject::erase() + followed by JsonObject::insert(). + + \sa value() +*/ + +/*! \fn JsonValueRef JsonObject::iterator::value() const + + Returns a modifiable reference to the current item's value. + + You can change the value of an item by using value() on + the left side of an assignment. + + The return value is of type JsonValueRef, a helper class for JsonArray + and JsonObject. When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the element in the JsonArray or JsonObject + from which you got the reference. + + \sa key(), operator*() +*/ + +/*! \fn JsonValueRef JsonObject::iterator::operator*() const + + Returns a modifiable reference to the current item's value. + + Same as value(). + + The return value is of type JsonValueRef, a helper class for JsonArray + and JsonObject. When you get an object of type JsonValueRef, you can + use it as if it were a reference to a JsonValue. If you assign to it, + the assignment will apply to the element in the JsonArray or JsonObject + from which you got the reference. + + \sa key() +*/ + +/*! \fn JsonValueRef *JsonObject::iterator::operator->() const + + Returns a pointer to a modifiable reference to the current item. +*/ + +/*! + \fn bool JsonObject::iterator::operator==(const iterator &other) const + \fn bool JsonObject::iterator::operator==(const const_iterator &other) const + + Returns \c true if \a other points to the same item as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! + \fn bool JsonObject::iterator::operator!=(const iterator &other) const + \fn bool JsonObject::iterator::operator!=(const const_iterator &other) const + + Returns \c true if \a other points to a different item than this + iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! \fn JsonObject::iterator JsonObject::iterator::operator++() + + The prefix ++ operator, \c{++i}, advances the iterator to the + next item in the object and returns an iterator to the new current + item. + + Calling this function on JsonObject::end() leads to undefined results. + + \sa operator--() +*/ + +/*! \fn JsonObject::iterator JsonObject::iterator::operator++(int) + + \overload + + The postfix ++ operator, \c{i++}, advances the iterator to the + next item in the object and returns an iterator to the previously + current item. +*/ + +/*! \fn JsonObject::iterator JsonObject::iterator::operator--() + + The prefix -- operator, \c{--i}, makes the preceding item + current and returns an iterator pointing to the new current item. + + Calling this function on JsonObject::begin() leads to undefined + results. + + \sa operator++() +*/ + +/*! \fn JsonObject::iterator JsonObject::iterator::operator--(int) + + \overload + + The postfix -- operator, \c{i--}, makes the preceding item + current and returns an iterator pointing to the previously + current item. +*/ + +/*! \fn JsonObject::iterator JsonObject::iterator::operator+(int j) const + + Returns an iterator to the item at \a j positions forward from + this iterator. If \a j is negative, the iterator goes backward. + + \sa operator-() + +*/ + +/*! \fn JsonObject::iterator JsonObject::iterator::operator-(int j) const + + Returns an iterator to the item at \a j positions backward from + this iterator. If \a j is negative, the iterator goes forward. + + \sa operator+() +*/ + +/*! \fn JsonObject::iterator &JsonObject::iterator::operator+=(int j) + + Advances the iterator by \a j items. If \a j is negative, the + iterator goes backward. + + \sa operator-=(), operator+() +*/ + +/*! \fn JsonObject::iterator &JsonObject::iterator::operator-=(int j) + + Makes the iterator go back by \a j items. If \a j is negative, + the iterator goes forward. + + \sa operator+=(), operator-() +*/ + +/*! + \class JsonObject::const_iterator + \inmodule QtCore + \ingroup json + \since 5.0 + \brief The JsonObject::const_iterator class provides an STL-style const iterator for JsonObject. + + JsonObject::const_iterator allows you to iterate over a JsonObject. + If you want to modify the JsonObject as you iterate + over it, you must use JsonObject::iterator instead. It is generally + good practice to use JsonObject::const_iterator on a non-const JsonObject as + well, unless you need to change the JsonObject through the iterator. + Const iterators are slightly faster and improve code + readability. + + The default JsonObject::const_iterator constructor creates an + uninitialized iterator. You must initialize it using a JsonObject + function like JsonObject::constBegin(), JsonObject::constEnd(), or + JsonObject::find() before you can start iterating. + + Multiple iterators can be used on the same object. Existing iterators + will however become dangling if the object gets modified. + + \sa JsonObject::iterator, {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! \typedef JsonObject::const_iterator::difference_type + + \internal +*/ + +/*! \typedef JsonObject::const_iterator::iterator_category + + A synonym for \e {std::bidirectional_iterator_tag} indicating + this iterator is a bidirectional iterator. +*/ + +/*! \typedef JsonObject::const_iterator::reference + + \internal +*/ + +/*! \typedef JsonObject::const_iterator::value_type + + \internal +*/ + +/*! \fn JsonObject::const_iterator::const_iterator() + + Constructs an uninitialized iterator. + + Functions like key(), value(), and operator++() must not be + called on an uninitialized iterator. Use operator=() to assign a + value to it before using it. + + \sa JsonObject::constBegin(), JsonObject::constEnd() +*/ + +/*! \fn JsonObject::const_iterator::const_iterator(const JsonObject *obj, int index) + \internal +*/ + +/*! \fn JsonObject::const_iterator::const_iterator(const iterator &other) + + Constructs a copy of \a other. +*/ + +/*! \fn QString JsonObject::const_iterator::key() const + + Returns the current item's key. + + \sa value() +*/ + +/*! \fn JsonValue JsonObject::const_iterator::value() const + + Returns the current item's value. + + \sa key(), operator*() +*/ + +/*! \fn JsonValue JsonObject::const_iterator::operator*() const + + Returns the current item's value. + + Same as value(). + + \sa key() +*/ + +/*! \fn JsonValue *JsonObject::const_iterator::operator->() const + + Returns a pointer to the current item. +*/ + +/*! \fn bool JsonObject::const_iterator::operator==(const const_iterator &other) const + \fn bool JsonObject::const_iterator::operator==(const iterator &other) const + + Returns \c true if \a other points to the same item as this + iterator; otherwise returns \c false. + + \sa operator!=() +*/ + +/*! \fn bool JsonObject::const_iterator::operator!=(const const_iterator &other) const + \fn bool JsonObject::const_iterator::operator!=(const iterator &other) const + + Returns \c true if \a other points to a different item than this + iterator; otherwise returns \c false. + + \sa operator==() +*/ + +/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator++() + + The prefix ++ operator, \c{++i}, advances the iterator to the + next item in the object and returns an iterator to the new current + item. + + Calling this function on JsonObject::end() leads to undefined results. + + \sa operator--() +*/ + +/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator++(int) + + \overload + + The postfix ++ operator, \c{i++}, advances the iterator to the + next item in the object and returns an iterator to the previously + current item. +*/ + +/*! \fn JsonObject::const_iterator &JsonObject::const_iterator::operator--() + + The prefix -- operator, \c{--i}, makes the preceding item + current and returns an iterator pointing to the new current item. + + Calling this function on JsonObject::begin() leads to undefined + results. + + \sa operator++() +*/ + +/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator--(int) + + \overload + + The postfix -- operator, \c{i--}, makes the preceding item + current and returns an iterator pointing to the previously + current item. +*/ + +/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator+(int j) const + + Returns an iterator to the item at \a j positions forward from + this iterator. If \a j is negative, the iterator goes backward. + + This operation can be slow for large \a j values. + + \sa operator-() +*/ + +/*! \fn JsonObject::const_iterator JsonObject::const_iterator::operator-(int j) const + + Returns an iterator to the item at \a j positions backward from + this iterator. If \a j is negative, the iterator goes forward. + + This operation can be slow for large \a j values. + + \sa operator+() +*/ + +/*! \fn JsonObject::const_iterator &JsonObject::const_iterator::operator+=(int j) + + Advances the iterator by \a j items. If \a j is negative, the + iterator goes backward. + + This operation can be slow for large \a j values. + + \sa operator-=(), operator+() +*/ + +/*! \fn JsonObject::const_iterator &JsonObject::const_iterator::operator-=(int j) + + Makes the iterator go back by \a j items. If \a j is negative, + the iterator goes forward. + + This operation can be slow for large \a j values. + + \sa operator+=(), operator-() +*/ + + +/*! + \internal + */ +void JsonObject::detach(uint32_t reserve) +{ + if (!d) { + d = new Internal::Data(reserve, JsonValue::Object); + o = static_cast<Internal::Object *>(d->header->root()); + d->ref.ref(); + return; + } + if (reserve == 0 && d->ref.load() == 1) + return; + + Internal::Data *x = d->clone(o, reserve); + x->ref.ref(); + if (!d->ref.deref()) + delete d; + d = x; + o = static_cast<Internal::Object *>(d->header->root()); +} + +/*! + \internal + */ +void JsonObject::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + o = static_cast<Internal::Object *>(d->header->root()); +} + +/*! + \internal + */ +std::string JsonObject::keyAt(int i) const +{ + // assert(o && i >= 0 && i < (int)o->length); + + Internal::Entry *e = o->entryAt(i); + return e->key(); +} + +/*! + \internal + */ +JsonValue JsonObject::valueAt(int i) const +{ + if (!o || i < 0 || i >= (int)o->length) + return JsonValue(JsonValue::Undefined); + + Internal::Entry *e = o->entryAt(i); + return JsonValue(d, o, e->value); +} + +/*! + \internal + */ +void JsonObject::setValueAt(int i, const JsonValue &val) +{ + // assert(o && i >= 0 && i < (int)o->length); + + Internal::Entry *e = o->entryAt(i); + insert(e->key(), val); +} + + +/*! \class JsonDocument + \inmodule QtCore + \ingroup json + \ingroup shared + \reentrant + \since 5.0 + + \brief The JsonDocument class provides a way to read and write JSON documents. + + JsonDocument is a class that wraps a complete JSON document and can read and + write this document both from a UTF-8 encoded text based representation as well + as Qt's own binary format. + + A JSON document can be converted from its text-based representation to a JsonDocument + using JsonDocument::fromJson(). toJson() converts it back to text. The parser is very + fast and efficient and converts the JSON to the binary representation used by Qt. + + Validity of the parsed document can be queried with !isNull() + + A document can be queried as to whether it contains an array or an object using isArray() + and isObject(). The array or object contained in the document can be retrieved using + array() or object() and then read or manipulated. + + A document can also be created from a stored binary representation using fromBinaryData() or + fromRawData(). + + \sa {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! + * Constructs an empty and invalid document. + */ +JsonDocument::JsonDocument() + : d(0) +{ +} + +/*! + * Creates a JsonDocument from \a object. + */ +JsonDocument::JsonDocument(const JsonObject &object) + : d(0) +{ + setObject(object); +} + +/*! + * Constructs a JsonDocument from \a array. + */ +JsonDocument::JsonDocument(const JsonArray &array) + : d(0) +{ + setArray(array); +} + +/*! + \internal + */ +JsonDocument::JsonDocument(Internal::Data *data) + : d(data) +{ + // assert(d); + d->ref.ref(); +} + +/*! + Deletes the document. + + Binary data set with fromRawData is not freed. + */ +JsonDocument::~JsonDocument() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + * Creates a copy of the \a other document. + */ +JsonDocument::JsonDocument(const JsonDocument &other) +{ + d = other.d; + if (d) + d->ref.ref(); +} + +/*! + * Assigns the \a other document to this JsonDocument. + * Returns a reference to this object. + */ +JsonDocument &JsonDocument::operator=(const JsonDocument &other) +{ + if (d != other.d) { + if (d && !d->ref.deref()) + delete d; + d = other.d; + if (d) + d->ref.ref(); + } + + return *this; +} + +/*! \enum JsonDocument::DataValidation + + This value is used to tell JsonDocument whether to validate the binary data + when converting to a JsonDocument using fromBinaryData() or fromRawData(). + + \value Validate Validate the data before using it. This is the default. + \value BypassValidation Bypasses data validation. Only use if you received the + data from a trusted place and know it's valid, as using of invalid data can crash + the application. + */ + +/*! + Creates a JsonDocument that uses the first \a size bytes from + \a data. It assumes \a data contains a binary encoded JSON document. + The created document does not take ownership of \a data and the caller + has to guarantee that \a data will not be deleted or modified as long as + any JsonDocument, JsonObject or JsonArray still references the data. + + \a data has to be aligned to a 4 byte boundary. + + \a validation decides whether the data is checked for validity before being used. + By default the data is validated. If the \a data is not valid, the method returns + a null document. + + Returns a JsonDocument representing the data. + + \sa rawData(), fromBinaryData(), isNull(), DataValidation + */ +JsonDocument JsonDocument::fromRawData(const char *data, int size, DataValidation validation) +{ + if (std::uintptr_t(data) & 3) { + std::cerr <<"JsonDocument::fromRawData: data has to have 4 byte alignment\n"; + return JsonDocument(); + } + + Internal::Data *d = new Internal::Data((char *)data, size); + d->ownsData = false; + + if (validation != BypassValidation && !d->valid()) { + delete d; + return JsonDocument(); + } + + return JsonDocument(d); +} + +/*! + Returns the raw binary representation of the data + \a size will contain the size of the returned data. + + This method is useful to e.g. stream the JSON document + in it's binary form to a file. + */ +const char *JsonDocument::rawData(int *size) const +{ + if (!d) { + *size = 0; + return 0; + } + *size = d->alloc; + return d->rawData; +} + +/*! + Creates a JsonDocument from \a data. + + \a validation decides whether the data is checked for validity before being used. + By default the data is validated. If the \a data is not valid, the method returns + a null document. + + \sa toBinaryData(), fromRawData(), isNull(), DataValidation + */ +JsonDocument JsonDocument::fromBinaryData(const std::string &data, DataValidation validation) +{ + if (data.size() < (int)(sizeof(Internal::Header) + sizeof(Internal::Base))) + return JsonDocument(); + + Internal::Header h; + memcpy(&h, data.data(), sizeof(Internal::Header)); + Internal::Base root; + memcpy(&root, data.data() + sizeof(Internal::Header), sizeof(Internal::Base)); + + // do basic checks here, so we don't try to allocate more memory than we can. + if (h.tag != JsonDocument::BinaryFormatTag || h.version != 1u || + sizeof(Internal::Header) + root.size > (uint32_t)data.size()) + return JsonDocument(); + + const uint32_t size = sizeof(Internal::Header) + root.size; + char *raw = (char *)malloc(size); + if (!raw) + return JsonDocument(); + + memcpy(raw, data.data(), size); + Internal::Data *d = new Internal::Data(raw, size); + + if (validation != BypassValidation && !d->valid()) { + delete d; + return JsonDocument(); + } + + return JsonDocument(d); +} + +/*! + \enum JsonDocument::JsonFormat + + This value defines the format of the JSON byte array produced + when converting to a JsonDocument using toJson(). + + \value Indented Defines human readable output as follows: + \code + { + "Array": [ + true, + 999, + "string" + ], + "Key": "Value", + "null": null + } + \endcode + + \value Compact Defines a compact output as follows: + \code + {"Array":[true,999,"string"],"Key":"Value","null":null} + \endcode + */ + +/*! + Converts the JsonDocument to a UTF-8 encoded JSON document in the provided \a format. + + \sa fromJson(), JsonFormat + */ +#ifndef QT_JSON_READONLY +std::string JsonDocument::toJson(JsonFormat format) const +{ + std::string json; + + if (!d) + return json; + + if (d->header->root()->isArray()) + Internal::arrayToJson(static_cast<Internal::Array *>(d->header->root()), json, 0, (format == Compact)); + else + Internal::objectToJson(static_cast<Internal::Object *>(d->header->root()), json, 0, (format == Compact)); + + return json; +} +#endif + +/*! + Parses a UTF-8 encoded JSON document and creates a JsonDocument + from it. + + \a json contains the json document to be parsed. + + The optional \a error variable can be used to pass in a JsonParseError data + structure that will contain information about possible errors encountered during + parsing. + + \sa toJson(), JsonParseError + */ +JsonDocument JsonDocument::fromJson(const std::string &json, JsonParseError *error) +{ + Internal::Parser parser(json.data(), json.length()); + return parser.parse(error); +} + +/*! + Returns \c true if the document doesn't contain any data. + */ +bool JsonDocument::isEmpty() const +{ + if (!d) + return true; + + return false; +} + +/*! + Returns a binary representation of the document. + + The binary representation is also the native format used internally in Qt, + and is very efficient and fast to convert to and from. + + The binary format can be stored on disk and interchanged with other applications + or computers. fromBinaryData() can be used to convert it back into a + JSON document. + + \sa fromBinaryData() + */ +std::string JsonDocument::toBinaryData() const +{ + if (!d || !d->rawData) + return std::string(); + + return std::string(d->rawData, d->header->root()->size + sizeof(Internal::Header)); +} + +/*! + Returns \c true if the document contains an array. + + \sa array(), isObject() + */ +bool JsonDocument::isArray() const +{ + if (!d) + return false; + + Internal::Header *h = (Internal::Header *)d->rawData; + return h->root()->isArray(); +} + +/*! + Returns \c true if the document contains an object. + + \sa object(), isArray() + */ +bool JsonDocument::isObject() const +{ + if (!d) + return false; + + Internal::Header *h = (Internal::Header *)d->rawData; + return h->root()->isObject(); +} + +/*! + Returns the JsonObject contained in the document. + + Returns an empty object if the document contains an + array. + + \sa isObject(), array(), setObject() + */ +JsonObject JsonDocument::object() const +{ + if (d) { + Internal::Base *b = d->header->root(); + if (b->isObject()) + return JsonObject(d, static_cast<Internal::Object *>(b)); + } + return JsonObject(); +} + +/*! + Returns the JsonArray contained in the document. + + Returns an empty array if the document contains an + object. + + \sa isArray(), object(), setArray() + */ +JsonArray JsonDocument::array() const +{ + if (d) { + Internal::Base *b = d->header->root(); + if (b->isArray()) + return JsonArray(d, static_cast<Internal::Array *>(b)); + } + return JsonArray(); +} + +/*! + Sets \a object as the main object of this document. + + \sa setArray(), object() + */ +void JsonDocument::setObject(const JsonObject &object) +{ + if (d && !d->ref.deref()) + delete d; + + d = object.d; + + if (!d) { + d = new Internal::Data(0, JsonValue::Object); + } else if (d->compactionCounter || object.o != d->header->root()) { + JsonObject o(object); + if (d->compactionCounter) + o.compact(); + else + o.detach(); + d = o.d; + d->ref.ref(); + return; + } + d->ref.ref(); +} + +/*! + Sets \a array as the main object of this document. + + \sa setObject(), array() + */ +void JsonDocument::setArray(const JsonArray &array) +{ + if (d && !d->ref.deref()) + delete d; + + d = array.d; + + if (!d) { + d = new Internal::Data(0, JsonValue::Array); + } else if (d->compactionCounter || array.a != d->header->root()) { + JsonArray a(array); + if (d->compactionCounter) + a.compact(); + else + a.detach(); + d = a.d; + d->ref.ref(); + return; + } + d->ref.ref(); +} + +/*! + Returns \c true if the \a other document is equal to this document. + */ +bool JsonDocument::operator==(const JsonDocument &other) const +{ + if (d == other.d) + return true; + + if (!d || !other.d) + return false; + + if (d->header->root()->isArray() != other.d->header->root()->isArray()) + return false; + + if (d->header->root()->isObject()) + return JsonObject(d, static_cast<Internal::Object *>(d->header->root())) + == JsonObject(other.d, static_cast<Internal::Object *>(other.d->header->root())); + else + return JsonArray(d, static_cast<Internal::Array *>(d->header->root())) + == JsonArray(other.d, static_cast<Internal::Array *>(other.d->header->root())); +} + +/*! + \fn bool JsonDocument::operator!=(const JsonDocument &other) const + + returns \c true if \a other is not equal to this document + */ + +/*! + returns \c true if this document is null. + + Null documents are documents created through the default constructor. + + Documents created from UTF-8 encoded text or the binary format are + validated during parsing. If validation fails, the returned document + will also be null. + */ +bool JsonDocument::isNull() const +{ + return (d == 0); +} + + +static void objectContentToJson(const Object *o, std::string &json, int indent, bool compact); +static void arrayContentToJson(const Array *a, std::string &json, int indent, bool compact); + +static uint8_t hexdig(uint32_t u) +{ + return (u < 0xa ? '0' + u : 'a' + u - 0xa); +} + +static std::string escapedString(const std::string &in) +{ + std::string ba; + ba.reserve(in.length()); + + auto src = in.begin(); + auto end = in.end(); + + while (src != end) { + uint8_t u = (*src++); + if (u < 0x20 || u == 0x22 || u == 0x5c) { + ba.push_back('\\'); + switch (u) { + case 0x22: + ba.push_back('"'); + break; + case 0x5c: + ba.push_back('\\'); + break; + case 0x8: + ba.push_back('b'); + break; + case 0xc: + ba.push_back('f'); + break; + case 0xa: + ba.push_back('n'); + break; + case 0xd: + ba.push_back('r'); + break; + case 0x9: + ba.push_back('t'); + break; + default: + ba.push_back('u'); + ba.push_back('0'); + ba.push_back('0'); + ba.push_back(hexdig(u>>4)); + ba.push_back(hexdig(u & 0xf)); + } + } else { + ba.push_back(u); + } + } + + return ba; +} + +static void valueToJson(const Base *b, const Value &v, std::string &json, int indent, bool compact) +{ + JsonValue::Type type = (JsonValue::Type)(uint32_t)v.type; + switch (type) { + case JsonValue::Bool: + json += v.toBoolean() ? "true" : "false"; + break; + case JsonValue::Double: { + const double d = v.toDouble(b); + if (std::isfinite(d)) { + // +2 to format to ensure the expected precision + const int n = std::numeric_limits<double>::digits10 + 2; + char buf[30] = {0}; + sprintf(buf, "%.*g", n, d); + // Hack: + if (buf[0] == '-' && buf[1] == '0' && buf[2] == '\0') + json += "0"; + else + json += buf; + } else { + json += "null"; // +INF || -INF || NaN (see RFC4627#section2.4) + } + break; + } + case JsonValue::String: + json += '"'; + json += escapedString(v.toString(b)); + json += '"'; + break; + case JsonValue::Array: + json += compact ? "[" : "[\n"; + arrayContentToJson(static_cast<Array *>(v.base(b)), json, indent + (compact ? 0 : 1), compact); + json += std::string(4*indent, ' '); + json += ']'; + break; + case JsonValue::Object: + json += compact ? "{" : "{\n"; + objectContentToJson(static_cast<Object *>(v.base(b)), json, indent + (compact ? 0 : 1), compact); + json += std::string(4*indent, ' '); + json += '}'; + break; + case JsonValue::Null: + default: + json += "null"; + } +} + +static void arrayContentToJson(const Array *a, std::string &json, int indent, bool compact) +{ + if (!a || !a->length) + return; + + std::string indentString(4*indent, ' '); + + uint32_t i = 0; + while (1) { + json += indentString; + valueToJson(a, a->at(i), json, indent, compact); + + if (++i == a->length) { + if (!compact) + json += '\n'; + break; + } + + json += compact ? "," : ",\n"; + } +} + +static void objectContentToJson(const Object *o, std::string &json, int indent, bool compact) +{ + if (!o || !o->length) + return; + + std::string indentString(4*indent, ' '); + + uint32_t i = 0; + while (1) { + Entry *e = o->entryAt(i); + json += indentString; + json += '"'; + json += escapedString(e->key()); + json += compact ? "\":" : "\": "; + valueToJson(o, e->value, json, indent, compact); + + if (++i == o->length) { + if (!compact) + json += '\n'; + break; + } + + json += compact ? "," : ",\n"; + } +} + +namespace Internal { + +void objectToJson(const Object *o, std::string &json, int indent, bool compact) +{ + json.reserve(json.size() + (o ? (int)o->size : 16)); + json += compact ? "{" : "{\n"; + objectContentToJson(o, json, indent + (compact ? 0 : 1), compact); + json += std::string(4*indent, ' '); + json += compact ? "}" : "}\n"; +} + +void arrayToJson(const Array *a, std::string &json, int indent, bool compact) +{ + json.reserve(json.size() + (a ? (int)a->size : 16)); + json += compact ? "[" : "[\n"; + arrayContentToJson(a, json, indent + (compact ? 0 : 1), compact); + json += std::string(4*indent, ' '); + json += compact ? "]" : "]\n"; +} + +} + + + +/*! + \class JsonParseError + \inmodule QtCore + \ingroup json + \ingroup shared + \reentrant + \since 5.0 + + \brief The JsonParseError class is used to report errors during JSON parsing. + + \sa {JSON Support in Qt}, {JSON Save Game Example} +*/ + +/*! + \enum JsonParseError::ParseError + + This enum describes the type of error that occurred during the parsing of a JSON document. + + \value NoError No error occurred + \value UnterminatedObject An object is not correctly terminated with a closing curly bracket + \value MissingNameSeparator A comma separating different items is missing + \value UnterminatedArray The array is not correctly terminated with a closing square bracket + \value MissingValueSeparator A colon separating keys from values inside objects is missing + \value IllegalValue The value is illegal + \value TerminationByNumber The input stream ended while parsing a number + \value IllegalNumber The number is not well formed + \value IllegalEscapeSequence An illegal escape sequence occurred in the input + \value IllegalUTF8String An illegal UTF8 sequence occurred in the input + \value UnterminatedString A string wasn't terminated with a quote + \value MissingObject An object was expected but couldn't be found + \value DeepNesting The JSON document is too deeply nested for the parser to parse it + \value DocumentTooLarge The JSON document is too large for the parser to parse it + \value GarbageAtEnd The parsed document contains additional garbage characters at the end + +*/ + +/*! + \variable JsonParseError::error + + Contains the type of the parse error. Is equal to JsonParseError::NoError if the document + was parsed correctly. + + \sa ParseError, errorString() +*/ + + +/*! + \variable JsonParseError::offset + + Contains the offset in the input string where the parse error occurred. + + \sa error, errorString() +*/ + +using namespace Internal; + +Parser::Parser(const char *json, int length) + : head(json), json(json), data(0), dataLength(0), current(0), nestingLevel(0), lastError(JsonParseError::NoError) +{ + 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 +}; + +void Parser::eatBOM() +{ + // eat UTF-8 byte order mark + if (end - json > 3 + && (unsigned char)json[0] == 0xef + && (unsigned char)json[1] == 0xbb + && (unsigned char)json[2] == 0xbf) + json += 3; +} + +bool Parser::eatSpace() +{ + while (json < end) { + if (*json > Space) + break; + if (*json != Space && + *json != Tab && + *json != LineFeed && + *json != Return) + break; + ++json; + } + return (json < end); +} + +char Parser::nextToken() +{ + if (!eatSpace()) + return 0; + char token = *json++; + switch (token) { + case BeginArray: + case BeginObject: + case NameSeparator: + case ValueSeparator: + case EndArray: + case EndObject: + eatSpace(); + case Quote: + break; + default: + token = 0; + break; + } + return token; +} + +/* + JSON-text = object / array +*/ +JsonDocument Parser::parse(JsonParseError *error) +{ +#ifdef PARSER_DEBUG + indent = 0; + std::cerr << ">>>>> parser begin"; +#endif + // allocate some space + dataLength = std::max(end - json, std::ptrdiff_t(256)); + data = (char *)malloc(dataLength); + + // fill in Header data + Header *h = (Header *)data; + h->tag = JsonDocument::BinaryFormatTag; + h->version = 1u; + + current = sizeof(Header); + + eatBOM(); + char token = nextToken(); + + DEBUG << std::hex << (uint32_t)token; + if (token == BeginArray) { + if (!parseArray()) + goto error; + } else if (token == BeginObject) { + if (!parseObject()) + goto error; + } else { + lastError = JsonParseError::IllegalValue; + goto error; + } + + eatSpace(); + if (json < end) { + lastError = JsonParseError::GarbageAtEnd; + goto error; + } + + END; + { + if (error) { + error->offset = 0; + error->error = JsonParseError::NoError; + } + Data *d = new Data(data, current); + return JsonDocument(d); + } + +error: +#ifdef PARSER_DEBUG + std::cerr << ">>>>> parser error"; +#endif + if (error) { + error->offset = json - head; + error->error = lastError; + } + free(data); + return JsonDocument(); +} + + +void Parser::ParsedObject::insert(uint32_t offset) +{ + const Entry *newEntry = reinterpret_cast<const Entry *>(parser->data + objectPosition + offset); + size_t min = 0; + size_t n = offsets.size(); + while (n > 0) { + int half = n >> 1; + int middle = min + half; + if (*entryAt(middle) >= *newEntry) { + n = half; + } else { + min = middle + 1; + n -= half + 1; + } + } + if (min < offsets.size() && *entryAt(min) == *newEntry) { + offsets[min] = offset; + } else { + offsets.insert(offsets.begin() + min, offset); + } +} + +/* + object = begin-object [ member *( value-separator member ) ] + end-object +*/ + +bool Parser::parseObject() +{ + if (++nestingLevel > nestingLimit) { + lastError = JsonParseError::DeepNesting; + return false; + } + + int objectOffset = reserveSpace(sizeof(Object)); + BEGIN << "parseObject pos=" << objectOffset << current << json; + + ParsedObject parsedObject(this, objectOffset); + + char token = nextToken(); + while (token == Quote) { + int off = current - objectOffset; + if (!parseMember(objectOffset)) + return false; + parsedObject.insert(off); + token = nextToken(); + if (token != ValueSeparator) + break; + token = nextToken(); + if (token == EndObject) { + lastError = JsonParseError::MissingObject; + return false; + } + } + + DEBUG << "end token=" << token; + if (token != EndObject) { + lastError = JsonParseError::UnterminatedObject; + return false; + } + + DEBUG << "numEntries" << parsedObject.offsets.size(); + int table = objectOffset; + // finalize the object + if (parsedObject.offsets.size()) { + int tableSize = parsedObject.offsets.size()*sizeof(uint32_t); + table = reserveSpace(tableSize); + memcpy(data + table, &*parsedObject.offsets.begin(), tableSize); + } + + Object *o = (Object *)(data + objectOffset); + o->tableOffset = table - objectOffset; + o->size = current - objectOffset; + o->is_object = true; + o->length = parsedObject.offsets.size(); + + DEBUG << "current=" << current; + END; + + --nestingLevel; + return true; +} + +/* + member = string name-separator value +*/ +bool Parser::parseMember(int baseOffset) +{ + int entryOffset = reserveSpace(sizeof(Entry)); + BEGIN << "parseMember pos=" << entryOffset; + + if (!parseString()) + return false; + char token = nextToken(); + if (token != NameSeparator) { + lastError = JsonParseError::MissingNameSeparator; + return false; + } + Value val; + if (!parseValue(&val, baseOffset)) + return false; + + // finalize the entry + Entry *e = (Entry *)(data + entryOffset); + e->value = val; + + END; + return true; +} + +/* + array = begin-array [ value *( value-separator value ) ] end-array +*/ +bool Parser::parseArray() +{ + BEGIN << "parseArray"; + + if (++nestingLevel > nestingLimit) { + lastError = JsonParseError::DeepNesting; + return false; + } + + int arrayOffset = reserveSpace(sizeof(Array)); + + std::vector<Value> values; + values.reserve(64); + + if (!eatSpace()) { + lastError = JsonParseError::UnterminatedArray; + return false; + } + if (*json == EndArray) { + nextToken(); + } else { + while (1) { + Value val; + if (!parseValue(&val, arrayOffset)) + return false; + values.push_back(val); + char token = nextToken(); + if (token == EndArray) + break; + else if (token != ValueSeparator) { + if (!eatSpace()) + lastError = JsonParseError::UnterminatedArray; + else + lastError = JsonParseError::MissingValueSeparator; + 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.data(), tableSize); + } + + Array *a = (Array *)(data + arrayOffset); + a->tableOffset = table - arrayOffset; + a->size = current - arrayOffset; + a->is_object = false; + a->length = values.size(); + + DEBUG << "current=" << current; + END; + + --nestingLevel; + return true; +} + +/* +value = false / null / true / object / array / number / string + +*/ + +bool Parser::parseValue(Value *val, int baseOffset) +{ + BEGIN << "parse Value" << json; + val->_dummy = 0; + + switch (*json++) { + case 'n': + if (end - json < 4) { + lastError = JsonParseError::IllegalValue; + return false; + } + if (*json++ == 'u' && + *json++ == 'l' && + *json++ == 'l') { + val->type = JsonValue::Null; + DEBUG << "value: null"; + END; + return true; + } + lastError = JsonParseError::IllegalValue; + return false; + case 't': + if (end - json < 4) { + lastError = JsonParseError::IllegalValue; + return false; + } + if (*json++ == 'r' && + *json++ == 'u' && + *json++ == 'e') { + val->type = JsonValue::Bool; + val->value = true; + DEBUG << "value: true"; + END; + return true; + } + lastError = JsonParseError::IllegalValue; + return false; + case 'f': + if (end - json < 5) { + lastError = JsonParseError::IllegalValue; + return false; + } + if (*json++ == 'a' && + *json++ == 'l' && + *json++ == 's' && + *json++ == 'e') { + val->type = JsonValue::Bool; + val->value = false; + DEBUG << "value: false"; + END; + return true; + } + lastError = JsonParseError::IllegalValue; + return false; + case Quote: { + val->type = JsonValue::String; + if (current - baseOffset >= Value::MaxSize) { + lastError = JsonParseError::DocumentTooLarge; + return false; + } + val->value = current - baseOffset; + if (!parseString()) + return false; + val->intValue = false; + DEBUG << "value: string"; + END; + return true; + } + case BeginArray: + val->type = JsonValue::Array; + if (current - baseOffset >= Value::MaxSize) { + lastError = JsonParseError::DocumentTooLarge; + return false; + } + val->value = current - baseOffset; + if (!parseArray()) + return false; + DEBUG << "value: array"; + END; + return true; + case BeginObject: + val->type = JsonValue::Object; + if (current - baseOffset >= Value::MaxSize) { + lastError = JsonParseError::DocumentTooLarge; + return false; + } + val->value = current - baseOffset; + if (!parseObject()) + return false; + DEBUG << "value: object"; + END; + return true; + case EndArray: + lastError = JsonParseError::MissingObject; + return false; + 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 Parser::parseNumber(Value *val, int baseOffset) +{ + BEGIN << "parseNumber" << json; + val->type = JsonValue::Double; + + 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) { + lastError = JsonParseError::TerminationByNumber; + return false; + } + + if (isInt) { + char *endptr = const_cast<char *>(json); + long long int n = strtoll(start, &endptr, 0); + if (endptr != start && n < (1<<25) && n > -(1<<25)) { + val->int_value = n; + val->intValue = true; + END; + return true; + } + } + + char *endptr = const_cast<char *>(json); + double d = strtod(start, &endptr); + + if (start == endptr || isinf(d)) { + lastError = JsonParseError::IllegalNumber; + return false; + } + + int pos = reserveSpace(sizeof(double)); + memcpy(data + pos, &d, sizeof(double)); + if (current - baseOffset >= Value::MaxSize) { + lastError = JsonParseError::DocumentTooLarge; + return false; + } + val->value = pos - baseOffset; + val->intValue = 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 bool addHexDigit(char digit, uint32_t *result) +{ + *result <<= 4; + if (digit >= '0' && digit <= '9') + *result |= (digit - '0'); + else if (digit >= 'a' && digit <= 'f') + *result |= (digit - 'a') + 10; + else if (digit >= 'A' && digit <= 'F') + *result |= (digit - 'A') + 10; + else + return false; + return true; +} + +static bool scanEscapeSequence(const char *&json, const char *end, std::string *out) +{ + ++json; + if (json >= end) + return false; + + DEBUG << "scan escape" << (char)*json; + char escaped = *json++; + switch (escaped) { + case '"': + out->push_back('"'); break; + case '\\': + out->push_back('\\'); break; + case '/': + out->push_back('/'); break; + case 'b': + out->push_back(0x8); break; + case 'f': + out->push_back(0xc); break; + case 'n': + out->push_back(0xa); break; + case 'r': + out->push_back(0xd); break; + case 't': + out->push_back(0x9); break; + case 'u': { + uint32_t c = 0; + if (json > end - 4) + return false; + for (int i = 0; i < 4; ++i) { + if (!addHexDigit(*json, &c)) + return false; + ++json; + } + if (c < 0x80) { + out->push_back(c); + return true; + } + if (c < 0x800) { + out->push_back(192 + c / 64); + out->push_back(128 + c % 64); + return true; + } + if (c - 0xd800u < 0x800) { + return false; + } + if (c < 0x10000) { + out->push_back(224 + c / 4096); + out->push_back(128 + c / 64 % 64); + out->push_back(128 + c % 64); + return true; + } + if (c < 0x110000) { + out->push_back(240 + c / 262144); + out->push_back(128 + c / 4096 % 64); + out->push_back(128 + c / 64 % 64); + out->push_back(128 + c % 64); + return true; + } + return false; + } + default: + // this is not as strict as one could be, but allows for more Json files + // to be parsed correctly. + out->push_back(escaped); + return true; + } + return true; +} + +bool Parser::parseString() +{ + const char *start = json; + const int outStart = current; + int stringPos = reserveSpace(2); + + json = start; + current = outStart + sizeof(int); + +// const size_t insize = end - json; + std::string out; + out.reserve(100); + + while (json < end) { + if (*json == '"') + break; + else if (*json == '\\') { + if (!scanEscapeSequence(json, end, &out)) { + lastError = JsonParseError::IllegalEscapeSequence; + return false; + } + } else { + out.push_back(*json++); + } + } + ++json; + + const int pos = reserveSpace(out.size()); + memcpy(data + pos, out.data(), out.size()); + + if (json >= end) { + lastError = JsonParseError::UnterminatedString; + return false; + } + + // write string length and padding. + *(int *)(data + stringPos) = out.size(); + reserveSpace((4 - current) & 3); + + END; + return true; +} + +namespace Internal { + +static const Base emptyArray = { sizeof(Base), { 0 }, 0 }; +static const Base emptyObject = { sizeof(Base), { 0 }, 0 }; + + +void Data::compact() +{ + // assert(sizeof(Value) == sizeof(offset)); + + if (!compactionCounter) + return; + + Base *base = header->root(); + int reserve = 0; + if (base->is_object) { + Object *o = static_cast<Object *>(base); + for (int i = 0; i < (int)o->length; ++i) + reserve += o->entryAt(i)->usedStorage(o); + } else { + Array *a = static_cast<Array *>(base); + for (int i = 0; i < (int)a->length; ++i) + reserve += (*a)[i].usedStorage(a); + } + + int size = sizeof(Base) + reserve + base->length*sizeof(offset); + int alloc = sizeof(Header) + size; + Header *h = (Header *) malloc(alloc); + h->tag = JsonDocument::BinaryFormatTag; + h->version = 1; + Base *b = h->root(); + b->size = size; + b->is_object = header->root()->is_object; + b->length = base->length; + b->tableOffset = reserve + sizeof(Array); + + int offset = sizeof(Base); + if (b->is_object) { + Object *o = static_cast<Object *>(base); + Object *no = static_cast<Object *>(b); + + for (int i = 0; i < (int)o->length; ++i) { + no->table()[i] = offset; + + const Entry *e = o->entryAt(i); + Entry *ne = no->entryAt(i); + int s = e->size(); + memcpy(ne, e, s); + offset += s; + int dataSize = e->value.usedStorage(o); + if (dataSize) { + memcpy((char *)no + offset, e->value.data(o), dataSize); + ne->value.value = offset; + offset += dataSize; + } + } + } else { + Array *a = static_cast<Array *>(base); + Array *na = static_cast<Array *>(b); + + for (int i = 0; i < (int)a->length; ++i) { + const Value &v = (*a)[i]; + Value &nv = (*na)[i]; + nv = v; + int dataSize = v.usedStorage(a); + if (dataSize) { + memcpy((char *)na + offset, v.data(a), dataSize); + nv.value = offset; + offset += dataSize; + } + } + } + // assert(offset == (int)b->tableOffset); + + free(header); + header = h; + this->alloc = alloc; + compactionCounter = 0; +} + +bool Data::valid() const +{ + if (header->tag != JsonDocument::BinaryFormatTag || header->version != 1u) + return false; + + bool res = false; + if (header->root()->is_object) + res = static_cast<Object *>(header->root())->isValid(); + else + res = static_cast<Array *>(header->root())->isValid(); + + return res; +} + + +int Base::reserveSpace(uint32_t dataSize, int posInTable, uint32_t numItems, bool replace) +{ + // assert(posInTable >= 0 && posInTable <= (int)length); + if (size + dataSize >= Value::MaxSize) { + fprintf(stderr, "Json: Document too large to store in data structure %d %d %d\n", (uint32_t)size, dataSize, Value::MaxSize); + return 0; + } + + offset off = tableOffset; + // move table to new position + if (replace) { + memmove((char *)(table()) + dataSize, table(), length*sizeof(offset)); + } else { + memmove((char *)(table() + posInTable + numItems) + dataSize, table() + posInTable, (length - posInTable)*sizeof(offset)); + memmove((char *)(table()) + dataSize, table(), posInTable*sizeof(offset)); + } + tableOffset += dataSize; + for (int i = 0; i < (int)numItems; ++i) + table()[posInTable + i] = off; + size += dataSize; + if (!replace) { + length += numItems; + size += numItems * sizeof(offset); + } + return off; +} + +void Base::removeItems(int pos, int numItems) +{ + // assert(pos >= 0 && pos <= (int)length); + if (pos + numItems < (int)length) + memmove(table() + pos, table() + pos + numItems, (length - pos - numItems)*sizeof(offset)); + length -= numItems; +} + +int Object::indexOf(const std::string &key, bool *exists) +{ + int min = 0; + int n = length; + while (n > 0) { + int half = n >> 1; + int middle = min + half; + if (*entryAt(middle) >= key) { + n = half; + } else { + min = middle + 1; + n -= half + 1; + } + } + if (min < (int)length && *entryAt(min) == key) { + *exists = true; + return min; + } + *exists = false; + return min; +} + +bool Object::isValid() const +{ + if (tableOffset + length*sizeof(offset) > size) + return false; + + std::string lastKey; + for (uint32_t i = 0; i < length; ++i) { + offset entryOffset = table()[i]; + if (entryOffset + sizeof(Entry) >= tableOffset) + return false; + Entry *e = entryAt(i); + int s = e->size(); + if (table()[i] + s > tableOffset) + return false; + std::string key = e->key(); + if (key < lastKey) + return false; + if (!e->value.isValid(this)) + return false; + lastKey = key; + } + return true; +} + +bool Array::isValid() const +{ + if (tableOffset + length*sizeof(offset) > size) + return false; + + for (uint32_t i = 0; i < length; ++i) { + if (!at(i).isValid(this)) + return false; + } + return true; +} + + +bool Entry::operator==(const std::string &key) const +{ + return shallowKey() == key; +} + +bool Entry::operator==(const Entry &other) const +{ + return shallowKey() == other.shallowKey(); +} + +bool Entry::operator>=(const Entry &other) const +{ + return shallowKey() >= other.shallowKey(); +} + + +int Value::usedStorage(const Base *b) const +{ + int s = 0; + switch (type) { + case JsonValue::Double: + if (intValue) + break; + s = sizeof(double); + break; + case JsonValue::String: { + char *d = data(b); + s = sizeof(int) + (*(int *)d); + break; + } + case JsonValue::Array: + case JsonValue::Object: + s = base(b)->size; + break; + case JsonValue::Null: + case JsonValue::Bool: + default: + break; + } + return alignedSize(s); +} + +bool Value::isValid(const Base *b) const +{ + int offset = 0; + switch (type) { + case JsonValue::Double: + if (intValue) + break; + // fall through + case JsonValue::String: + case JsonValue::Array: + case JsonValue::Object: + offset = value; + break; + case JsonValue::Null: + case JsonValue::Bool: + default: + break; + } + + if (!offset) + return true; + if (offset + sizeof(uint32_t) > b->tableOffset) + return false; + + int s = usedStorage(b); + if (!s) + return true; + if (s < 0 || offset + s > (int)b->tableOffset) + return false; + if (type == JsonValue::Array) + return static_cast<Array *>(base(b))->isValid(); + if (type == JsonValue::Object) + return static_cast<Object *>(base(b))->isValid(); + return true; +} + +/*! + \internal + */ +int Value::requiredStorage(JsonValue &v, bool *compressed) +{ + *compressed = false; + switch (v.t) { + case JsonValue::Double: + if (Internal::compressedNumber(v.dbl) != INT_MAX) { + *compressed = true; + return 0; + } + return sizeof(double); + case JsonValue::String: { + std::string s = v.toString().data(); + *compressed = false; + return Internal::qStringSize(s); + } + case JsonValue::Array: + case JsonValue::Object: + if (v.d && v.d->compactionCounter) { + v.detach(); + v.d->compact(); + v.base = static_cast<Internal::Base *>(v.d->header->root()); + } + return v.base ? v.base->size : sizeof(Internal::Base); + case JsonValue::Undefined: + case JsonValue::Null: + case JsonValue::Bool: + break; + } + return 0; +} + +/*! + \internal + */ +uint32_t Value::valueToStore(const JsonValue &v, uint32_t offset) +{ + switch (v.t) { + case JsonValue::Undefined: + case JsonValue::Null: + break; + case JsonValue::Bool: + return v.b; + case JsonValue::Double: { + int c = Internal::compressedNumber(v.dbl); + if (c != INT_MAX) + return c; + } + // fall through + case JsonValue::String: + case JsonValue::Array: + case JsonValue::Object: + return offset; + } + return 0; +} + +/*! + \internal + */ + +void Value::copyData(const JsonValue &v, char *dest, bool compressed) +{ + switch (v.t) { + case JsonValue::Double: + if (!compressed) + memcpy(dest, &v.ui, 8); + break; + case JsonValue::String: { + std::string str = v.toString(); + Internal::copyString(dest, str); + break; + } + case JsonValue::Array: + case JsonValue::Object: { + const Internal::Base *b = v.base; + if (!b) + b = (v.t == JsonValue::Array ? &emptyArray : &emptyObject); + memcpy(dest, b, b->size); + break; + } + default: + break; + } +} + +} // namespace Internal +} // namespace Json diff --git a/src/shared/json/json.h b/src/shared/json/json.h new file mode 100644 index 00000000000..7bc945cd3e9 --- /dev/null +++ b/src/shared/json/json.h @@ -0,0 +1,582 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef JSONVALUE_H +#define JSONVALUE_H + +#include <initializer_list> +#include <string> +#include <vector> + +namespace Json { + +class JsonArray; +class JsonObject; + +namespace Internal { +class Data; +class Base; +class Object; +class Header; +class Array; +class Value; +class Entry; +class SharedString; +class Parser; +} + +class JsonValue +{ +public: + enum Type { + Null = 0x0, + Bool = 0x1, + Double = 0x2, + String = 0x3, + Array = 0x4, + Object = 0x5, + Undefined = 0x80 + }; + + JsonValue(Type = Null); + JsonValue(bool b); + JsonValue(double n); + JsonValue(int n); + JsonValue(int64_t n); + JsonValue(const std::string &s); + JsonValue(const char *s); + JsonValue(const JsonArray &a); + JsonValue(const JsonObject &o); + + ~JsonValue(); + + JsonValue(const JsonValue &other); + JsonValue &operator =(const JsonValue &other); + + Type type() const { return t; } + bool isNull() const { return t == Null; } + bool isBool() const { return t == Bool; } + bool isDouble() const { return t == Double; } + bool isString() const { return t == String; } + bool isArray() const { return t == Array; } + bool isObject() const { return t == Object; } + bool isUndefined() const { return t == Undefined; } + + bool toBool(bool defaultValue = false) const; + int toInt(int defaultValue = 0) const; + double toDouble(double defaultValue = 0) const; + std::string toString(const std::string &defaultValue = std::string()) const; + JsonArray toArray() const; + JsonArray toArray(const JsonArray &defaultValue) const; + JsonObject toObject() const; + JsonObject toObject(const JsonObject &defaultValue) const; + + bool operator==(const JsonValue &other) const; + bool operator!=(const JsonValue &other) const; + +private: + // avoid implicit conversions from char * to bool + JsonValue(const void *) : t(Null) {} + friend class Internal::Value; + friend class JsonArray; + friend class JsonObject; + + JsonValue(Internal::Data *d, Internal::Base *b, const Internal::Value& v); + + void detach(); + + union { + uint64_t ui; + bool b; + double dbl; + Internal::SharedString *stringData; + Internal::Base *base; + }; + Internal::Data *d; // needed for Objects and Arrays + Type t; +}; + +class JsonValueRef +{ +public: + JsonValueRef(JsonArray *array, int idx) + : a(array), is_object(false), index(idx) {} + JsonValueRef(JsonObject *object, int idx) + : o(object), is_object(true), index(idx) {} + + operator JsonValue() const { return toValue(); } + JsonValueRef &operator=(const JsonValue &val); + JsonValueRef &operator=(const JsonValueRef &val); + + JsonValue::Type type() const { return toValue().type(); } + bool isNull() const { return type() == JsonValue::Null; } + bool isBool() const { return type() == JsonValue::Bool; } + bool isDouble() const { return type() == JsonValue::Double; } + bool isString() const { return type() == JsonValue::String; } + bool isArray() const { return type() == JsonValue::Array; } + bool isObject() const { return type() == JsonValue::Object; } + bool isUndefined() const { return type() == JsonValue::Undefined; } + + std::string toString() const { return toValue().toString(); } + JsonArray toArray() const; + JsonObject toObject() const; + + bool toBool(bool defaultValue = false) const { return toValue().toBool(defaultValue); } + int toInt(int defaultValue = 0) const { return toValue().toInt(defaultValue); } + double toDouble(double defaultValue = 0) const { return toValue().toDouble(defaultValue); } + std::string toString(const std::string &defaultValue) const { return toValue().toString(defaultValue); } + + bool operator==(const JsonValue &other) const { return toValue() == other; } + bool operator!=(const JsonValue &other) const { return toValue() != other; } + +private: + JsonValue toValue() const; + + union { + JsonArray *a; + JsonObject *o; + }; + uint32_t is_object : 1; + uint32_t index : 31; +}; + +class JsonValuePtr +{ + JsonValue value; +public: + explicit JsonValuePtr(const JsonValue& val) + : value(val) {} + + JsonValue& operator*() { return value; } + JsonValue* operator->() { return &value; } +}; + +class JsonValueRefPtr +{ + JsonValueRef valueRef; +public: + JsonValueRefPtr(JsonArray *array, int idx) + : valueRef(array, idx) {} + JsonValueRefPtr(JsonObject *object, int idx) + : valueRef(object, idx) {} + + JsonValueRef& operator*() { return valueRef; } + JsonValueRef* operator->() { return &valueRef; } +}; + + + +class JsonArray +{ +public: + JsonArray(); + JsonArray(std::initializer_list<JsonValue> args); + + ~JsonArray(); + + JsonArray(const JsonArray &other); + JsonArray &operator=(const JsonArray &other); + + int size() const; + int count() const { return size(); } + + bool isEmpty() const; + JsonValue at(int i) const; + JsonValue first() const; + JsonValue last() const; + + void prepend(const JsonValue &value); + void append(const JsonValue &value); + void removeAt(int i); + JsonValue takeAt(int i); + void removeFirst() { removeAt(0); } + void removeLast() { removeAt(size() - 1); } + + void insert(int i, const JsonValue &value); + void replace(int i, const JsonValue &value); + + bool contains(const JsonValue &element) const; + JsonValueRef operator[](int i); + JsonValue operator[](int i) const; + + bool operator==(const JsonArray &other) const; + bool operator!=(const JsonArray &other) const; + + class const_iterator; + + class iterator { + public: + JsonArray *a; + int i; + typedef std::random_access_iterator_tag iterator_category; + typedef int difference_type; + typedef JsonValue value_type; + typedef JsonValueRef reference; + typedef JsonValueRefPtr pointer; + + iterator() : a(nullptr), i(0) { } + explicit iterator(JsonArray *array, int index) : a(array), i(index) { } + + JsonValueRef operator*() const { return JsonValueRef(a, i); } + JsonValueRefPtr operator->() const { return JsonValueRefPtr(a, i); } + JsonValueRef operator[](int j) const { return JsonValueRef(a, i + j); } + + bool operator==(const iterator &o) const { return i == o.i; } + bool operator!=(const iterator &o) const { return i != o.i; } + bool operator<(const iterator& other) const { return i < other.i; } + bool operator<=(const iterator& other) const { return i <= other.i; } + bool operator>(const iterator& other) const { return i > other.i; } + bool operator>=(const iterator& other) const { return i >= other.i; } + bool operator==(const const_iterator &o) const { return i == o.i; } + bool operator!=(const const_iterator &o) const { return i != o.i; } + bool operator<(const const_iterator& other) const { return i < other.i; } + bool operator<=(const const_iterator& other) const { return i <= other.i; } + bool operator>(const const_iterator& other) const { return i > other.i; } + bool operator>=(const const_iterator& other) const { return i >= other.i; } + iterator &operator++() { ++i; return *this; } + iterator operator++(int) { iterator n = *this; ++i; return n; } + iterator &operator--() { i--; return *this; } + iterator operator--(int) { iterator n = *this; i--; return n; } + iterator &operator+=(int j) { i+=j; return *this; } + iterator &operator-=(int j) { i-=j; return *this; } + iterator operator+(int j) const { return iterator(a, i+j); } + iterator operator-(int j) const { return iterator(a, i-j); } + int operator-(iterator j) const { return i - j.i; } + }; + friend class iterator; + + class const_iterator { + public: + const JsonArray *a; + int i; + typedef std::random_access_iterator_tag iterator_category; + typedef std::ptrdiff_t difference_type; + typedef JsonValue value_type; + typedef JsonValue reference; + typedef JsonValuePtr pointer; + + const_iterator() : a(nullptr), i(0) { } + explicit const_iterator(const JsonArray *array, int index) : a(array), i(index) { } + const_iterator(const iterator &o) : a(o.a), i(o.i) {} + + JsonValue operator*() const { return a->at(i); } + JsonValuePtr operator->() const { return JsonValuePtr(a->at(i)); } + JsonValue operator[](int j) const { return a->at(i+j); } + bool operator==(const const_iterator &o) const { return i == o.i; } + bool operator!=(const const_iterator &o) const { return i != o.i; } + bool operator<(const const_iterator& other) const { return i < other.i; } + bool operator<=(const const_iterator& other) const { return i <= other.i; } + bool operator>(const const_iterator& other) const { return i > other.i; } + bool operator>=(const const_iterator& other) const { return i >= other.i; } + const_iterator &operator++() { ++i; return *this; } + const_iterator operator++(int) { const_iterator n = *this; ++i; return n; } + const_iterator &operator--() { i--; return *this; } + const_iterator operator--(int) { const_iterator n = *this; i--; return n; } + const_iterator &operator+=(int j) { i+=j; return *this; } + const_iterator &operator-=(int j) { i-=j; return *this; } + const_iterator operator+(int j) const { return const_iterator(a, i+j); } + const_iterator operator-(int j) const { return const_iterator(a, i-j); } + int operator-(const_iterator j) const { return i - j.i; } + }; + friend class const_iterator; + + // stl style + iterator begin() { detach(); return iterator(this, 0); } + const_iterator begin() const { return const_iterator(this, 0); } + const_iterator constBegin() const { return const_iterator(this, 0); } + iterator end() { detach(); return iterator(this, size()); } + const_iterator end() const { return const_iterator(this, size()); } + const_iterator constEnd() const { return const_iterator(this, size()); } + iterator insert(iterator before, const JsonValue &value) { insert(before.i, value); return before; } + iterator erase(iterator it) { removeAt(it.i); return it; } + + void push_back(const JsonValue &t) { append(t); } + void push_front(const JsonValue &t) { prepend(t); } + void pop_front() { removeFirst(); } + void pop_back() { removeLast(); } + bool empty() const { return isEmpty(); } + typedef int size_type; + typedef JsonValue value_type; + typedef value_type *pointer; + typedef const value_type *const_pointer; + typedef JsonValueRef reference; + typedef JsonValue const_reference; + typedef int difference_type; + +private: + friend class Internal::Data; + friend class JsonValue; + friend class JsonDocument; + + JsonArray(Internal::Data *data, Internal::Array *array); + void compact(); + void detach(uint32_t reserve = 0); + + Internal::Data *d; + Internal::Array *a; +}; + + +class JsonObject +{ +public: + JsonObject(); + JsonObject(std::initializer_list<std::pair<std::string, JsonValue> > args); + ~JsonObject(); + + JsonObject(const JsonObject &other); + JsonObject &operator =(const JsonObject &other); + + typedef std::vector<std::string> Keys; + Keys keys() const; + int size() const; + int count() const { return size(); } + int length() const { return size(); } + bool isEmpty() const; + + JsonValue value(const std::string &key) const; + JsonValue operator[] (const std::string &key) const; + JsonValueRef operator[] (const std::string &key); + + void remove(const std::string &key); + JsonValue take(const std::string &key); + bool contains(const std::string &key) const; + + bool operator==(const JsonObject &other) const; + bool operator!=(const JsonObject &other) const; + + class const_iterator; + + class iterator + { + friend class const_iterator; + friend class JsonObject; + JsonObject *o; + int i; + + public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef int difference_type; + typedef JsonValue value_type; + typedef JsonValueRef reference; + + constexpr iterator() : o(nullptr), i(0) {} + constexpr iterator(JsonObject *obj, int index) : o(obj), i(index) {} + + std::string key() const { return o->keyAt(i); } + JsonValueRef value() const { return JsonValueRef(o, i); } + JsonValueRef operator*() const { return JsonValueRef(o, i); } + JsonValueRefPtr operator->() const { return JsonValueRefPtr(o, i); } + bool operator==(const iterator &other) const { return i == other.i; } + bool operator!=(const iterator &other) const { return i != other.i; } + + iterator &operator++() { ++i; return *this; } + iterator operator++(int) { iterator r = *this; ++i; return r; } + iterator &operator--() { --i; return *this; } + iterator operator--(int) { iterator r = *this; --i; return r; } + iterator operator+(int j) const + { iterator r = *this; r.i += j; return r; } + iterator operator-(int j) const { return operator+(-j); } + iterator &operator+=(int j) { i += j; return *this; } + iterator &operator-=(int j) { i -= j; return *this; } + + public: + bool operator==(const const_iterator &other) const { return i == other.i; } + bool operator!=(const const_iterator &other) const { return i != other.i; } + }; + friend class iterator; + + class const_iterator + { + friend class iterator; + const JsonObject *o; + int i; + + public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef int difference_type; + typedef JsonValue value_type; + typedef JsonValue reference; + + constexpr const_iterator() : o(nullptr), i(0) {} + constexpr const_iterator(const JsonObject *obj, int index) + : o(obj), i(index) {} + const_iterator(const iterator &other) + : o(other.o), i(other.i) {} + + std::string key() const { return o->keyAt(i); } + JsonValue value() const { return o->valueAt(i); } + JsonValue operator*() const { return o->valueAt(i); } + JsonValuePtr operator->() const { return JsonValuePtr(o->valueAt(i)); } + bool operator==(const const_iterator &other) const { return i == other.i; } + bool operator!=(const const_iterator &other) const { return i != other.i; } + + const_iterator &operator++() { ++i; return *this; } + const_iterator operator++(int) { const_iterator r = *this; ++i; return r; } + const_iterator &operator--() { --i; return *this; } + const_iterator operator--(int) { const_iterator r = *this; --i; return r; } + const_iterator operator+(int j) const + { const_iterator r = *this; r.i += j; return r; } + const_iterator operator-(int j) const { return operator+(-j); } + const_iterator &operator+=(int j) { i += j; return *this; } + const_iterator &operator-=(int j) { i -= j; return *this; } + + bool operator==(const iterator &other) const { return i == other.i; } + bool operator!=(const iterator &other) const { return i != other.i; } + }; + friend class const_iterator; + + // STL style + iterator begin() { detach(); return iterator(this, 0); } + const_iterator begin() const { return const_iterator(this, 0); } + const_iterator constBegin() const { return const_iterator(this, 0); } + iterator end() { detach(); return iterator(this, size()); } + const_iterator end() const { return const_iterator(this, size()); } + const_iterator constEnd() const { return const_iterator(this, size()); } + iterator erase(iterator it); + + // more Qt + iterator find(const std::string &key); + const_iterator find(const std::string &key) const { return constFind(key); } + const_iterator constFind(const std::string &key) const; + iterator insert(const std::string &key, const JsonValue &value); + + // STL compatibility + typedef JsonValue mapped_type; + typedef std::string key_type; + typedef int size_type; + + bool empty() const { return isEmpty(); } + +private: + friend class Internal::Data; + friend class JsonValue; + friend class JsonDocument; + friend class JsonValueRef; + + JsonObject(Internal::Data *data, Internal::Object *object); + void detach(uint32_t reserve = 0); + void compact(); + + std::string keyAt(int i) const; + JsonValue valueAt(int i) const; + void setValueAt(int i, const JsonValue &val); + + Internal::Data *d; + Internal::Object *o; +}; + +struct JsonParseError +{ + enum ParseError { + NoError = 0, + UnterminatedObject, + MissingNameSeparator, + UnterminatedArray, + MissingValueSeparator, + IllegalValue, + TerminationByNumber, + IllegalNumber, + IllegalEscapeSequence, + IllegalUTF8String, + UnterminatedString, + MissingObject, + DeepNesting, + DocumentTooLarge, + GarbageAtEnd + }; + + int offset; + ParseError error; +}; + +class JsonDocument +{ +public: + static const uint32_t BinaryFormatTag = ('q') | ('b' << 8) | ('j' << 16) | ('s' << 24); + JsonDocument(); + explicit JsonDocument(const JsonObject &object); + explicit JsonDocument(const JsonArray &array); + ~JsonDocument(); + + JsonDocument(const JsonDocument &other); + JsonDocument &operator =(const JsonDocument &other); + + enum DataValidation { + Validate, + BypassValidation + }; + + static JsonDocument fromRawData(const char *data, int size, DataValidation validation = Validate); + const char *rawData(int *size) const; + + static JsonDocument fromBinaryData(const std::string &data, DataValidation validation = Validate); + std::string toBinaryData() const; + + enum JsonFormat { + Indented, + Compact + }; + + static JsonDocument fromJson(const std::string &json, JsonParseError *error = nullptr); + + std::string toJson(JsonFormat format = Indented) const; + + bool isEmpty() const; + bool isArray() const; + bool isObject() const; + + JsonObject object() const; + JsonArray array() const; + + void setObject(const JsonObject &object); + void setArray(const JsonArray &array); + + bool operator==(const JsonDocument &other) const; + bool operator!=(const JsonDocument &other) const { return !(*this == other); } + + bool isNull() const; + +private: + friend class JsonValue; + friend class Internal::Data; + friend class Internal::Parser; + + JsonDocument(Internal::Data *data); + + Internal::Data *d; +}; + +} // namespace Json + +#endif // JSONVALUE_H diff --git a/src/shared/json/json.pri b/src/shared/json/json.pri new file mode 100644 index 00000000000..db7ce19eee9 --- /dev/null +++ b/src/shared/json/json.pri @@ -0,0 +1,2 @@ +HEADERS += $$PWD/json.h +SOURCES += $$PWD/json.cpp diff --git a/src/shared/json/json.qbs b/src/shared/json/json.qbs new file mode 100644 index 00000000000..3587d307afc --- /dev/null +++ b/src/shared/json/json.qbs @@ -0,0 +1,15 @@ +import qbs + +StaticLibrary { + name: "qtcjson" + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" + files: [ + "json.cpp", + "json.h", + ] + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory] + } +} diff --git a/src/src.qbs b/src/src.qbs index 5b8c62e332e..1953849d02e 100644 --- a/src/src.qbs +++ b/src/src.qbs @@ -45,6 +45,7 @@ Project { qbsBaseDir + "/src/plugins/plugins.qbs", qbsBaseDir + "/share/share.qbs", qbsBaseDir + "/src/app/apps.qbs", + project.sharedSourcesDir + "/json", ] } } diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 6284497e4c5..eabe10b3a19 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -13,6 +13,7 @@ SUBDIRS += \ profilewriter \ treeviewfind \ qtcprocess \ + json \ utils \ filesearch \ sdktool \ diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs index 91c6e593f88..15465a61e6f 100644 --- a/tests/auto/auto.qbs +++ b/tests/auto/auto.qbs @@ -14,6 +14,7 @@ Project { "externaltool/externaltool.qbs", "filesearch/filesearch.qbs", "generichighlighter/generichighlighter.qbs", + "json/json.qbs", "profilewriter/profilewriter.qbs", "qml/qml.qbs", "qtcprocess/qtcprocess.qbs", diff --git a/tests/auto/json/bom.json b/tests/auto/json/bom.json new file mode 100644 index 00000000000..d1e8d90e280 --- /dev/null +++ b/tests/auto/json/bom.json @@ -0,0 +1,3 @@ +{ + "info-version": "1.0" +} diff --git a/tests/auto/json/json.pro b/tests/auto/json/json.pro new file mode 100644 index 00000000000..b7c59706983 --- /dev/null +++ b/tests/auto/json/json.pro @@ -0,0 +1,13 @@ +TARGET = tst_json +QT = core testlib +CONFIG -= app_bundle +CONFIG += testcase + +TESTDATA += test.json test.bjson test3.json test2.json bom.json + +INCLUDEPATH += ../../../src/shared/json + +DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII + +include(../../../src/shared/json/json.pri) +SOURCES += tst_json.cpp diff --git a/tests/auto/json/json.qbs b/tests/auto/json/json.qbs new file mode 100644 index 00000000000..de9a22fcf2a --- /dev/null +++ b/tests/auto/json/json.qbs @@ -0,0 +1,22 @@ +import qbs + +QtcAutotest { + name: "json autotest" + Depends { name: "bundle" } + Depends { name: "qtcjson" } + + bundle.isBundle: false + + Group { + name: "test data" + files: [ + "bom.json", + "test.bjson", + "test.json", + "test2.json", + "test3.json", + ] + } + + files: ["tst_json.cpp"] +} diff --git a/tests/auto/json/test.bjson b/tests/auto/json/test.bjson Binary files differnew file mode 100644 index 00000000000..ae12e5e446b --- /dev/null +++ b/tests/auto/json/test.bjson diff --git a/tests/auto/json/test.json b/tests/auto/json/test.json new file mode 100644 index 00000000000..330756894a6 --- /dev/null +++ b/tests/auto/json/test.json @@ -0,0 +1,66 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwxyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={\':[,]}|;.</>?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "nix", + "comment": "// /* <!-- --", + "# -- --> */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?" : "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00, +2e+00, +2e-00, +"rosebud", +{"foo": "bar"}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}} +] + diff --git a/tests/auto/json/test2.json b/tests/auto/json/test2.json new file mode 100644 index 00000000000..303f879b626 --- /dev/null +++ b/tests/auto/json/test2.json @@ -0,0 +1 @@ +{ "foo": ["ab"] } diff --git a/tests/auto/json/test3.json b/tests/auto/json/test3.json new file mode 100644 index 00000000000..48cb29a47f1 --- /dev/null +++ b/tests/auto/json/test3.json @@ -0,0 +1,15 @@ +{ + "firstName": "John", + "lastName" : "Smith", + "age" : 25, + "address" : { + "streetAddress": "21 2nd Street", + "city" : "New York", + "state" : "NY", + "postalCode" : "10021" + }, + "phoneNumber": [ + { "type" : "home", "number": "212 555-1234" }, + { "type" : "fax", "number": "646 555-4567" } + ] +} diff --git a/tests/auto/json/tst_json.cpp b/tests/auto/json/tst_json.cpp new file mode 100644 index 00000000000..f24c09710de --- /dev/null +++ b/tests/auto/json/tst_json.cpp @@ -0,0 +1,2524 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <json.h> + +#include <QTest> +#include <QCryptographicHash> + +#include <limits> + +#define INVALID_UNICODE "\xCE\xBA\xE1" +#define UNICODE_NON_CHARACTER "\xEF\xBF\xBF" +#define UNICODE_DJE "\320\202" // Character from the Serbian Cyrillic alphabet + +using namespace Json; + +Q_DECLARE_METATYPE(Json::JsonArray) +Q_DECLARE_METATYPE(Json::JsonObject) + +bool contains(const JsonObject::Keys &keys, const std::string &key) +{ + return std::find(keys.begin(), keys.end(), key) != keys.end(); +} + +class tst_Json: public QObject +{ + Q_OBJECT + +public: + tst_Json(QObject *parent = 0); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testValueSimple(); + void testNumbers(); + void testNumbers_2(); + void testNumbers_3(); + + void testObjectSimple(); + void testObjectSmallKeys(); + void testArraySimple(); + void testValueObject(); + void testValueArray(); + void testObjectNested(); + void testArrayNested(); + void testArrayNestedEmpty(); + void testObjectNestedEmpty(); + + void testValueRef(); + void testObjectIteration(); + void testArrayIteration(); + + void testObjectFind(); + + void testDocument(); + + void nullValues(); + void nullArrays(); + void nullObject(); + void constNullObject(); + + void keySorting(); + + void undefinedValues(); + + void toJson(); + void toJsonSillyNumericValues(); + void toJsonLargeNumericValues(); + void fromJson(); + void fromJsonErrors(); + void fromBinary(); + void toAndFromBinary_data(); + void toAndFromBinary(); + void parseNumbers(); + void parseStrings(); + void parseDuplicateKeys(); + void testParser(); + + void compactArray(); + void compactObject(); + + void validation(); + + void assignToDocument(); + + void testDuplicateKeys(); + void testCompaction(); + void testCompactionError(); + + void parseUnicodeEscapes(); + + void assignObjects(); + void assignArrays(); + + void testTrailingComma(); + void testDetachBug(); + void testJsonValueRefDefault(); + + void valueEquals(); + void objectEquals_data(); + void objectEquals(); + void arrayEquals_data(); + void arrayEquals(); + + void bom(); + void nesting(); + + void longStrings(); + + void arrayInitializerList(); + void objectInitializerList(); + + void unicodeKeys(); + void garbageAtEnd(); + + void removeNonLatinKey(); +private: + QString testDataDir; +}; + +tst_Json::tst_Json(QObject *parent) : QObject(parent) +{ +} + +void tst_Json::initTestCase() +{ + testDataDir = QFileInfo(QFINDTESTDATA("test.json")).absolutePath(); + if (testDataDir.isEmpty()) + testDataDir = QCoreApplication::applicationDirPath(); +} + +void tst_Json::cleanupTestCase() +{ +} + +void tst_Json::init() +{ +} + +void tst_Json::cleanup() +{ +} + +void tst_Json::testValueSimple() +{ + JsonObject object; + object.insert("number", 999.); + JsonArray array; + for (int i = 0; i < 10; ++i) + array.append((double)i); + + JsonValue value(true); + QCOMPARE(value.type(), JsonValue::Bool); + QCOMPARE(value.toDouble(), 0.); + QCOMPARE(value.toString(), std::string()); + QCOMPARE(value.toBool(), true); + QCOMPARE(value.toObject(), JsonObject()); + QCOMPARE(value.toArray(), JsonArray()); + QCOMPARE(value.toDouble(99.), 99.); + QCOMPARE(value.toString("test"), std::string("test")); + QCOMPARE(value.toObject(object), object); + QCOMPARE(value.toArray(array), array); + + value = 999.; + QCOMPARE(value.type(), JsonValue::Double); + QCOMPARE(value.toDouble(), 999.); + QCOMPARE(value.toString(), std::string()); + QCOMPARE(value.toBool(), false); + QCOMPARE(value.toBool(true), true); + QCOMPARE(value.toObject(), JsonObject()); + QCOMPARE(value.toArray(), JsonArray()); + + value = "test"; + QCOMPARE(value.toDouble(), 0.); + QCOMPARE(value.toString(), std::string("test")); + QCOMPARE(value.toBool(), false); + QCOMPARE(value.toObject(), JsonObject()); + QCOMPARE(value.toArray(), JsonArray()); +} + +void tst_Json::testNumbers() +{ + { + int numbers[] = { + 0, + -1, + 1, + (1<<26), + (1<<27), + (1<<28), + -(1<<26), + -(1<<27), + -(1<<28), + (1<<26) - 1, + (1<<27) - 1, + (1<<28) - 1, + -((1<<26) - 1), + -((1<<27) - 1), + -((1<<28) - 1) + }; + int n = sizeof(numbers)/sizeof(int); + + JsonArray array; + for (int i = 0; i < n; ++i) + array.append((double)numbers[i]); + + std::string serialized = JsonDocument(array).toJson(); + JsonDocument json = JsonDocument::fromJson(serialized); + JsonArray array2 = json.array(); + + QCOMPARE(array.size(), array2.size()); + for (int i = 0; i < array.size(); ++i) { + QCOMPARE(array.at(i).type(), JsonValue::Double); + QCOMPARE(array.at(i).toDouble(), (double)numbers[i]); + QCOMPARE(array2.at(i).type(), JsonValue::Double); + QCOMPARE(array2.at(i).toDouble(), (double)numbers[i]); + } + } + + { + int64_t numbers[] = { + 0, + -1, + 1, + (1ll<<54), + (1ll<<55), + (1ll<<56), + -(1ll<<54), + -(1ll<<55), + -(1ll<<56), + (1ll<<54) - 1, + (1ll<<55) - 1, + (1ll<<56) - 1, + -((1ll<<54) - 1), + -((1ll<<55) - 1), + -((1ll<<56) - 1) + }; + int n = sizeof(numbers)/sizeof(int64_t); + + JsonArray array; + for (int i = 0; i < n; ++i) + array.append((double)numbers[i]); + + std::string serialized = JsonDocument(array).toJson(); + JsonDocument json = JsonDocument::fromJson(serialized); + JsonArray array2 = json.array(); + + QCOMPARE(array.size(), array2.size()); + for (int i = 0; i < array.size(); ++i) { + QCOMPARE(array.at(i).type(), JsonValue::Double); + QCOMPARE(array.at(i).toDouble(), (double)numbers[i]); + QCOMPARE(array2.at(i).type(), JsonValue::Double); + QCOMPARE(array2.at(i).toDouble(), (double)numbers[i]); + } + } + + { + double numbers[] = { + 0, + -1, + 1, + double(1ll<<54), + double(1ll<<55), + double(1ll<<56), + double(-(1ll<<54)), + double(-(1ll<<55)), + double(-(1ll<<56)), + double((1ll<<54) - 1), + double((1ll<<55) - 1), + double((1ll<<56) - 1), + double(-((1ll<<54) - 1)), + double(-((1ll<<55) - 1)), + double(-((1ll<<56) - 1)), + 1.1, + 0.1, + -0.1, + -1.1, + 1e200, + -1e200 + }; + int n = sizeof(numbers)/sizeof(double); + + JsonArray array; + for (int i = 0; i < n; ++i) + array.append(numbers[i]); + + std::string serialized = JsonDocument(array).toJson(); + JsonDocument json = JsonDocument::fromJson(serialized); + JsonArray array2 = json.array(); + + QCOMPARE(array.size(), array2.size()); + for (int i = 0; i < array.size(); ++i) { + QCOMPARE(array.at(i).type(), JsonValue::Double); + QCOMPARE(array.at(i).toDouble(), numbers[i]); + QCOMPARE(array2.at(i).type(), JsonValue::Double); + QCOMPARE(array2.at(i).toDouble(), numbers[i]); + } + } + +} + +void tst_Json::testNumbers_2() +{ + // test cases from TC39 test suite for ECMAScript + // http://hg.ecmascript.org/tests/test262/file/d067d2f0ca30/test/suite/ch08/8.5/8.5.1.js + + // Fill an array with 2 to the power of (0 ... -1075) + double value = 1; + double floatValues[1076], floatValues_1[1076]; + JsonObject jObject; + for (int power = 0; power <= 1075; power++) { + floatValues[power] = value; + jObject.insert(std::to_string(power), JsonValue(floatValues[power])); + // Use basic math operations for testing, which are required to support 'gradual underflow' rather + // than Math.pow etc..., which are defined as 'implementation dependent'. + value = value * 0.5; + } + + JsonDocument jDocument1(jObject); + std::string ba(jDocument1.toJson()); + + JsonDocument jDocument2(JsonDocument::fromJson(ba)); + for (int power = 0; power <= 1075; power++) { + floatValues_1[power] = jDocument2.object().value(std::to_string(power)).toDouble(); +#ifdef Q_OS_QNX + if (power >= 970) + QEXPECT_FAIL("", "See QTBUG-37066", Abort); +#endif + QVERIFY2(floatValues[power] == floatValues_1[power], + QString::fromLatin1("floatValues[%1] != floatValues_1[%1]").arg(power).toLatin1()); + } + + // The last value is below min denorm and should round to 0, everything else should contain a value + QVERIFY2(floatValues_1[1075] == 0, "Value after min denorm should round to 0"); + + // Validate the last actual value is min denorm + QVERIFY2(floatValues_1[1074] == 4.9406564584124654417656879286822e-324, + QString::fromLatin1("Min denorm value is incorrect: %1").arg(floatValues_1[1074]).toLatin1()); + + // Validate that every value is half the value before it up to 1 + for (int index = 1074; index > 0; index--) { + QVERIFY2(floatValues_1[index] != 0, + QString::fromLatin1("2**- %1 should not be 0").arg(index).toLatin1()); + + QVERIFY2(floatValues_1[index - 1] == (floatValues_1[index] * 2), + QString::fromLatin1("Value should be double adjacent value at index %1").arg(index).toLatin1()); + } +} + +void tst_Json::testNumbers_3() +{ + // test case from QTBUG-31926 + double d1 = 1.123451234512345; + double d2 = 1.123451234512346; + + JsonObject jObject; + jObject.insert("d1", JsonValue(d1)); + jObject.insert("d2", JsonValue(d2)); + JsonDocument jDocument1(jObject); + std::string ba(jDocument1.toJson()); + + JsonDocument jDocument2(JsonDocument::fromJson(ba)); + + double d1_1(jDocument2.object().value("d1").toDouble()); + double d2_1(jDocument2.object().value("d2").toDouble()); + QVERIFY(d1_1 != d2_1); +} + +void tst_Json::testObjectSimple() +{ + JsonObject object; + object.insert("number", 999.); + QCOMPARE(object.value("number").type(), JsonValue::Double); + QCOMPARE(object.value("number").toDouble(), 999.); + object.insert("string", std::string("test")); + QCOMPARE(object.value("string").type(), JsonValue::String); + QCOMPARE(object.value("string").toString(), std::string("test")); + object.insert("boolean", true); + QCOMPARE(object.value("boolean").toBool(), true); + + JsonObject::Keys keys = object.keys(); + QVERIFY2(contains(keys, "number"), "key number not found"); + QVERIFY2(contains(keys, "string"), "key string not found"); + QVERIFY2(contains(keys, "boolean"), "key boolean not found"); + + // if we put a JsonValue into the JsonObject and retrieve + // it, it should be identical. + JsonValue value("foo"); + object.insert("value", value); + QCOMPARE(object.value("value"), value); + + int size = object.size(); + object.remove("boolean"); + QCOMPARE(object.size(), size - 1); + QVERIFY2(!object.contains("boolean"), "key boolean should have been removed"); + + JsonValue taken = object.take("value"); + QCOMPARE(taken, value); + QVERIFY2(!object.contains("value"), "key value should have been removed"); + + std::string before = object.value("string").toString(); + object.insert("string", std::string("foo")); + QVERIFY2(object.value("string").toString() != before, "value should have been updated"); + + size = object.size(); + JsonObject subobject; + subobject.insert("number", 42); + subobject.insert("string", "foobar"); + object.insert("subobject", subobject); + QCOMPARE(object.size(), size+1); + JsonValue subvalue = object.take("subobject"); + QCOMPARE(object.size(), size); + QCOMPARE(subvalue.toObject(), subobject); + // make object detach by modifying it many times + for (int i = 0; i < 64; ++i) + object.insert("string", "bar"); + QCOMPARE(object.size(), size); + QCOMPARE(subvalue.toObject(), subobject); +} + +void tst_Json::testObjectSmallKeys() +{ + JsonObject data1; + data1.insert("1", 123.); + QVERIFY(data1.contains("1")); + QCOMPARE(data1.value("1").toDouble(), (double)123); + data1.insert("12", 133.); + QCOMPARE(data1.value("12").toDouble(), (double)133); + QVERIFY(data1.contains("12")); + data1.insert("123", 323.); + QCOMPARE(data1.value("12").toDouble(), (double)133); + QVERIFY(data1.contains("123")); + QCOMPARE(data1.value("123").type(), JsonValue::Double); + QCOMPARE(data1.value("123").toDouble(), (double)323); +} + +void tst_Json::testArraySimple() +{ + JsonArray array; + array.append(999.); + array.append(std::string("test")); + array.append(true); + + JsonValue val = array.at(0); + QCOMPARE(array.at(0).toDouble(), 999.); + QCOMPARE(array.at(1).toString(), std::string("test")); + QCOMPARE(array.at(2).toBool(), true); + QCOMPARE(array.size(), 3); + + // if we put a JsonValue into the JsonArray and retrieve + // it, it should be identical. + JsonValue value("foo"); + array.append(value); + QCOMPARE(array.at(3), value); + + int size = array.size(); + array.removeAt(2); + --size; + QCOMPARE(array.size(), size); + + JsonValue taken = array.takeAt(0); + --size; + QCOMPARE(taken.toDouble(), 999.); + QCOMPARE(array.size(), size); + + // check whether null values work + array.append(JsonValue()); + ++size; + QCOMPARE(array.size(), size); + QCOMPARE(array.last().type(), JsonValue::Null); + QCOMPARE(array.last(), JsonValue()); + + QCOMPARE(array.first().type(), JsonValue::String); + QCOMPARE(array.first(), JsonValue("test")); + + array.prepend(false); + QCOMPARE(array.first().type(), JsonValue::Bool); + QCOMPARE(array.first(), JsonValue(false)); + + QCOMPARE(array.at(-1), JsonValue(JsonValue::Undefined)); + QCOMPARE(array.at(array.size()), JsonValue(JsonValue::Undefined)); + + array.replace(0, -555.); + QCOMPARE(array.first().type(), JsonValue::Double); + QCOMPARE(array.first(), JsonValue(-555.)); + QCOMPARE(array.at(1).type(), JsonValue::String); + QCOMPARE(array.at(1), JsonValue("test")); +} + +void tst_Json::testValueObject() +{ + JsonObject object; + object.insert("number", 999.); + object.insert("string", "test"); + object.insert("boolean", true); + + JsonValue value(object); + + // if we don't modify the original JsonObject, toObject() + // on the JsonValue should return the same object (non-detached). + QCOMPARE(value.toObject(), object); + + // if we modify the original object, it should detach + object.insert("test", JsonValue("test")); + QVERIFY2(value.toObject() != object, "object should have detached"); +} + +void tst_Json::testValueArray() +{ + JsonArray array; + array.append(999.); + array.append("test"); + array.append(true); + + JsonValue value(array); + + // if we don't modify the original JsonArray, toArray() + // on the JsonValue should return the same object (non-detached). + QCOMPARE(value.toArray(), array); + + // if we modify the original array, it should detach + array.append("test"); + QVERIFY2(value.toArray() != array, "array should have detached"); +} + +void tst_Json::testObjectNested() +{ + JsonObject inner, outer; + inner.insert("number", 999.); + outer.insert("nested", inner); + + // if we don't modify the original JsonObject, value() + // should return the same object (non-detached). + JsonObject value = outer.value("nested").toObject(); + QCOMPARE(value, inner); + QCOMPARE(value.value("number").toDouble(), 999.); + + // if we modify the original object, it should detach and not + // affect the nested object + inner.insert("number", 555.); + value = outer.value("nested").toObject(); + QVERIFY2(inner.value("number").toDouble() != value.value("number").toDouble(), + "object should have detached"); + + // array in object + JsonArray array; + array.append(123.); + array.append(456.); + outer.insert("array", array); + QCOMPARE(outer.value("array").toArray(), array); + QCOMPARE(outer.value("array").toArray().at(1).toDouble(), 456.); + + // two deep objects + JsonObject twoDeep; + twoDeep.insert("boolean", true); + inner.insert("nested", twoDeep); + outer.insert("nested", inner); + QCOMPARE(outer.value("nested").toObject().value("nested").toObject(), twoDeep); + QCOMPARE(outer.value("nested").toObject().value("nested").toObject().value("boolean").toBool(), + true); +} + +void tst_Json::testArrayNested() +{ + JsonArray inner, outer; + inner.append(999.); + outer.append(inner); + + // if we don't modify the original JsonArray, value() + // should return the same array (non-detached). + JsonArray value = outer.at(0).toArray(); + QCOMPARE(value, inner); + QCOMPARE(value.at(0).toDouble(), 999.); + + // if we modify the original array, it should detach and not + // affect the nested array + inner.append(555.); + value = outer.at(0).toArray(); + QVERIFY2(inner.size() != value.size(), "array should have detached"); + + // objects in arrays + JsonObject object; + object.insert("boolean", true); + outer.append(object); + QCOMPARE(outer.last().toObject(), object); + QCOMPARE(outer.last().toObject().value("boolean").toBool(), true); + + // two deep arrays + JsonArray twoDeep; + twoDeep.append(JsonValue("nested")); + inner.append(twoDeep); + outer.append(inner); + QCOMPARE(outer.last().toArray().last().toArray(), twoDeep); + QCOMPARE(outer.last().toArray().last().toArray().at(0).toString(), std::string("nested")); +} + +void tst_Json::testArrayNestedEmpty() +{ + JsonObject object; + JsonArray inner; + object.insert("inner", inner); + JsonValue val = object.value("inner"); + JsonArray value = object.value("inner").toArray(); + QCOMPARE(value.size(), 0); + QCOMPARE(value, inner); + QCOMPARE(value.size(), 0); + object.insert("count", 0.); + QCOMPARE(object.value("inner").toArray().size(), 0); + QVERIFY(object.value("inner").toArray().isEmpty()); + JsonDocument(object).toBinaryData(); + QCOMPARE(object.value("inner").toArray().size(), 0); +} + +void tst_Json::testObjectNestedEmpty() +{ + JsonObject object; + JsonObject inner; + JsonObject inner2; + object.insert("inner", inner); + object.insert("inner2", inner2); + JsonObject value = object.value("inner").toObject(); + QCOMPARE(value.size(), 0); + QCOMPARE(value, inner); + QCOMPARE(value.size(), 0); + object.insert("count", 0.); + QCOMPARE(object.value("inner").toObject().size(), 0); + QCOMPARE(object.value("inner").type(), JsonValue::Object); + JsonDocument(object).toBinaryData(); + QVERIFY(object.value("inner").toObject().isEmpty()); + QVERIFY(object.value("inner2").toObject().isEmpty()); + JsonDocument doc = JsonDocument::fromBinaryData(JsonDocument(object).toBinaryData()); + QVERIFY(!doc.isNull()); + JsonObject reconstituted(doc.object()); + QCOMPARE(reconstituted.value("inner").toObject().size(), 0); + QCOMPARE(reconstituted.value("inner").type(), JsonValue::Object); + QCOMPARE(reconstituted.value("inner2").type(), JsonValue::Object); +} + +void tst_Json::testValueRef() +{ + JsonArray array; + array.append(1.); + array.append(2.); + array.append(3.); + array.append(4); + array.append(4.1); + array[1] = false; + + QCOMPARE(array.size(), 5); + QCOMPARE(array.at(0).toDouble(), 1.); + QCOMPARE(array.at(2).toDouble(), 3.); + QCOMPARE(array.at(3).toInt(), 4); + QCOMPARE(array.at(4).toInt(), 0); + QCOMPARE(array.at(1).type(), JsonValue::Bool); + QCOMPARE(array.at(1).toBool(), false); + + JsonObject object; + object["key"] = true; + QCOMPARE(object.size(), 1); + object.insert("null", JsonValue()); + QCOMPARE(object.value("null"), JsonValue()); + object["null"] = 100.; + QCOMPARE(object.value("null").type(), JsonValue::Double); + JsonValue val = object["null"]; + QCOMPARE(val.toDouble(), 100.); + QCOMPARE(object.size(), 2); + + array[1] = array[2] = object["key"] = 42; + QCOMPARE(array[1], array[2]); + QCOMPARE(array[2], object["key"]); + QCOMPARE(object.value("key"), JsonValue(42)); +} + +void tst_Json::testObjectIteration() +{ + JsonObject object; + + for (JsonObject::iterator it = object.begin(); it != object.end(); ++it) + QVERIFY(false); + + const std::string property = "kkk"; + object.insert(property, 11); + object.take(property); + for (JsonObject::iterator it = object.begin(); it != object.end(); ++it) + QVERIFY(false); + + for (int i = 0; i < 10; ++i) + object[std::to_string(i)] = (double)i; + + QCOMPARE(object.size(), 10); + + QCOMPARE(object.begin()->toDouble(), object.constBegin()->toDouble()); + + for (JsonObject::iterator it = object.begin(); it != object.end(); ++it) { + JsonValue value = it.value(); + QCOMPARE((double)atoi(it.key().data()), value.toDouble()); + } + + { + JsonObject object2 = object; + QCOMPARE(object, object2); + + JsonValue val = *object2.begin(); + object2.erase(object2.begin()); + QCOMPARE(object.size(), 10); + QCOMPARE(object2.size(), 9); + + for (JsonObject::const_iterator it = object2.constBegin(); it != object2.constEnd(); ++it) { + JsonValue value = it.value(); + QVERIFY(it.value() != val); + QCOMPARE((double)atoi(it.key().data()), value.toDouble()); + } + } + + { + JsonObject object2 = object; + QCOMPARE(object, object2); + + JsonObject::iterator it = object2.find(std::to_string(5)); + object2.erase(it); + QCOMPARE(object.size(), 10); + QCOMPARE(object2.size(), 9); + } + + { + JsonObject::iterator it = object.begin(); + it += 5; + QCOMPARE(JsonValue(it.value()).toDouble(), 5.); + it -= 3; + QCOMPARE(JsonValue(it.value()).toDouble(), 2.); + JsonObject::iterator it2 = it + 5; + QCOMPARE(JsonValue(it2.value()).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(JsonValue(it2.value()).toDouble(), 1.); + } + + { + JsonObject::const_iterator it = object.constBegin(); + it += 5; + QCOMPARE(JsonValue(it.value()).toDouble(), 5.); + it -= 3; + QCOMPARE(JsonValue(it.value()).toDouble(), 2.); + JsonObject::const_iterator it2 = it + 5; + QCOMPARE(JsonValue(it2.value()).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(JsonValue(it2.value()).toDouble(), 1.); + } + + JsonObject::iterator it = object.begin(); + while (!object.isEmpty()) + it = object.erase(it); + QCOMPARE(object.size() , 0); + QCOMPARE(it, object.end()); +} + +void tst_Json::testArrayIteration() +{ + JsonArray array; + for (int i = 0; i < 10; ++i) + array.append(i); + + QCOMPARE(array.size(), 10); + + int i = 0; + for (JsonArray::iterator it = array.begin(); it != array.end(); ++it, ++i) { + JsonValue value = (*it); + QCOMPARE((double)i, value.toDouble()); + } + + QCOMPARE(array.begin()->toDouble(), array.constBegin()->toDouble()); + + { + JsonArray array2 = array; + QCOMPARE(array, array2); + + JsonValue val = *array2.begin(); + array2.erase(array2.begin()); + QCOMPARE(array.size(), 10); + QCOMPARE(array2.size(), 9); + + i = 1; + for (JsonArray::const_iterator it = array2.constBegin(); it != array2.constEnd(); ++it, ++i) { + JsonValue value = (*it); + QCOMPARE((double)i, value.toDouble()); + } + } + + { + JsonArray::iterator it = array.begin(); + it += 5; + QCOMPARE(JsonValue((*it)).toDouble(), 5.); + it -= 3; + QCOMPARE(JsonValue((*it)).toDouble(), 2.); + JsonArray::iterator it2 = it + 5; + QCOMPARE(JsonValue(*it2).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(JsonValue(*it2).toDouble(), 1.); + } + + { + JsonArray::const_iterator it = array.constBegin(); + it += 5; + QCOMPARE(JsonValue((*it)).toDouble(), 5.); + it -= 3; + QCOMPARE(JsonValue((*it)).toDouble(), 2.); + JsonArray::const_iterator it2 = it + 5; + QCOMPARE(JsonValue(*it2).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(JsonValue(*it2).toDouble(), 1.); + } + + JsonArray::iterator it = array.begin(); + while (!array.isEmpty()) + it = array.erase(it); + QCOMPARE(array.size() , 0); + QCOMPARE(it, array.end()); +} + +void tst_Json::testObjectFind() +{ + JsonObject object; + for (int i = 0; i < 10; ++i) + object[std::to_string(i)] = i; + + QCOMPARE(object.size(), 10); + + JsonObject::iterator it = object.find("1"); + QCOMPARE((*it).toDouble(), 1.); + it = object.find("11"); + QCOMPARE((*it).type(), JsonValue::Undefined); + QCOMPARE(it, object.end()); + + JsonObject::const_iterator cit = object.constFind("1"); + QCOMPARE((*cit).toDouble(), 1.); + cit = object.constFind("11"); + QCOMPARE((*it).type(), JsonValue::Undefined); + QCOMPARE(it, object.end()); +} + +void tst_Json::testDocument() +{ + JsonDocument doc; + QCOMPARE(doc.isEmpty(), true); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), false); + + JsonObject object; + doc.setObject(object); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + + object.insert("Key", "Value"); + doc.setObject(object); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QCOMPARE(doc.object(), object); + QCOMPARE(doc.array(), JsonArray()); + + doc = JsonDocument(); + QCOMPARE(doc.isEmpty(), true); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), false); + + JsonArray array; + doc.setArray(array); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + + array.append("Value"); + doc.setArray(array); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QCOMPARE(doc.array(), array); + QCOMPARE(doc.object(), JsonObject()); + + JsonObject outer; + outer.insert("outerKey", 22); + JsonObject inner; + inner.insert("innerKey", 42); + outer.insert("innter", inner); + JsonArray innerArray; + innerArray.append(23); + outer.insert("innterArray", innerArray); + + JsonDocument doc2(outer.value("innter").toObject()); + QVERIFY(doc2.object().contains("innerKey")); + QCOMPARE(doc2.object().value("innerKey"), JsonValue(42)); + + JsonDocument doc3; + doc3.setObject(outer.value("innter").toObject()); + QCOMPARE(doc3.isArray(), false); + QCOMPARE(doc3.isObject(), true); + QVERIFY(doc3.object().contains("innerKey")); + QCOMPARE(doc3.object().value("innerKey"), JsonValue(42)); + + JsonDocument doc4(outer.value("innterArray").toArray()); + QCOMPARE(doc4.isArray(), true); + QCOMPARE(doc4.isObject(), false); + QCOMPARE(doc4.array().size(), 1); + QCOMPARE(doc4.array().at(0), JsonValue(23)); + + JsonDocument doc5; + doc5.setArray(outer.value("innterArray").toArray()); + QCOMPARE(doc5.isArray(), true); + QCOMPARE(doc5.isObject(), false); + QCOMPARE(doc5.array().size(), 1); + QCOMPARE(doc5.array().at(0), JsonValue(23)); +} + +void tst_Json::nullValues() +{ + JsonArray array; + array.append(JsonValue()); + + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0), JsonValue()); + + JsonObject object; + object.insert("key", JsonValue()); + QCOMPARE(object.contains("key"), true); + QCOMPARE(object.size(), 1); + QCOMPARE(object.value("key"), JsonValue()); +} + +void tst_Json::nullArrays() +{ + JsonArray nullArray; + JsonArray nonNull; + nonNull.append("bar"); + + QCOMPARE(nullArray, JsonArray()); + QVERIFY(nullArray != nonNull); + QVERIFY(nonNull != nullArray); + + QCOMPARE(nullArray.size(), 0); + QCOMPARE(nullArray.takeAt(0), JsonValue(JsonValue::Undefined)); + QCOMPARE(nullArray.first(), JsonValue(JsonValue::Undefined)); + QCOMPARE(nullArray.last(), JsonValue(JsonValue::Undefined)); + nullArray.removeAt(0); + nullArray.removeAt(-1); + + nullArray.append("bar"); + nullArray.removeAt(0); + + QCOMPARE(nullArray.size(), 0); + QCOMPARE(nullArray.takeAt(0), JsonValue(JsonValue::Undefined)); + QCOMPARE(nullArray.first(), JsonValue(JsonValue::Undefined)); + QCOMPARE(nullArray.last(), JsonValue(JsonValue::Undefined)); + nullArray.removeAt(0); + nullArray.removeAt(-1); +} + +void tst_Json::nullObject() +{ + JsonObject nullObject; + JsonObject nonNull; + nonNull.insert("foo", "bar"); + + QCOMPARE(nullObject, JsonObject()); + QVERIFY(nullObject != nonNull); + QVERIFY(nonNull != nullObject); + + QCOMPARE(nullObject.size(), 0); + QCOMPARE(nullObject.keys(), JsonObject::Keys()); + nullObject.remove("foo"); + QCOMPARE(nullObject, JsonObject()); + QCOMPARE(nullObject.take("foo"), JsonValue(JsonValue::Undefined)); + QCOMPARE(nullObject.contains("foo"), false); + + nullObject.insert("foo", "bar"); + nullObject.remove("foo"); + + QCOMPARE(nullObject.size(), 0); + QCOMPARE(nullObject.keys(), JsonObject::Keys()); + nullObject.remove("foo"); + QCOMPARE(nullObject, JsonObject()); + QCOMPARE(nullObject.take("foo"), JsonValue(JsonValue::Undefined)); + QCOMPARE(nullObject.contains("foo"), false); +} + +void tst_Json::constNullObject() +{ + const JsonObject nullObject; + JsonObject nonNull; + nonNull.insert("foo", "bar"); + + QCOMPARE(nullObject, JsonObject()); + QVERIFY(nullObject != nonNull); + QVERIFY(nonNull != nullObject); + + QCOMPARE(nullObject.size(), 0); + QCOMPARE(nullObject.keys(), JsonObject::Keys()); + QCOMPARE(nullObject, JsonObject()); + QCOMPARE(nullObject.contains("foo"), false); + QCOMPARE(nullObject["foo"], JsonValue(JsonValue::Undefined)); +} + +void tst_Json::keySorting() +{ + const char *json = "{ \"B\": true, \"A\": false }"; + JsonDocument doc = JsonDocument::fromJson(json); + + QCOMPARE(doc.isObject(), true); + + JsonObject o = doc.object(); + QCOMPARE(o.size(), 2); + JsonObject::const_iterator it = o.constBegin(); + QCOMPARE(it.key(), std::string("A")); + ++it; + QCOMPARE(it.key(), std::string("B")); + + JsonObject::Keys keys; + keys.push_back("A"); + keys.push_back("B"); + QCOMPARE(o.keys(), keys); +} + +void tst_Json::undefinedValues() +{ + JsonObject object; + object.insert("Key", JsonValue(JsonValue::Undefined)); + QCOMPARE(object.size(), 0); + + object.insert("Key", "Value"); + QCOMPARE(object.size(), 1); + QCOMPARE(object.value("Key").type(), JsonValue::String); + QCOMPARE(object.value("foo").type(), JsonValue::Undefined); + object.insert("Key", JsonValue(JsonValue::Undefined)); + QCOMPARE(object.size(), 0); + QCOMPARE(object.value("Key").type(), JsonValue::Undefined); + + JsonArray array; + array.append(JsonValue(JsonValue::Undefined)); + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0).type(), JsonValue::Null); + + QCOMPARE(array.at(1).type(), JsonValue::Undefined); + QCOMPARE(array.at(-1).type(), JsonValue::Undefined); +} + +void tst_Json::toJson() +{ + // Test JsonDocument::Indented format + { + JsonObject object; + object.insert("\\Key\n", "Value"); + object.insert("null", JsonValue()); + JsonArray array; + array.append(true); + array.append(999.); + array.append("string"); + array.append(JsonValue()); + array.append("\\\a\n\r\b\tabcABC\""); + object.insert("Array", array); + + std::string json = JsonDocument(object).toJson(); + + std::string expected = + "{\n" + " \"Array\": [\n" + " true,\n" + " 999,\n" + " \"string\",\n" + " null,\n" + " \"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"\n" + " ],\n" + " \"\\\\Key\\n\": \"Value\",\n" + " \"null\": null\n" + "}\n"; + QCOMPARE(json, expected); + + JsonDocument doc; + doc.setObject(object); + json = doc.toJson(); + QCOMPARE(json, expected); + + doc.setArray(array); + json = doc.toJson(); + expected = + "[\n" + " true,\n" + " 999,\n" + " \"string\",\n" + " null,\n" + " \"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"\n" + "]\n"; + QCOMPARE(json, expected); + } + + // Test JsonDocument::Compact format + { + JsonObject object; + object.insert("\\Key\n", "Value"); + object.insert("null", JsonValue()); + JsonArray array; + array.append(true); + array.append(999.); + array.append("string"); + array.append(JsonValue()); + array.append("\\\a\n\r\b\tabcABC\""); + object.insert("Array", array); + + std::string json = JsonDocument(object).toJson(JsonDocument::Compact); + std::string expected = + "{\"Array\":[true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"],\"\\\\Key\\n\":\"Value\",\"null\":null}"; + QCOMPARE(json, expected); + + JsonDocument doc; + doc.setObject(object); + json = doc.toJson(JsonDocument::Compact); + QCOMPARE(json, expected); + + doc.setArray(array); + json = doc.toJson(JsonDocument::Compact); + expected = "[true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"]"; + QCOMPARE(json, expected); + } +} + +void tst_Json::toJsonSillyNumericValues() +{ + JsonObject object; + JsonArray array; + array.append(JsonValue(std::numeric_limits<double>::infinity())); // encode to: null + array.append(JsonValue(-std::numeric_limits<double>::infinity())); // encode to: null + array.append(JsonValue(std::numeric_limits<double>::quiet_NaN())); // encode to: null + object.insert("Array", array); + + std::string json = JsonDocument(object).toJson(); + + std::string expected = + "{\n" + " \"Array\": [\n" + " null,\n" + " null,\n" + " null\n" + " ]\n" + "}\n"; + + QCOMPARE(json, expected); + + JsonDocument doc; + doc.setObject(object); + json = doc.toJson(); + QCOMPARE(json, expected); +} + +void tst_Json::toJsonLargeNumericValues() +{ + JsonObject object; + JsonArray array; + array.append(JsonValue(1.234567)); // actual precision bug in Qt 5.0.0 + array.append(JsonValue(1.7976931348623157e+308)); // JS Number.MAX_VALUE + array.append(JsonValue(5e-324)); // JS Number.MIN_VALUE + array.append(JsonValue(std::numeric_limits<double>::min())); + array.append(JsonValue(std::numeric_limits<double>::max())); + array.append(JsonValue(std::numeric_limits<double>::epsilon())); + array.append(JsonValue(std::numeric_limits<double>::denorm_min())); + array.append(JsonValue(0.0)); + array.append(JsonValue(-std::numeric_limits<double>::min())); + array.append(JsonValue(-std::numeric_limits<double>::max())); + array.append(JsonValue(-std::numeric_limits<double>::epsilon())); + array.append(JsonValue(-std::numeric_limits<double>::denorm_min())); + array.append(JsonValue(-0.0)); + array.append(JsonValue(int64_t(9007199254740992LL))); // JS Number max integer + array.append(JsonValue(int64_t(-9007199254740992LL))); // JS Number min integer + object.insert("Array", array); + + std::string json = JsonDocument(object).toJson(); + + std::string expected = + "{\n" + " \"Array\": [\n" + " 1.234567,\n" + " 1.7976931348623157e+308,\n" + // ((4.9406564584124654e-324 == 5e-324) == true) + // I can only think JavaScript has a special formatter to + // emit this value for this IEEE754 bit pattern. + " 4.9406564584124654e-324,\n" + " 2.2250738585072014e-308,\n" + " 1.7976931348623157e+308,\n" + " 2.2204460492503131e-16,\n" + " 4.9406564584124654e-324,\n" + " 0,\n" + " -2.2250738585072014e-308,\n" + " -1.7976931348623157e+308,\n" + " -2.2204460492503131e-16,\n" + " -4.9406564584124654e-324,\n" + " 0,\n" + " 9007199254740992,\n" + " -9007199254740992\n" + " ]\n" + "}\n"; + + QCOMPARE(json, expected); + + JsonDocument doc; + doc.setObject(object); + json = doc.toJson(); + QCOMPARE(json, expected); +} + +void tst_Json::fromJson() +{ + { + std::string json = "[\n true\n]\n"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0).type(), JsonValue::Bool); + QCOMPARE(array.at(0).toBool(), true); + QCOMPARE(doc.toJson(), json); + } + { + //regression test: test if unicode_control_characters are correctly decoded + std::string json = "[\n \"" UNICODE_NON_CHARACTER "\"\n]\n"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0).type(), JsonValue::String); + QCOMPARE(array.at(0).toString(), std::string(UNICODE_NON_CHARACTER)); + QCOMPARE(doc.toJson(), json); + } + { + std::string json = "[]"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 0); + } + { + std::string json = "{}"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + JsonObject object = doc.object(); + QCOMPARE(object.size(), 0); + } + { + std::string json = "{\n \"Key\": true\n}\n"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + JsonObject object = doc.object(); + QCOMPARE(object.size(), 1); + QCOMPARE(object.value("Key"), JsonValue(true)); + QCOMPARE(doc.toJson(), json); + } + { + std::string json = "[ null, true, false, \"Foo\", 1, [], {} ]"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 7); + QCOMPARE(array.at(0).type(), JsonValue::Null); + QCOMPARE(array.at(1).type(), JsonValue::Bool); + QCOMPARE(array.at(1).toBool(), true); + QCOMPARE(array.at(2).type(), JsonValue::Bool); + QCOMPARE(array.at(2).toBool(), false); + QCOMPARE(array.at(3).type(), JsonValue::String); + QCOMPARE(array.at(3).toString(), std::string("Foo")); + QCOMPARE(array.at(4).type(), JsonValue::Double); + QCOMPARE(array.at(4).toDouble(), 1.); + QCOMPARE(array.at(5).type(), JsonValue::Array); + QCOMPARE(array.at(5).toArray().size(), 0); + QCOMPARE(array.at(6).type(), JsonValue::Object); + QCOMPARE(array.at(6).toObject().size(), 0); + } + { + std::string json = "{ \"0\": null, \"1\": true, \"2\": false, \"3\": \"Foo\", \"4\": 1, \"5\": [], \"6\": {} }"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + JsonObject object = doc.object(); + QCOMPARE(object.size(), 7); + QCOMPARE(object.value("0").type(), JsonValue::Null); + QCOMPARE(object.value("1").type(), JsonValue::Bool); + QCOMPARE(object.value("1").toBool(), true); + QCOMPARE(object.value("2").type(), JsonValue::Bool); + QCOMPARE(object.value("2").toBool(), false); + QCOMPARE(object.value("3").type(), JsonValue::String); + QCOMPARE(object.value("3").toString(), std::string("Foo")); + QCOMPARE(object.value("4").type(), JsonValue::Double); + QCOMPARE(object.value("4").toDouble(), 1.); + QCOMPARE(object.value("5").type(), JsonValue::Array); + QCOMPARE(object.value("5").toArray().size(), 0); + QCOMPARE(object.value("6").type(), JsonValue::Object); + QCOMPARE(object.value("6").toObject().size(), 0); + } + { + std::string compactJson = "{\"Array\": [true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"],\"\\\\Key\\n\": \"Value\",\"null\": null}"; + JsonDocument doc = JsonDocument::fromJson(compactJson); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + JsonObject object = doc.object(); + QCOMPARE(object.size(), 3); + QCOMPARE(object.value("\\Key\n").isString(), true); + QCOMPARE(object.value("\\Key\n").toString(), std::string("Value")); + QCOMPARE(object.value("null").isNull(), true); + QCOMPARE(object.value("Array").isArray(), true); + JsonArray array = object.value("Array").toArray(); + QCOMPARE(array.size(), 5); + QCOMPARE(array.at(0).isBool(), true); + QCOMPARE(array.at(0).toBool(), true); + QCOMPARE(array.at(1).isDouble(), true); + QCOMPARE(array.at(1).toDouble(), 999.); + QCOMPARE(array.at(2).isString(), true); + QCOMPARE(array.at(2).toString(), std::string("string")); + QCOMPARE(array.at(3).isNull(), true); + QCOMPARE(array.at(4).isString(), true); + QCOMPARE(array.at(4).toString(), std::string("\\\a\n\r\b\tabcABC\"")); + } +} + +void tst_Json::fromJsonErrors() +{ + { + JsonParseError error; + std::string json = "{\n \n\n"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::UnterminatedObject); + QCOMPARE(error.offset, 8); + } + { + JsonParseError error; + std::string json = "{\n \"key\" 10\n"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::MissingNameSeparator); + QCOMPARE(error.offset, 13); + } + { + JsonParseError error; + std::string json = "[\n \n\n"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::UnterminatedArray); + QCOMPARE(error.offset, 8); + } + { + JsonParseError error; + std::string json = "[\n 1, true\n\n"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::UnterminatedArray); + QCOMPARE(error.offset, 14); + } + { + JsonParseError error; + std::string json = "[\n 1 true\n\n"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::MissingValueSeparator); + QCOMPARE(error.offset, 7); + } + { + JsonParseError error; + std::string json = "[\n nul"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalValue); + QCOMPARE(error.offset, 7); + } + { + JsonParseError error; + std::string json = "[\n nulzz"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalValue); + QCOMPARE(error.offset, 10); + } + { + JsonParseError error; + std::string json = "[\n tru"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalValue); + QCOMPARE(error.offset, 7); + } + { + JsonParseError error; + std::string json = "[\n trud]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalValue); + QCOMPARE(error.offset, 10); + } + { + JsonParseError error; + std::string json = "[\n fal"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalValue); + QCOMPARE(error.offset, 7); + } + { + JsonParseError error; + std::string json = "[\n falsd]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalValue); + QCOMPARE(error.offset, 11); + } + { + JsonParseError error; + std::string json = "[\n 11111"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::TerminationByNumber); + QCOMPARE(error.offset, 11); + } + { + JsonParseError error; + std::string json = "[\n -1E10000]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalNumber); + QCOMPARE(error.offset, 14); + } + { + /* + JsonParseError error; + std::string json = "[\n -1e-10000]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalNumber); + QCOMPARE(error.offset, 15); + */ + } + { + JsonParseError error; + std::string json = "[\n \"\\u12\"]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalEscapeSequence); + QCOMPARE(error.offset, 11); + } + { + // This is not caught by the new parser as we don't parse + // UTF-8 anymore, but pass it as opaque blob. +// JsonParseError error; +// std::string json = "[\n \"foo" INVALID_UNICODE "bar\"]"; +// JsonDocument doc = JsonDocument::fromJson(json, &error); +// QVERIFY(doc.isEmpty()); +// QCOMPARE(error.error, JsonParseError::IllegalUTF8String); +// QCOMPARE(error.offset, 12); + } + { + JsonParseError error; + std::string json = "[\n \""; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::UnterminatedString); + QCOMPARE(error.offset, 8); + } + { + JsonParseError error; + std::string json = "[\n \"c" UNICODE_DJE "a\\u12\"]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::IllegalEscapeSequence); + QCOMPARE(error.offset, 15); + } + { + // This is not caught by the new parser as we don't parse + // UTF-8 anymore, but pass it as opaque blob. +// JsonParseError error; +// std::string json = "[\n \"c" UNICODE_DJE "a" INVALID_UNICODE "bar\"]"; +// JsonDocument doc = JsonDocument::fromJson(json, &error); +// QVERIFY(doc.isEmpty()); +// QCOMPARE(error.error, JsonParseError::IllegalUTF8String); +// QCOMPARE(error.offset, 13); + } + { + JsonParseError error; + std::string json = "[\n \"c" UNICODE_DJE "a ]"; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, JsonParseError::UnterminatedString); + QCOMPARE(error.offset, 14); + } +} + +void tst_Json::fromBinary() +{ + QFile file(testDataDir + QLatin1String("/test.json")); + file.open(QFile::ReadOnly); + std::string testJson = file.readAll().data(); + + JsonDocument doc = JsonDocument::fromJson(testJson); + JsonDocument outdoc = JsonDocument::fromBinaryData(doc.toBinaryData()); + QVERIFY(!outdoc.isNull()); + QCOMPARE(doc, outdoc); + +// // Can be used to re-create test.bjson: +// QFile b1file(testDataDir + QLatin1String("/test.bjson.x")); +// b1file.open(QFile::WriteOnly); +// std::string d = doc.toBinaryData(); +// b1file.write(d.data(), d.size()); +// b1file.close(); + + QFile bfile(testDataDir + QLatin1String("/test.bjson")); + bfile.open(QFile::ReadOnly); + std::string binary = bfile.readAll().toStdString(); + + JsonDocument bdoc = JsonDocument::fromBinaryData(binary); + QVERIFY(!bdoc.isNull()); + QCOMPARE(doc, bdoc); +} + +void tst_Json::toAndFromBinary_data() +{ + QTest::addColumn<QString>("filename"); + QTest::newRow("test.json") << (testDataDir + QLatin1String("/test.json")); + QTest::newRow("test2.json") << (testDataDir + QLatin1String("/test2.json")); +} + +void tst_Json::toAndFromBinary() +{ + QFETCH(QString, filename); + QFile file(filename); + QVERIFY(file.open(QFile::ReadOnly)); + std::string data = file.readAll().data(); + + JsonDocument doc = JsonDocument::fromJson(data); + QVERIFY(!doc.isNull()); + JsonDocument outdoc = JsonDocument::fromBinaryData(doc.toBinaryData()); + QVERIFY(!outdoc.isNull()); + QCOMPARE(doc, outdoc); +} + +void tst_Json::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) { + std::string json = "[ "; + json += numbers[i].str; + json += " ]"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + JsonValue val = array.at(0); + QCOMPARE(val.type(), JsonValue::Double); + QCOMPARE(val.toDouble(), (double)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) { + std::string json = "[ "; + json += numbers[i].str; + json += " ]"; + JsonDocument doc = JsonDocument::fromJson(json); +#ifdef Q_OS_QNX + if (0 == QString::compare(numbers[i].str, "1.1e-308")) + QEXPECT_FAIL("", "See QTBUG-37066", Abort); +#endif + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + JsonValue val = array.at(0); + QCOMPARE(val.type(), JsonValue::Double); + QCOMPARE(val.toDouble(), numbers[i].n); + } + } +} + +void tst_Json::parseStrings() +{ + const char *strings [] = + { + "Foo", + "abc\\\"abc", + "abc\\\\abc", + "abc\\babc", + "abc\\fabc", + "abc\\nabc", + "abc\\rabc", + "abc\\tabc", + "abc\\u0019abc", + "abc" UNICODE_DJE "abc", + UNICODE_NON_CHARACTER + }; + int size = sizeof(strings)/sizeof(const char *); + + for (int i = 0; i < size; ++i) { + std::string json = "[\n \""; + json += strings[i]; + json += "\"\n]\n"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + JsonValue val = array.at(0); + QCOMPARE(val.type(), JsonValue::String); + + QCOMPARE(doc.toJson(), json); + } + + struct Pairs { + const char *in; + const char *out; + }; + Pairs pairs [] = { + { "abc\\/abc", "abc/abc" }, + { "abc\\u0402abc", "abc" UNICODE_DJE "abc" }, + { "abc\\u0065abc", "abceabc" }, + { "abc\\uFFFFabc", "abc" UNICODE_NON_CHARACTER "abc" } + }; + size = sizeof(pairs)/sizeof(Pairs); + + for (int i = 0; i < size; ++i) { + std::string json = "[\n \""; + json += pairs[i].in; + json += "\"\n]\n"; + std::string out = "[\n \""; + out += pairs[i].out; + out += "\"\n]\n"; + JsonDocument doc = JsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + JsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + JsonValue val = array.at(0); + QCOMPARE(val.type(), JsonValue::String); + + QCOMPARE(doc.toJson(), out); + } + +} + +void tst_Json::parseDuplicateKeys() +{ + const char *json = "{ \"B\": true, \"A\": null, \"B\": false }"; + + JsonDocument doc = JsonDocument::fromJson(json); + QCOMPARE(doc.isObject(), true); + + JsonObject o = doc.object(); + QCOMPARE(o.size(), 2); + JsonObject::const_iterator it = o.constBegin(); + QCOMPARE(it.key(), std::string("A")); + QCOMPARE(it.value(), JsonValue()); + ++it; + QCOMPARE(it.key(), std::string("B")); + QCOMPARE(it.value(), JsonValue(false)); +} + +void tst_Json::testParser() +{ + QFile file(testDataDir + QLatin1String("/test.json")); + file.open(QFile::ReadOnly); + std::string testJson = file.readAll().data(); + + JsonDocument doc = JsonDocument::fromJson(testJson); + QVERIFY(!doc.isEmpty()); +} + +void tst_Json::compactArray() +{ + JsonArray array; + array.append("First Entry"); + array.append("Second Entry"); + array.append("Third Entry"); + JsonDocument doc(array); + auto s = doc.toBinaryData().size(); + array.removeAt(1); + doc.setArray(array); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + std::string("[\n" + " \"First Entry\",\n" + " \"Third Entry\"\n" + "]\n")); + + array.removeAt(0); + doc.setArray(array); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + std::string("[\n" + " \"Third Entry\"\n" + "]\n")); + + array.removeAt(0); + doc.setArray(array); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + std::string("[\n" + "]\n")); + +} + +void tst_Json::compactObject() +{ + JsonObject object; + object.insert("Key1", "First Entry"); + object.insert("Key2", "Second Entry"); + object.insert("Key3", "Third Entry"); + JsonDocument doc(object); + auto s = doc.toBinaryData().size(); + object.remove("Key2"); + doc.setObject(object); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + std::string("{\n" + " \"Key1\": \"First Entry\",\n" + " \"Key3\": \"Third Entry\"\n" + "}\n")); + + object.remove("Key1"); + doc.setObject(object); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + std::string("{\n" + " \"Key3\": \"Third Entry\"\n" + "}\n")); + + object.remove("Key3"); + doc.setObject(object); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + std::string("{\n" + "}\n")); + +} + +void tst_Json::validation() +{ + // this basically tests that we don't crash on corrupt data + QFile file(testDataDir + QLatin1String("/test.json")); + QVERIFY(file.open(QFile::ReadOnly)); + std::string testJson = file.readAll().data(); + QVERIFY(!testJson.empty()); + + JsonDocument doc = JsonDocument::fromJson(testJson); + QVERIFY(!doc.isNull()); + + std::string binary = doc.toBinaryData(); + + // only test the first 1000 bytes. Testing the full file takes too long + for (int i = 0; i < 1000; ++i) { + std::string corrupted = binary; + corrupted[i] = char(0xff); + JsonDocument doc = JsonDocument::fromBinaryData(corrupted); + if (doc.isNull()) + continue; + std::string json = doc.toJson(); + } + + + QFile file2(testDataDir + QLatin1String("/test3.json")); + file2.open(QFile::ReadOnly); + testJson = file2.readAll().data(); + QVERIFY(!testJson.empty()); + + doc = JsonDocument::fromJson(testJson); + QVERIFY(!doc.isNull()); + + binary = doc.toBinaryData(); + + for (size_t i = 0; i < binary.size(); ++i) { + std::string corrupted = binary; + corrupted[i] = char(0xff); + JsonDocument doc = JsonDocument::fromBinaryData(corrupted); + if (doc.isNull()) + continue; + std::string json = doc.toJson(); + + corrupted = binary; + corrupted[i] = 0x00; + doc = JsonDocument::fromBinaryData(corrupted); + if (doc.isNull()) + continue; + json = doc.toJson(); + } +} + +void tst_Json::assignToDocument() +{ + { + const char *json = "{ \"inner\": { \"key\": true } }"; + JsonDocument doc = JsonDocument::fromJson(json); + + JsonObject o = doc.object(); + JsonValue inner = o.value("inner"); + + JsonDocument innerDoc(inner.toObject()); + + QVERIFY(innerDoc != doc); + QCOMPARE(innerDoc.object(), inner.toObject()); + } + { + const char *json = "[ [ true ] ]"; + JsonDocument doc = JsonDocument::fromJson(json); + + JsonArray a = doc.array(); + JsonValue inner = a.at(0); + + JsonDocument innerDoc(inner.toArray()); + + QVERIFY(innerDoc != doc); + QCOMPARE(innerDoc.array(), inner.toArray()); + } +} + + +void tst_Json::testDuplicateKeys() +{ + JsonObject obj; + obj.insert("foo", "bar"); + obj.insert("foo", "zap"); + QCOMPARE(obj.size(), 1); + QCOMPARE(obj.value("foo").toString(), std::string("zap")); +} + +void tst_Json::testCompaction() +{ + // modify object enough times to trigger compactionCounter + // and make sure the data is still valid + JsonObject obj; + for (int i = 0; i < 33; ++i) { + obj.remove("foo"); + obj.insert("foo", "bar"); + } + QCOMPARE(obj.size(), 1); + QCOMPARE(obj.value("foo").toString(), std::string("bar")); + + JsonDocument doc = JsonDocument::fromBinaryData(JsonDocument(obj).toBinaryData()); + QVERIFY(!doc.isNull()); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QCOMPARE(doc.object(), obj); +} + +void tst_Json::testCompactionError() +{ + JsonObject schemaObject; + schemaObject.insert("_Type", "_SchemaType"); + schemaObject.insert("name", "Address"); + schemaObject.insert("schema", JsonObject()); + { + JsonObject content(schemaObject); + JsonDocument doc(content); + QVERIFY(!doc.isNull()); + QByteArray hash = QCryptographicHash::hash(doc.toBinaryData().data(), QCryptographicHash::Md5).toHex(); + schemaObject.insert("_Version", hash.data()); + } + + JsonObject schema; + schema.insert("streetNumber", schema.value("number").toObject()); + schemaObject.insert("schema", schema); + { + JsonObject content(schemaObject); + content.remove("_Uuid"); + content.remove("_Version"); + JsonDocument doc(content); + QVERIFY(!doc.isNull()); + QByteArray hash = QCryptographicHash::hash(doc.toBinaryData().data(), QCryptographicHash::Md5).toHex(); + schemaObject.insert("_Version", hash.data()); + } +} + +void tst_Json::parseUnicodeEscapes() +{ + const std::string json = "[ \"A\\u00e4\\u00C4\" ]"; + + JsonDocument doc = JsonDocument::fromJson(json); + JsonArray array = doc.array(); + + QString result = QLatin1String("A"); + result += QChar(0xe4); + result += QChar(0xc4); + + std::string expected = result.toUtf8().data(); + + QCOMPARE(array.first().toString(), expected); +} + +void tst_Json::assignObjects() +{ + const char *json = + "[ { \"Key\": 1 }, { \"Key\": 2 } ]"; + + JsonDocument doc = JsonDocument::fromJson(json); + JsonArray array = doc.array(); + + JsonObject object = array.at(0).toObject(); + QCOMPARE(object.value("Key").toDouble(), 1.); + + object = array.at(1).toObject(); + QCOMPARE(object.value("Key").toDouble(), 2.); +} + +void tst_Json::assignArrays() +{ + const char *json = + "[ [ 1 ], [ 2 ] ]"; + + JsonDocument doc = JsonDocument::fromJson(json); + JsonArray array = doc.array(); + + JsonArray inner = array.at(0).toArray() ; + QCOMPARE(inner.at(0).toDouble(), 1.); + + inner= array.at(1).toArray(); + QCOMPARE(inner.at(0).toDouble(), 2.); +} + +void tst_Json::testTrailingComma() +{ + const char *jsons[] = { "{ \"Key\": 1, }", "[ { \"Key\": 1 }, ]" }; + + for (unsigned i = 0; i < sizeof(jsons)/sizeof(jsons[0]); ++i) { + JsonParseError error; + JsonDocument doc = JsonDocument::fromJson(jsons[i], &error); + QCOMPARE(error.error, JsonParseError::MissingObject); + } +} + +void tst_Json::testDetachBug() +{ + JsonObject dynamic; + JsonObject embedded; + + JsonObject local; + + embedded.insert("Key1", "Value1"); + embedded.insert("Key2", "Value2"); + dynamic.insert("Bogus", "bogusValue"); + dynamic.insert("embedded", embedded); + local = dynamic.value("embedded").toObject(); + + dynamic.remove("embedded"); + + QCOMPARE(local.keys().size(), size_t(2)); + local.remove("Key1"); + local.remove("Key2"); + QCOMPARE(local.keys().size(), size_t(0)); + + local.insert("Key1", "anotherValue"); + QCOMPARE(local.keys().size(), size_t(1)); +} + +void tst_Json::valueEquals() +{ + QCOMPARE(JsonValue(), JsonValue()); + QVERIFY(JsonValue() != JsonValue(JsonValue::Undefined)); + QVERIFY(JsonValue() != JsonValue(true)); + QVERIFY(JsonValue() != JsonValue(1.)); + QVERIFY(JsonValue() != JsonValue(JsonArray())); + QVERIFY(JsonValue() != JsonValue(JsonObject())); + + QCOMPARE(JsonValue(true), JsonValue(true)); + QVERIFY(JsonValue(true) != JsonValue(false)); + QVERIFY(JsonValue(true) != JsonValue(JsonValue::Undefined)); + QVERIFY(JsonValue(true) != JsonValue()); + QVERIFY(JsonValue(true) != JsonValue(1.)); + QVERIFY(JsonValue(true) != JsonValue(JsonArray())); + QVERIFY(JsonValue(true) != JsonValue(JsonObject())); + + QCOMPARE(JsonValue(1), JsonValue(1)); + QVERIFY(JsonValue(1) != JsonValue(2)); + QCOMPARE(JsonValue(1), JsonValue(1.)); + QVERIFY(JsonValue(1) != JsonValue(1.1)); + QVERIFY(JsonValue(1) != JsonValue(JsonValue::Undefined)); + QVERIFY(JsonValue(1) != JsonValue()); + QVERIFY(JsonValue(1) != JsonValue(true)); + QVERIFY(JsonValue(1) != JsonValue(JsonArray())); + QVERIFY(JsonValue(1) != JsonValue(JsonObject())); + + QCOMPARE(JsonValue(1.), JsonValue(1.)); + QVERIFY(JsonValue(1.) != JsonValue(2.)); + QVERIFY(JsonValue(1.) != JsonValue(JsonValue::Undefined)); + QVERIFY(JsonValue(1.) != JsonValue()); + QVERIFY(JsonValue(1.) != JsonValue(true)); + QVERIFY(JsonValue(1.) != JsonValue(JsonArray())); + QVERIFY(JsonValue(1.) != JsonValue(JsonObject())); + + QCOMPARE(JsonValue(JsonArray()), JsonValue(JsonArray())); + JsonArray nonEmptyArray; + nonEmptyArray.append(true); + QVERIFY(JsonValue(JsonArray()) != nonEmptyArray); + QVERIFY(JsonValue(JsonArray()) != JsonValue(JsonValue::Undefined)); + QVERIFY(JsonValue(JsonArray()) != JsonValue()); + QVERIFY(JsonValue(JsonArray()) != JsonValue(true)); + QVERIFY(JsonValue(JsonArray()) != JsonValue(1.)); + QVERIFY(JsonValue(JsonArray()) != JsonValue(JsonObject())); + + QCOMPARE(JsonValue(JsonObject()), JsonValue(JsonObject())); + JsonObject nonEmptyObject; + nonEmptyObject.insert("Key", true); + QVERIFY(JsonValue(JsonObject()) != nonEmptyObject); + QVERIFY(JsonValue(JsonObject()) != JsonValue(JsonValue::Undefined)); + QVERIFY(JsonValue(JsonObject()) != JsonValue()); + QVERIFY(JsonValue(JsonObject()) != JsonValue(true)); + QVERIFY(JsonValue(JsonObject()) != JsonValue(1.)); + QVERIFY(JsonValue(JsonObject()) != JsonValue(JsonArray())); + + QCOMPARE(JsonValue("foo"), JsonValue("foo")); + QCOMPARE(JsonValue("\x66\x6f\x6f"), JsonValue("foo")); + QCOMPARE(JsonValue("\x62\x61\x72"), JsonValue("bar")); + QCOMPARE(JsonValue(UNICODE_NON_CHARACTER), JsonValue(UNICODE_NON_CHARACTER)); + QCOMPARE(JsonValue(UNICODE_DJE), JsonValue(UNICODE_DJE)); + QCOMPARE(JsonValue("\xc3\xa9"), JsonValue("\xc3\xa9")); +} + +void tst_Json::objectEquals_data() +{ + QTest::addColumn<JsonObject>("left"); + QTest::addColumn<JsonObject>("right"); + QTest::addColumn<bool>("result"); + + QTest::newRow("two defaults") << JsonObject() << JsonObject() << true; + + JsonObject object1; + object1.insert("property", 1); + JsonObject object2; + object2["property"] = 1; + JsonObject object3; + object3.insert("property1", 1); + object3.insert("property2", 2); + + QTest::newRow("the same object (1 vs 2)") << object1 << object2 << true; + QTest::newRow("the same object (3 vs 3)") << object3 << object3 << true; + QTest::newRow("different objects (2 vs 3)") << object2 << object3 << false; + QTest::newRow("object vs default") << object1 << JsonObject() << false; + + JsonObject empty; + empty.insert("property", 1); + empty.take("property"); + QTest::newRow("default vs empty") << JsonObject() << empty << true; + QTest::newRow("empty vs empty") << empty << empty << true; + QTest::newRow("object vs empty") << object1 << empty << false; + + JsonObject referencedEmpty; + referencedEmpty["undefined"]; + QTest::newRow("referenced empty vs referenced empty") << referencedEmpty << referencedEmpty << true; + QTest::newRow("referenced empty vs object") << referencedEmpty << object1 << false; + + JsonObject referencedObject1; + referencedObject1.insert("property", 1); + referencedObject1["undefined"]; + JsonObject referencedObject2; + referencedObject2.insert("property", 1); + referencedObject2["aaaaaaaaa"]; // earlier then "property" + referencedObject2["zzzzzzzzz"]; // after "property" + QTest::newRow("referenced object vs default") << referencedObject1 << JsonObject() << false; + QTest::newRow("referenced object vs referenced object") << referencedObject1 << referencedObject1 << true; + QTest::newRow("referenced object vs object (different)") << referencedObject1 << object3 << false; +} + +void tst_Json::objectEquals() +{ + QFETCH(JsonObject, left); + QFETCH(JsonObject, right); + QFETCH(bool, result); + + QCOMPARE(left == right, result); + QCOMPARE(right == left, result); + + // invariants checks + QCOMPARE(left, left); + QCOMPARE(right, right); + QCOMPARE(left != right, !result); + QCOMPARE(right != left, !result); + + // The same but from JsonValue perspective + QCOMPARE(JsonValue(left) == JsonValue(right), result); + QCOMPARE(JsonValue(left) != JsonValue(right), !result); + QCOMPARE(JsonValue(right) == JsonValue(left), result); + QCOMPARE(JsonValue(right) != JsonValue(left), !result); +} + +void tst_Json::arrayEquals_data() +{ + QTest::addColumn<JsonArray>("left"); + QTest::addColumn<JsonArray>("right"); + QTest::addColumn<bool>("result"); + + QTest::newRow("two defaults") << JsonArray() << JsonArray() << true; + + JsonArray array1; + array1.append(1); + JsonArray array2; + array2.append(2111); + array2[0] = 1; + JsonArray array3; + array3.insert(0, 1); + array3.insert(1, 2); + + QTest::newRow("the same array (1 vs 2)") << array1 << array2 << true; + QTest::newRow("the same array (3 vs 3)") << array3 << array3 << true; + QTest::newRow("different arrays (2 vs 3)") << array2 << array3 << false; + QTest::newRow("array vs default") << array1 << JsonArray() << false; + + JsonArray empty; + empty.append(1); + empty.takeAt(0); + QTest::newRow("default vs empty") << JsonArray() << empty << true; + QTest::newRow("empty vs default") << empty << JsonArray() << true; + QTest::newRow("empty vs empty") << empty << empty << true; + QTest::newRow("array vs empty") << array1 << empty << false; +} + +void tst_Json::arrayEquals() +{ + QFETCH(JsonArray, left); + QFETCH(JsonArray, right); + QFETCH(bool, result); + + QCOMPARE(left == right, result); + QCOMPARE(right == left, result); + + // invariants checks + QCOMPARE(left, left); + QCOMPARE(right, right); + QCOMPARE(left != right, !result); + QCOMPARE(right != left, !result); + + // The same but from JsonValue perspective + QCOMPARE(JsonValue(left) == JsonValue(right), result); + QCOMPARE(JsonValue(left) != JsonValue(right), !result); + QCOMPARE(JsonValue(right) == JsonValue(left), result); + QCOMPARE(JsonValue(right) != JsonValue(left), !result); +} + +void tst_Json::bom() +{ + QFile file(testDataDir + QLatin1String("/bom.json")); + file.open(QFile::ReadOnly); + std::string json = file.readAll().data(); + + // Import json document into a JsonDocument + JsonParseError error; + JsonDocument doc = JsonDocument::fromJson(json, &error); + + QVERIFY(!doc.isNull()); + QCOMPARE(error.error, JsonParseError::NoError); +} + +void tst_Json::nesting() +{ + // check that we abort parsing too deeply nested json documents. + // this is to make sure we don't crash because the parser exhausts the + // stack. + + const char *array_data = + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"; + + std::string json(array_data); + JsonParseError error; + JsonDocument doc = JsonDocument::fromJson(json, &error); + + QVERIFY(!doc.isNull()); + QCOMPARE(error.error, JsonParseError::NoError); + + json = '[' + json + ']'; + doc = JsonDocument::fromJson(json, &error); + + QVERIFY(doc.isNull()); + QCOMPARE(error.error, JsonParseError::DeepNesting); + + json = std::string("true "); + + for (int i = 0; i < 1024; ++i) + json = "{ \"Key\": " + json + " }"; + + doc = JsonDocument::fromJson(json, &error); + + QVERIFY(!doc.isNull()); + QCOMPARE(error.error, JsonParseError::NoError); + + json = '[' + json + ']'; + doc = JsonDocument::fromJson(json, &error); + + QVERIFY(doc.isNull()); + QCOMPARE(error.error, JsonParseError::DeepNesting); + +} + +void tst_Json::longStrings() +{ +#if 0 + // test around 15 and 16 bit boundaries, as these are limits + // in the data structures (for Latin1String in qjson_p.h) + QString s(0x7ff0, 'a'); + for (int i = 0x7ff0; i < 0x8010; i++) { + s.append(QLatin1Char('c')); + + QMap <QString, QVariant> map; + map["key"] = s; + + /* Create a JsonDocument from the QMap ... */ + JsonDocument d1 = JsonDocument::fromVariant(QVariant(map)); + /* ... and a std::string from the JsonDocument */ + std::string a1 = d1.toJson(); + + /* Create a JsonDocument from the std::string ... */ + JsonDocument d2 = JsonDocument::fromJson(a1); + /* ... and a std::string from the JsonDocument */ + std::string a2 = d2.toJson(); + QCOMPARE(a1, a2); + } + + s = QString(0xfff0, 'a'); + for (int i = 0xfff0; i < 0x10010; i++) { + s.append(QLatin1Char('c')); + + QMap <QString, QVariant> map; + map["key"] = s; + + /* Create a JsonDocument from the QMap ... */ + JsonDocument d1 = JsonDocument::fromVariant(QVariant(map)); + /* ... and a std::string from the JsonDocument */ + std::string a1 = d1.toJson(); + + /* Create a JsonDocument from the std::string ... */ + JsonDocument d2 = JsonDocument::fromJson(a1); + /* ... and a std::string from the JsonDocument */ + std::string a2 = d2.toJson(); + QCOMPARE(a1, a2); + } +#endif +} + +void tst_Json::testJsonValueRefDefault() +{ + JsonObject empty; + + QCOMPARE(empty["n/a"].toString(), std::string()); + QCOMPARE(empty["n/a"].toString("default"), std::string("default")); + + QCOMPARE(empty["n/a"].toBool(), false); + QCOMPARE(empty["n/a"].toBool(true), true); + + QCOMPARE(empty["n/a"].toInt(), 0); + QCOMPARE(empty["n/a"].toInt(42), 42); + + QCOMPARE(empty["n/a"].toDouble(), 0.0); + QCOMPARE(empty["n/a"].toDouble(42.0), 42.0); +} + +void tst_Json::arrayInitializerList() +{ +#ifndef Q_COMPILER_INITIALIZER_LISTS + QSKIP("initializer_list is enabled only with c++11 support"); +#else + QVERIFY(JsonArray{}.isEmpty()); + QCOMPARE(JsonArray{"one"}.count(), 1); + QCOMPARE(JsonArray{1}.count(), 1); + + { + JsonArray a{1.3, "hello", 0}; + QCOMPARE(JsonValue(a[0]), JsonValue(1.3)); + QCOMPARE(JsonValue(a[1]), JsonValue("hello")); + QCOMPARE(JsonValue(a[2]), JsonValue(0)); + QCOMPARE(a.count(), 3); + } + { + JsonObject o; + o["property"] = 1; + JsonArray a1 {o}; + QCOMPARE(a1.count(), 1); + QCOMPARE(a1[0].toObject(), o); + + JsonArray a2 {o, 23}; + QCOMPARE(a2.count(), 2); + QCOMPARE(a2[0].toObject(), o); + QCOMPARE(JsonValue(a2[1]), JsonValue(23)); + + JsonArray a3 { a1, o, a2 }; + QCOMPARE(JsonValue(a3[0]), JsonValue(a1)); + QCOMPARE(JsonValue(a3[1]), JsonValue(o)); + QCOMPARE(JsonValue(a3[2]), JsonValue(a2)); + + JsonArray a4 { 1, JsonArray{1,2,3}, JsonArray{"hello", 2}, JsonObject{{"one", 1}} }; + QCOMPARE(a4.count(), 4); + QCOMPARE(JsonValue(a4[0]), JsonValue(1)); + + { + JsonArray a41 = a4[1].toArray(); + JsonArray a42 = a4[2].toArray(); + JsonObject a43 = a4[3].toObject(); + QCOMPARE(a41.count(), 3); + QCOMPARE(a42.count(), 2); + QCOMPARE(a43.count(), 1); + + QCOMPARE(JsonValue(a41[2]), JsonValue(3)); + QCOMPARE(JsonValue(a42[1]), JsonValue(2)); + QCOMPARE(JsonValue(a43["one"]), JsonValue(1)); + } + } +#endif +} + +void tst_Json::objectInitializerList() +{ +#ifndef Q_COMPILER_INITIALIZER_LISTS + QSKIP("initializer_list is enabled only with c++11 support"); +#else + QVERIFY(JsonObject{}.isEmpty()); + + { // one property + JsonObject one {{"one", 1}}; + QCOMPARE(one.count(), 1); + QVERIFY(one.contains("one")); + QCOMPARE(JsonValue(one["one"]), JsonValue(1)); + } + { // two properties + JsonObject two { + {"one", 1}, + {"two", 2} + }; + QCOMPARE(two.count(), 2); + QVERIFY(two.contains("one")); + QVERIFY(two.contains("two")); + QCOMPARE(JsonValue(two["one"]), JsonValue(1)); + QCOMPARE(JsonValue(two["two"]), JsonValue(2)); + } + { // nested object + JsonObject object{{"nested", JsonObject{{"innerProperty", 2}}}}; + QCOMPARE(object.count(), 1); + QVERIFY(object.contains("nested")); + QVERIFY(object["nested"].isObject()); + + JsonObject nested = object["nested"].toObject(); + QCOMPARE(JsonValue(nested["innerProperty"]), JsonValue(2)); + } + { // nested array + JsonObject object{{"nested", JsonArray{"innerValue", 2.1, "bum cyk cyk"}}}; + QCOMPARE(object.count(), 1); + QVERIFY(object.contains("nested")); + QVERIFY(object["nested"].isArray()); + + JsonArray nested = object["nested"].toArray(); + QCOMPARE(nested.count(), 3); + QCOMPARE(JsonValue(nested[0]), JsonValue("innerValue")); + QCOMPARE(JsonValue(nested[1]), JsonValue(2.1)); + } +#endif +} + +void tst_Json::unicodeKeys() +{ + std::string json = "{" + "\"x\\u2090_1\": \"hello_1\"," + "\"y\\u2090_2\": \"hello_2\"," + "\"T\\u2090_3\": \"hello_3\"," + "\"xyz_4\": \"hello_4\"," + "\"abc_5\": \"hello_5\"" + "}"; + + JsonParseError error; + JsonDocument doc = JsonDocument::fromJson(json, &error); + QCOMPARE(error.error, JsonParseError::NoError); + JsonObject o = doc.object(); + + QCOMPARE(o.keys().size(), size_t(5)); + Q_FOREACH (const std::string &k, o.keys()) { + QByteArray key(k.data()); + std::string suffix = key.mid(key.indexOf('_')).data(); + QCOMPARE(o[key.data()].toString(), "hello" + suffix); + } +} + +void tst_Json::garbageAtEnd() +{ + JsonParseError error; + JsonDocument doc = JsonDocument::fromJson("{},", &error); + QCOMPARE(error.error, JsonParseError::GarbageAtEnd); + QCOMPARE(error.offset, 2); + QVERIFY(doc.isEmpty()); + + doc = JsonDocument::fromJson("{} ", &error); + QCOMPARE(error.error, JsonParseError::NoError); + QVERIFY(!doc.isEmpty()); +} + +void tst_Json::removeNonLatinKey() +{ + const std::string nonLatinKeyName = "Атрибут100500"; + + JsonObject sourceObject; + + sourceObject.insert("code", 1); + sourceObject.remove("code"); + + sourceObject.insert(nonLatinKeyName, 1); + + const std::string json = JsonDocument(sourceObject).toJson(); + const JsonObject restoredObject = JsonDocument::fromJson(json).object(); + + QCOMPARE(sourceObject.keys(), restoredObject.keys()); + QVERIFY(sourceObject.contains(nonLatinKeyName)); + QVERIFY(restoredObject.contains(nonLatinKeyName)); +} + +QTEST_MAIN(tst_Json) + +#include "tst_json.moc" diff --git a/tests/benchmarks/json/json.pro b/tests/benchmarks/json/json.pro new file mode 100644 index 00000000000..93569682659 --- /dev/null +++ b/tests/benchmarks/json/json.pro @@ -0,0 +1,14 @@ +TARGET = tst_bench_json +QT = core testlib +CONFIG -= app_bundle + +SOURCES += tst_bench_json.cpp + +TESTDATA = numbers.json test.json + + +INCLUDEPATH += ../../../src/shared/json + +#DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII + +include(../../../src/shared/json/json.pri) diff --git a/tests/benchmarks/json/numbers.json b/tests/benchmarks/json/numbers.json new file mode 100644 index 00000000000..469156a78a3 --- /dev/null +++ b/tests/benchmarks/json/numbers.json @@ -0,0 +1,19 @@ +[ + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1 + }, + [ + -1234567890, + -1234567890, + -1234567890, + 1234567890, + 1234567890, + 1234567890 + ] +] diff --git a/tests/benchmarks/json/test.json b/tests/benchmarks/json/test.json new file mode 100644 index 00000000000..330756894a6 --- /dev/null +++ b/tests/benchmarks/json/test.json @@ -0,0 +1,66 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwxyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={\':[,]}|;.</>?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "nix", + "comment": "// /* <!-- --", + "# -- --> */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?" : "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00, +2e+00, +2e-00, +"rosebud", +{"foo": "bar"}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}}, +{"classification":{"relevancyScore":1000,"searchUrl":{"value":"nix"}},"products":{"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$4,833.99","integral":483399}},"product":[{"type":"PRODUCT","title":"Silicone c","description":"Elite Hori","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":310711221747712.000000,"priceSet":{"minPrice":{"value":"$1.56","integral":156},"maxPrice":{"value":"$29.99","integral":2999},"stores":14},"id":1968262863,"categoryId":8515},{"type":"PRODUCT","title":"Nonslip Ch","description":"Specificat","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":175580930637824.000000,"priceSet":{"minPrice":{"value":"$0.45","integral":45},"maxPrice":{"value":"$194.95","integral":19495},"stores":34},"id":2534935499,"categoryId":8515},{"type":"PRODUCT","title":"Plastic Ca","description":"Descriptio","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":132488642953216.000000,"priceSet":{"minPrice":{"value":"$0.99","integral":99},"maxPrice":{"value":"$303.68","integral":30368},"stores":33},"id":2305624670,"categoryId":8515},{"type":"PRODUCT","title":"Protective","description":"Made of hi","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":108614681362432.000000,"priceSet":{"minPrice":{"value":"$1.70","integral":170},"maxPrice":{"value":"$99.99","integral":9999},"stores":11},"id":2120981405,"categoryId":8515},{"type":"PRODUCT","title":"P® 4","description":"Do more th","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":96203484168192.000000,"priceSet":{"minPrice":{"value":"$2.49","integral":249},"maxPrice":{"value":"$79.95","integral":7995},"stores":16},"id":2203798762,"categoryId":8515},{"type":"PRODUCT","title":"Case Refle","description":"NCAA iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":84727583211520.000000,"priceSet":{"minPrice":{"value":"$0.69","integral":69},"maxPrice":{"value":"$75.52","integral":7552},"stores":59},"id":1114627445,"categoryId":8515},{"type":"PRODUCT","title":"Infuse Pro","description":"Protect an","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":80831066406912.000000,"priceSet":{"minPrice":{"value":"$0.59","integral":59},"maxPrice":{"value":"$79.00","integral":7900},"stores":24},"id":2557462717,"categoryId":8515},{"type":"PRODUCT","title":"Dragonfly ","description":"d","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":70900229603328.000000,"priceSet":{"minPrice":{"value":"$1.05","integral":105},"maxPrice":{"value":"$94.49","integral":9449},"stores":30},"id":2442061740,"categoryId":8515},{"type":"PRODUCT","title":"Pho","description":"Snap on Ap","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":65194915004416.000000,"priceSet":{"minPrice":{"value":"$0.01","integral":1},"maxPrice":{"value":"$414.99","integral":41499},"stores":39},"id":2004746863,"categoryId":8515},{"type":"PRODUCT","title":"Otterbox i","description":"Your iPhon","manufacturer":"someone","url":{"value":"nix"},"images":{"image":[{"value":"nix","xsize":60,"ysize":60},{"value":"nix","xsize":100,"ysize":100},{"value":"nix","xsize":160,"ysize":160},{"value":"nix","xsize":400,"ysize":400}]},"relevancy":61515478597632.000000,"priceSet":{"minPrice":{"value":"$3.28","integral":328},"maxPrice":{"value":"$110.65","integral":11065},"stores":25},"id":2584611575,"categoryId":8515}],"includedResults":10,"totalResults":2000}} +] + diff --git a/tests/benchmarks/json/tst_bench_json.cpp b/tests/benchmarks/json/tst_bench_json.cpp new file mode 100644 index 00000000000..02481950641 --- /dev/null +++ b/tests/benchmarks/json/tst_bench_json.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> +#include <qjsondocument.h> +#include <qjsonobject.h> + +#include <json.h> + +using namespace Json; + +class BenchmarkJson: public QObject +{ + Q_OBJECT + +public: + BenchmarkJson() {} + +private Q_SLOTS: + void jsonObjectInsertQt(); + void jsonObjectInsertStd(); + + void createBinaryMessageQt(); + void createBinaryMessageStd(); + + void readBinaryMessageQt(); + void readBinaryMessageStd(); + + void createTextMessageQt(); + void createTextMessageStd(); + + void readTextMessageQt(); + void readTextMessageStd(); + + void parseJsonQt(); + void parseJsonStd(); + + void parseNumbersQt(); + void parseNumbersStd(); +}; + +void BenchmarkJson::parseNumbersQt() +{ + QString testFile = QFINDTESTDATA("numbers.json"); + QVERIFY2(!testFile.isEmpty(), "cannot find test file numbers.json!"); + QFile file(testFile); + file.open(QFile::ReadOnly); + QByteArray testJson = file.readAll(); + + QBENCHMARK { + QJsonDocument doc = QJsonDocument::fromJson(testJson); + doc.object(); + } +} + +void BenchmarkJson::parseNumbersStd() +{ + QString testFile = QFINDTESTDATA("numbers.json"); + QVERIFY2(!testFile.isEmpty(), "cannot find test file numbers.json!"); + QFile file(testFile); + file.open(QFile::ReadOnly); + std::string testJson = file.readAll().toStdString(); + + QBENCHMARK { + JsonDocument doc = JsonDocument::fromJson(testJson); + doc.object(); + } +} + +void BenchmarkJson::parseJsonQt() +{ + QString testFile = QFINDTESTDATA("test.json"); + QVERIFY2(!testFile.isEmpty(), "cannot find test file test.json!"); + QFile file(testFile); + file.open(QFile::ReadOnly); + QByteArray testJson = file.readAll(); + + QBENCHMARK { + QJsonDocument doc = QJsonDocument::fromJson(testJson); + doc.object(); + } +} + +void BenchmarkJson::parseJsonStd() +{ + QString testFile = QFINDTESTDATA("test.json"); + QVERIFY2(!testFile.isEmpty(), "cannot find test file test.json!"); + QFile file(testFile); + file.open(QFile::ReadOnly); + std::string testJson = file.readAll().toStdString(); + + QBENCHMARK { + JsonDocument doc = JsonDocument::fromJson(testJson); + doc.object(); + } +} + +void BenchmarkJson::createBinaryMessageQt() +{ + // Example: send information over a datastream to another process + // Measure performance of creating and processing data into bytearray + QBENCHMARK { + QJsonObject ob; + ob.insert(QStringLiteral("command"), 1); + ob.insert(QStringLiteral("key"), "some information"); + ob.insert(QStringLiteral("env"), "some environment variables"); + QJsonDocument(ob).toBinaryData(); + } +} + +void BenchmarkJson::createBinaryMessageStd() +{ + // Example: send information over a datastream to another process + // Measure performance of creating and processing data into bytearray + QBENCHMARK { + JsonObject ob; + ob.insert("command", 1); + ob.insert("key", "some information"); + ob.insert("env", "some environment variables"); + JsonDocument(ob).toBinaryData(); + } +} + +void BenchmarkJson::readBinaryMessageQt() +{ + // Example: receive information over a datastream from another process + // Measure performance of converting content back to QVariantMap + // We need to recreate the bytearray but here we only want to measure the latter + QJsonObject ob; + ob.insert(QStringLiteral("command"), 1); + ob.insert(QStringLiteral("key"), "some information"); + ob.insert(QStringLiteral("env"), "some environment variables"); + QByteArray msg = QJsonDocument(ob).toBinaryData(); + + QBENCHMARK { + QJsonDocument::fromBinaryData(msg, QJsonDocument::Validate).object(); + } +} + +void BenchmarkJson::readBinaryMessageStd() +{ + // Example: receive information over a datastream from another process + // Measure performance of converting content back to QVariantMap + // We need to recreate the bytearray but here we only want to measure the latter + JsonObject ob; + ob.insert("command", 1); + ob.insert("key", "some information"); + ob.insert("env", "some environment variables"); + std::string msg = JsonDocument(ob).toBinaryData(); + + QBENCHMARK { + JsonDocument::fromBinaryData(msg, JsonDocument::Validate).object(); + } +} + +void BenchmarkJson::createTextMessageQt() +{ + // Example: send information over a datastream to another process + // Measure performance of creating and processing data into bytearray + QBENCHMARK { + QJsonObject ob; + ob.insert(QStringLiteral("command"), 1); + ob.insert(QStringLiteral("key"), "some information"); + ob.insert(QStringLiteral("env"), "some environment variables"); + QByteArray msg = QJsonDocument(ob).toJson(); + } +} + +void BenchmarkJson::createTextMessageStd() +{ + // Example: send information over a datastream to another process + // Measure performance of creating and processing data into bytearray + QBENCHMARK { + JsonObject ob; + ob.insert("command", 1); + ob.insert("key", "some information"); + ob.insert("env", "some environment variables"); + std::string msg = JsonDocument(ob).toJson(); + } +} + +void BenchmarkJson::readTextMessageQt() +{ + // Example: receive information over a datastream from another process + // Measure performance of converting content back to QVariantMap + // We need to recreate the bytearray but here we only want to measure the latter + QJsonObject ob; + ob.insert(QStringLiteral("command"), 1); + ob.insert(QStringLiteral("key"), "some information"); + ob.insert(QStringLiteral("env"), "some environment variables"); + QByteArray msg = QJsonDocument(ob).toJson(); + + QBENCHMARK { + QJsonDocument::fromJson(msg).object(); + } +} + +void BenchmarkJson::readTextMessageStd() +{ + // Example: receive information over a datastream from another process + // Measure performance of converting content back to QVariantMap + // We need to recreate the bytearray but here we only want to measure the latter + JsonObject ob; + ob.insert("command", 1); + ob.insert("key", "some information"); + ob.insert("env", "some environment variables"); + std::string msg = JsonDocument(ob).toJson(); + + QBENCHMARK { + JsonDocument::fromJson(msg).object(); + } +} + +void BenchmarkJson::jsonObjectInsertQt() +{ + QJsonObject object; + QJsonValue value(1.5); + + QBENCHMARK { + for (int i = 0; i < 1000; i++) + object.insert("testkey_" + QString::number(i), value); + } +} + +void BenchmarkJson::jsonObjectInsertStd() +{ + JsonObject object; + JsonValue value(1.5); + + QBENCHMARK { + for (int i = 0; i < 1000; i++) + object.insert("testkey_" + std::to_string(i), value); + } +} + +QTEST_MAIN(BenchmarkJson) + +#include "tst_bench_json.moc" + |