blob: ce759111f4ee3396fe6310d0334089932554235a [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://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 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qv4jsonobject_p.h>
#include <qv4objectproto_p.h>
#include <qv4numberobject_p.h>
#include <qv4stringobject_p.h>
#include <qv4booleanobject_p.h>
#include <qv4objectiterator_p.h>
#include <qv4scopedvalue_p.h>
#include <qv4runtime_p.h>
#include <qv4variantobject_p.h>
#include "qv4string_p.h"
#include "qv4jscall_p.h"
#include <qv4symbol_p.h>
#include <qstack.h>
#include <qstringlist.h>
#include <wtf/MathExtras.h>
using namespace QV4;
//#define PARSER_DEBUG
#ifdef PARSER_DEBUG
static int indent = 0;
#define BEGIN qDebug() << QByteArray(4*indent++, ' ').constData()
#define END --indent
#define DEBUG qDebug() << QByteArray(4*indent, ' ').constData()
#else
#define BEGIN if (1) ; else qDebug()
#define END do {} while (0)
#define DEBUG if (1) ; else qDebug()
#endif
DEFINE_OBJECT_VTABLE(JsonObject);
static const int nestingLimit = 1024;
JsonParser::JsonParser(ExecutionEngine *engine, const QChar *json, int length)
: engine(engine), head(json), json(json), nestingLevel(0), lastError(QJsonParseError::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
};
bool JsonParser::eatSpace()
{
while (json < end) {
if (*json > Space)
break;
if (*json != Space &&
*json != Tab &&
*json != LineFeed &&
*json != Return)
break;
++json;
}
return (json < end);
}
QChar JsonParser::nextToken()
{
if (!eatSpace())
return 0;
QChar token = *json++;
switch (token.unicode()) {
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
*/
ReturnedValue JsonParser::parse(QJsonParseError *error)
{
#ifdef PARSER_DEBUG
indent = 0;
qDebug() << ">>>>> parser begin";
#endif
eatSpace();
Scope scope(engine);
ScopedValue v(scope);
if (!parseValue(v)) {
#ifdef PARSER_DEBUG
qDebug() << ">>>>> parser error";
#endif
if (lastError == QJsonParseError::NoError)
lastError = QJsonParseError::IllegalValue;
error->offset = json - head;
error->error = lastError;
return Encode::undefined();
}
// some input left...
if (eatSpace()) {
lastError = QJsonParseError::IllegalValue;
error->offset = json - head;
error->error = lastError;
return Encode::undefined();
}
END;
error->offset = 0;
error->error = QJsonParseError::NoError;
return v->asReturnedValue();
}
/*
object = begin-object [ member *( value-separator member ) ]
end-object
*/
ReturnedValue JsonParser::parseObject()
{
if (++nestingLevel > nestingLimit) {
lastError = QJsonParseError::DeepNesting;
return Encode::undefined();
}
BEGIN << "parseObject pos=" << json;
Scope scope(engine);
ScopedObject o(scope, engine->newObject());
QChar token = nextToken();
while (token == Quote) {
if (!parseMember(o))
return Encode::undefined();
token = nextToken();
if (token != ValueSeparator)
break;
token = nextToken();
if (token == EndObject) {
lastError = QJsonParseError::MissingObject;
return Encode::undefined();
}
}
DEBUG << "end token=" << token;
if (token != EndObject) {
lastError = QJsonParseError::UnterminatedObject;
return Encode::undefined();
}
END;
--nestingLevel;
return o.asReturnedValue();
}
/*
member = string name-separator value
*/
bool JsonParser::parseMember(Object *o)
{
BEGIN << "parseMember";
Scope scope(engine);
QString key;
if (!parseString(&key))
return false;
QChar token = nextToken();
if (token != NameSeparator) {
lastError = QJsonParseError::MissingNameSeparator;
return false;
}
ScopedValue val(scope);
if (!parseValue(val))
return false;
ScopedString s(scope, engine->newString(key));
PropertyKey skey = s->toPropertyKey();
if (skey.isArrayIndex()) {
o->put(skey.asArrayIndex(), val);
} else {
// avoid trouble with properties named __proto__
o->insertMember(s, val);
}
END;
return true;
}
/*
array = begin-array [ value *( value-separator value ) ] end-array
*/
ReturnedValue JsonParser::parseArray()
{
Scope scope(engine);
BEGIN << "parseArray";
ScopedArrayObject array(scope, engine->newArrayObject());
if (++nestingLevel > nestingLimit) {
lastError = QJsonParseError::DeepNesting;
return Encode::undefined();
}
if (!eatSpace()) {
lastError = QJsonParseError::UnterminatedArray;
return Encode::undefined();
}
if (*json == EndArray) {
nextToken();
} else {
uint index = 0;
while (1) {
ScopedValue val(scope);
if (!parseValue(val))
return Encode::undefined();
array->arraySet(index, val);
QChar token = nextToken();
if (token == EndArray)
break;
else if (token != ValueSeparator) {
if (!eatSpace())
lastError = QJsonParseError::UnterminatedArray;
else
lastError = QJsonParseError::MissingValueSeparator;
return Encode::undefined();
}
++index;
}
}
DEBUG << "size =" << array->getLength();
END;
--nestingLevel;
return array.asReturnedValue();
}
/*
value = false / null / true / object / array / number / string
*/
bool JsonParser::parseValue(Value *val)
{
BEGIN << "parse Value" << *json;
switch ((json++)->unicode()) {
case 'n':
if (end - json < 3) {
lastError = QJsonParseError::IllegalValue;
return false;
}
if (*json++ == 'u' &&
*json++ == 'l' &&
*json++ == 'l') {
*val = Value::nullValue();
DEBUG << "value: null";
END;
return true;
}
lastError = QJsonParseError::IllegalValue;
return false;
case 't':
if (end - json < 3) {
lastError = QJsonParseError::IllegalValue;
return false;
}
if (*json++ == 'r' &&
*json++ == 'u' &&
*json++ == 'e') {
*val = Value::fromBoolean(true);
DEBUG << "value: true";
END;
return true;
}
lastError = QJsonParseError::IllegalValue;
return false;
case 'f':
if (end - json < 4) {
lastError = QJsonParseError::IllegalValue;
return false;
}
if (*json++ == 'a' &&
*json++ == 'l' &&
*json++ == 's' &&
*json++ == 'e') {
*val = Value::fromBoolean(false);
DEBUG << "value: false";
END;
return true;
}
lastError = QJsonParseError::IllegalValue;
return false;
case Quote: {
QString value;
if (!parseString(&value))
return false;
DEBUG << "value: string";
END;
*val = Value::fromHeapObject(engine->newString(value));
return true;
}
case BeginArray: {
*val = parseArray();
if (val->isUndefined())
return false;
DEBUG << "value: array";
END;
return true;
}
case BeginObject: {
*val = parseObject();
if (val->isUndefined())
return false;
DEBUG << "value: object";
END;
return true;
}
case EndArray:
lastError = QJsonParseError::MissingObject;
return false;
default:
--json;
if (!parseNumber(val))
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 JsonParser::parseNumber(Value *val)
{
BEGIN << "parseNumber" << *json;
const QChar *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;
}
QString number(start, json - start);
DEBUG << "numberstring" << number;
if (isInt) {
bool ok;
int n = number.toInt(&ok);
if (ok && n < (1<<25) && n > -(1<<25)) {
*val = Value::fromInt32(n);
END;
return true;
}
}
bool ok;
double d;
d = number.toDouble(&ok);
if (!ok) {
lastError = QJsonParseError::IllegalNumber;
return false;
}
* val = Value::fromDouble(d);
END;
return true;
}
/*
string = quotation-mark *char quotation-mark
char = unescaped /
escape (
%x22 / ; " quotation mark U+0022
%x5C / ; \ reverse solidus U+005C
%x2F / ; / solidus U+002F
%x62 / ; b backspace U+0008
%x66 / ; f form feed U+000C
%x6E / ; n line feed U+000A
%x72 / ; r carriage return U+000D
%x74 / ; t tab U+0009
%x75 4HEXDIG ) ; uXXXX U+XXXX
escape = %x5C ; \
quotation-mark = %x22 ; "
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
*/
static inline bool addHexDigit(QChar digit, uint *result)
{
ushort d = digit.unicode();
*result <<= 4;
if (d >= '0' && d <= '9')
*result |= (d - '0');
else if (d >= 'a' && d <= 'f')
*result |= (d - 'a') + 10;
else if (d >= 'A' && d <= 'F')
*result |= (d - 'A') + 10;
else
return false;
return true;
}
static inline bool scanEscapeSequence(const QChar *&json, const QChar *end, uint *ch)
{
++json;
if (json >= end)
return false;
DEBUG << "scan escape";
uint escaped = (json++)->unicode();
switch (escaped) {
case '"':
*ch = '"'; break;
case '\\':
*ch = '\\'; break;
case '/':
*ch = '/'; break;
case 'b':
*ch = 0x8; break;
case 'f':
*ch = 0xc; break;
case 'n':
*ch = 0xa; break;
case 'r':
*ch = 0xd; break;
case 't':
*ch = 0x9; break;
case 'u': {
*ch = 0;
if (json > end - 4)
return false;
for (int i = 0; i < 4; ++i) {
if (!addHexDigit(*json, ch))
return false;
++json;
}
return true;
}
default:
return false;
}
return true;
}
bool JsonParser::parseString(QString *string)
{
BEGIN << "parse string stringPos=" << json;
while (json < end) {
if (*json == '"')
break;
else if (*json == '\\') {
uint ch = 0;
if (!scanEscapeSequence(json, end, &ch)) {
lastError = QJsonParseError::IllegalEscapeSequence;
return false;
}
if (QChar::requiresSurrogates(ch)) {
*string += QChar(QChar::highSurrogate(ch)) + QChar(QChar::lowSurrogate(ch));
} else {
*string += QChar(ch);
}
} else {
if (json->unicode() <= 0x1f) {
lastError = QJsonParseError::IllegalEscapeSequence;
return false;
}
*string += *json;
++json;
}
}
++json;
if (json > end) {
lastError = QJsonParseError::UnterminatedString;
return false;
}
END;
return true;
}
struct Stringify
{
ExecutionEngine *v4;
FunctionObject *replacerFunction;
QV4::String *propertyList;
int propertyListSize;
QString gap;
QString indent;
QStack<Object *> stack;
bool stackContains(Object *o) {
for (int i = 0; i < stack.size(); ++i)
if (stack.at(i)->d() == o->d())
return true;
return false;
}
Stringify(ExecutionEngine *e) : v4(e), replacerFunction(nullptr), propertyList(nullptr), propertyListSize(0) {}
QString Str(const QString &key, const Value &v);
QString JA(Object *a);
QString JO(Object *o);
QString makeMember(const QString &key, const Value &v);
};
static QString quote(const QString &str)
{
QString product;
const int length = str.length();
product.reserve(length + 2);
product += QLatin1Char('"');
for (int i = 0; i < length; ++i) {
QChar c = str.at(i);
switch (c.unicode()) {
case '"':
product += QLatin1String("\\\"");
break;
case '\\':
product += QLatin1String("\\\\");
break;
case '\b':
product += QLatin1String("\\b");
break;
case '\f':
product += QLatin1String("\\f");
break;
case '\n':
product += QLatin1String("\\n");
break;
case '\r':
product += QLatin1String("\\r");
break;
case '\t':
product += QLatin1String("\\t");
break;
default:
if (c.unicode() <= 0x1f) {
product += QLatin1String("\\u00");
product += (c.unicode() > 0xf ? QLatin1Char('1') : QLatin1Char('0')) +
QLatin1Char("0123456789abcdef"[c.unicode() & 0xf]);
} else {
product += c;
}
}
}
product += QLatin1Char('"');
return product;
}
QString Stringify::Str(const QString &key, const Value &v)
{
Scope scope(v4);
ScopedValue value(scope, v);
ScopedObject o(scope, value);
if (o) {
ScopedString s(scope, v4->newString(QStringLiteral("toJSON")));
ScopedFunctionObject toJSON(scope, o->get(s));
if (!!toJSON) {
JSCallData jsCallData(scope, 1);
*jsCallData->thisObject = value;
jsCallData->args[0] = v4->newString(key);
value = toJSON->call(jsCallData);
if (v4->hasException)
return QString();
}
}
if (replacerFunction) {
ScopedObject holder(scope, v4->newObject());
holder->put(scope.engine->id_empty(), value);
JSCallData jsCallData(scope, 2);
jsCallData->args[0] = v4->newString(key);
jsCallData->args[1] = value;
*jsCallData->thisObject = holder;
value = replacerFunction->call(jsCallData);
if (v4->hasException)
return QString();
}
o = value->asReturnedValue();
if (o) {
if (NumberObject *n = o->as<NumberObject>())
value = Encode(n->value());
else if (StringObject *so = o->as<StringObject>())
value = so->d()->string;
else if (BooleanObject *b = o->as<BooleanObject>())
value = Encode(b->value());
}
if (value->isNull())
return QStringLiteral("null");
if (value->isBoolean())
return value->booleanValue() ? QStringLiteral("true") : QStringLiteral("false");
if (value->isString())
return quote(value->stringValue()->toQString());
if (value->isNumber()) {
double d = value->toNumber();
return std::isfinite(d) ? value->toQString() : QStringLiteral("null");
}
if (const QV4::VariantObject *v = value->as<QV4::VariantObject>()) {
return quote(v->d()->data().toString());
}
o = value->asReturnedValue();
if (o) {
if (!o->as<FunctionObject>()) {
if (o->isArrayLike()) {
return JA(o.getPointer());
} else {
return JO(o);
}
}
}
return QString();
}
QString Stringify::makeMember(const QString &key, const Value &v)
{
QString strP = Str(key, v);
if (!strP.isEmpty()) {
QString member = quote(key) + QLatin1Char(':');
if (!gap.isEmpty())
member += QLatin1Char(' ');
member += strP;
return member;
}
return QString();
}
QString Stringify::JO(Object *o)
{
if (stackContains(o)) {
v4->throwTypeError();
return QString();
}
Scope scope(v4);
QString result;
stack.push(o);
QString stepback = indent;
indent += gap;
QStringList partial;
if (!propertyListSize) {
ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly);
ScopedValue name(scope);
ScopedValue val(scope);
while (1) {
name = it.nextPropertyNameAsString(val);
if (name->isNull())
break;
QString key = name->toQString();
QString member = makeMember(key, val);
if (!member.isEmpty())
partial += member;
}
} else {
ScopedValue v(scope);
for (int i = 0; i < propertyListSize; ++i) {
bool exists;
String *s = propertyList + i;
if (!s)
continue;
v = o->get(s, &exists);
if (!exists)
continue;
QString member = makeMember(s->toQString(), v);
if (!member.isEmpty())
partial += member;
}
}
if (partial.isEmpty()) {
result = QStringLiteral("{}");
} else if (gap.isEmpty()) {
result = QLatin1Char('{') + partial.join(QLatin1Char(',')) + QLatin1Char('}');
} else {
QString separator = QLatin1String(",\n") + indent;
result = QLatin1String("{\n") + indent + partial.join(separator) + QLatin1Char('\n')
+ stepback + QLatin1Char('}');
}
indent = stepback;
stack.pop();
return result;
}
QString Stringify::JA(Object *a)
{
if (stackContains(a)) {
v4->throwTypeError();
return QString();
}
Scope scope(a->engine());
QString result;
stack.push(a);
QString stepback = indent;
indent += gap;
QStringList partial;
uint len = a->getLength();
ScopedValue v(scope);
for (uint i = 0; i < len; ++i) {
bool exists;
v = a->get(i, &exists);
if (!exists) {
partial += QStringLiteral("null");
continue;
}
QString strP = Str(QString::number(i), v);
if (!strP.isEmpty())
partial += strP;
else
partial += QStringLiteral("null");
}
if (partial.isEmpty()) {
result = QStringLiteral("[]");
} else if (gap.isEmpty()) {
result = QLatin1Char('[') + partial.join(QLatin1Char(',')) + QLatin1Char(']');
} else {
QString separator = QLatin1String(",\n") + indent;
result = QLatin1String("[\n") + indent + partial.join(separator) + QLatin1Char('\n') + stepback + QLatin1Char(']');
}
indent = stepback;
stack.pop();
return result;
}
void Heap::JsonObject::init()
{
Object::init();
Scope scope(internalClass->engine);
ScopedObject o(scope, this);
o->defineDefaultProperty(QStringLiteral("parse"), QV4::JsonObject::method_parse, 2);
o->defineDefaultProperty(QStringLiteral("stringify"), QV4::JsonObject::method_stringify, 3);
ScopedString json(scope, scope.engine->newString(QStringLiteral("JSON")));
o->defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), json);
}
ReturnedValue JsonObject::method_parse(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
ExecutionEngine *v4 = b->engine();
QString jtext;
if (argc > 0)
jtext = argv[0].toQString();
DEBUG << "parsing source = " << jtext;
JsonParser parser(v4, jtext.constData(), jtext.length());
QJsonParseError error;
ReturnedValue result = parser.parse(&error);
if (error.error != QJsonParseError::NoError) {
DEBUG << "parse error" << error.errorString();
RETURN_RESULT(v4->throwSyntaxError(QStringLiteral("JSON.parse: Parse error")));
}
return result;
}
ReturnedValue JsonObject::method_stringify(const FunctionObject *b, const Value *, const Value *argv, int argc)
{
Scope scope(b);
Stringify stringify(scope.engine);
ScopedObject o(scope, argc > 1 ? argv[1] : Value::undefinedValue());
if (o) {
stringify.replacerFunction = o->as<FunctionObject>();
if (o->isArrayObject()) {
uint arrayLen = o->getLength();
stringify.propertyList = static_cast<QV4::String *>(scope.alloc(arrayLen));
for (uint i = 0; i < arrayLen; ++i) {
Value *v = stringify.propertyList + i;
*v = o->get(i);
if (v->as<NumberObject>() || v->as<StringObject>() || v->isNumber())
*v = v->toString(scope.engine);
if (!v->isString()) {
v->setM(nullptr);
} else {
for (uint j = 0; j <i; ++j) {
if (stringify.propertyList[j].m() == v->m()) {
v->setM(nullptr);
break;
}
}
}
}
}
}
ScopedValue s(scope, argc > 2 ? argv[2] : Value::undefinedValue());
if (NumberObject *n = s->as<NumberObject>())
s = Encode(n->value());
else if (StringObject *so = s->as<StringObject>())
s = so->d()->string;
if (s->isNumber()) {
stringify.gap = QString(qMin(10, (int)s->toInteger()), ' ');
} else if (String *str = s->stringValue()) {
stringify.gap = str->toQString().left(10);
}
ScopedValue arg0(scope, argc ? argv[0] : Value::undefinedValue());
QString result = stringify.Str(QString(), arg0);
if (result.isEmpty() || scope.engine->hasException)
RETURN_UNDEFINED();
return Encode(scope.engine->newString(result));
}
ReturnedValue JsonObject::fromJsonValue(ExecutionEngine *engine, const QJsonValue &value)
{
if (value.isString())
return engine->newString(value.toString())->asReturnedValue();
else if (value.isDouble())
return Encode(value.toDouble());
else if (value.isBool())
return Encode(value.toBool());
else if (value.isArray())
return fromJsonArray(engine, value.toArray());
else if (value.isObject())
return fromJsonObject(engine, value.toObject());
else if (value.isNull())
return Encode::null();
else
return Encode::undefined();
}
QJsonValue JsonObject::toJsonValue(const Value &value, V4ObjectSet &visitedObjects)
{
if (value.isNumber())
return QJsonValue(value.toNumber());
else if (value.isBoolean())
return QJsonValue((bool)value.booleanValue());
else if (value.isNull())
return QJsonValue(QJsonValue::Null);
else if (value.isUndefined())
return QJsonValue(QJsonValue::Undefined);
else if (String *s = value.stringValue())
return QJsonValue(s->toQString());
Q_ASSERT(value.isObject());
Scope scope(value.as<Object>()->engine());
ScopedArrayObject a(scope, value);
if (a)
return toJsonArray(a, visitedObjects);
ScopedObject o(scope, value);
if (o)
return toJsonObject(o, visitedObjects);
return QJsonValue(value.toQString());
}
QV4::ReturnedValue JsonObject::fromJsonObject(ExecutionEngine *engine, const QJsonObject &object)
{
Scope scope(engine);
ScopedObject o(scope, engine->newObject());
ScopedString s(scope);
ScopedValue v(scope);
for (QJsonObject::const_iterator it = object.begin(), cend = object.end(); it != cend; ++it) {
v = fromJsonValue(engine, it.value());
o->put((s = engine->newString(it.key())), v);
}
return o.asReturnedValue();
}
QJsonObject JsonObject::toJsonObject(const Object *o, V4ObjectSet &visitedObjects)
{
QJsonObject result;
if (!o || o->as<FunctionObject>())
return result;
Scope scope(o->engine());
if (visitedObjects.contains(ObjectItem(o))) {
// Avoid recursion.
// For compatibility with QVariant{List,Map} conversion, we return an
// empty object (and no error is thrown).
return result;
}
visitedObjects.insert(ObjectItem(o));
ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly);
ScopedValue name(scope);
QV4::ScopedValue val(scope);
while (1) {
name = it.nextPropertyNameAsString(val);
if (name->isNull())
break;
QString key = name->toQStringNoThrow();
if (!val->as<FunctionObject>())
result.insert(key, toJsonValue(val, visitedObjects));
}
visitedObjects.remove(ObjectItem(o));
return result;
}
QV4::ReturnedValue JsonObject::fromJsonArray(ExecutionEngine *engine, const QJsonArray &array)
{
Scope scope(engine);
int size = array.size();
ScopedArrayObject a(scope, engine->newArrayObject());
a->arrayReserve(size);
ScopedValue v(scope);
for (int i = 0; i < size; i++)
a->arrayPut(i, (v = fromJsonValue(engine, array.at(i))));
a->setArrayLengthUnchecked(size);
return a.asReturnedValue();
}
QJsonArray JsonObject::toJsonArray(const ArrayObject *a, V4ObjectSet &visitedObjects)
{
QJsonArray result;
if (!a)
return result;
Scope scope(a->engine());
if (visitedObjects.contains(ObjectItem(a))) {
// Avoid recursion.
// For compatibility with QVariant{List,Map} conversion, we return an
// empty array (and no error is thrown).
return result;
}
visitedObjects.insert(ObjectItem(a));
ScopedValue v(scope);
quint32 length = a->getLength();
for (quint32 i = 0; i < length; ++i) {
v = a->get(i);
if (v->as<FunctionObject>())
v = Encode::null();
result.append(toJsonValue(v, visitedObjects));
}
visitedObjects.remove(ObjectItem(a));
return result;
}