Add QJson support to QV8Engine

Make QV8Engine perform direct conversion between JavaScript values
and QJson{Value,Object,Array}.

This implementation always makes a deep clone of the
QJson{Object,Array} when converting to JS; it might make sense to add
a lazy conversion scheme for dealing with large objects.

Change-Id: Id0b65891a19515ce22f1e51fa8a28d9f3e595271
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
Reviewed-by: Jamey Hicks <jamey.hicks@nokia.com>
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
This commit is contained in:
Kent Hansen 2012-03-23 18:14:29 +01:00 committed by Qt by Nokia
parent 31c5b237c4
commit e5f45d9b57
26 changed files with 925 additions and 0 deletions

View File

@ -58,6 +58,9 @@
#include "qv8domerrors_p.h"
#include "qv8sqlerrors_p.h"
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qjsonvalue.h>
Q_DECLARE_METATYPE(QJSValue)
Q_DECLARE_METATYPE(QList<int>)
@ -155,6 +158,7 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership)
m_variantWrapper.init(this);
m_valueTypeWrapper.init(this);
m_sequenceWrapper.init(this);
m_jsonWrapper.init(this);
{
v8::Handle<v8::Value> v = global()->Get(v8::String::New("Object"))->ToObject()->Get(v8::String::New("getOwnPropertyNames"));
@ -182,6 +186,7 @@ QV8Engine::~QV8Engine()
qPersistentDispose(m_strongReferencer);
m_jsonWrapper.destroy();
m_sequenceWrapper.destroy();
m_valueTypeWrapper.destroy();
m_variantWrapper.destroy();
@ -220,6 +225,9 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
if (typeHint == QVariant::Bool)
return QVariant(value->BooleanValue());
if (typeHint == QMetaType::QJsonValue)
return QVariant::fromValue(jsonValueFromJS(value));
if (value->IsObject()) {
QV8ObjectResource *r = (QV8ObjectResource *)value->ToObject()->GetExternalResource();
if (r) {
@ -251,6 +259,9 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
case QV8ObjectResource::SequenceType:
return m_sequenceWrapper.toVariant(r);
}
} else if (typeHint == QMetaType::QJsonObject
&& !value->IsArray() && !value->IsFunction()) {
return QVariant::fromValue(jsonObjectFromJS(value));
}
}
@ -269,6 +280,8 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
}
return qVariantFromValue<QList<QObject*> >(list);
} else if (typeHint == QMetaType::QJsonArray) {
return QVariant::fromValue(jsonArrayFromJS(value));
}
bool succeeded = false;
@ -368,6 +381,12 @@ v8::Handle<v8::Value> QV8Engine::fromVariant(const QVariant &variant)
return arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr));
case QMetaType::QVariantMap:
return objectFromVariantMap(this, *reinterpret_cast<const QVariantMap *>(ptr));
case QMetaType::QJsonValue:
return jsonValueToJS(*reinterpret_cast<const QJsonValue *>(ptr));
case QMetaType::QJsonObject:
return jsonObjectToJS(*reinterpret_cast<const QJsonObject *>(ptr));
case QMetaType::QJsonArray:
return jsonArrayToJS(*reinterpret_cast<const QJsonArray *>(ptr));
default:
break;
@ -1152,6 +1171,15 @@ v8::Handle<v8::Value> QV8Engine::metaTypeToJS(int type, const void *data)
case QMetaType::QVariant:
result = variantToJS(*reinterpret_cast<const QVariant*>(data));
break;
case QMetaType::QJsonValue:
result = jsonValueToJS(*reinterpret_cast<const QJsonValue *>(data));
break;
case QMetaType::QJsonObject:
result = jsonObjectToJS(*reinterpret_cast<const QJsonObject *>(data));
break;
case QMetaType::QJsonArray:
result = jsonArrayToJS(*reinterpret_cast<const QJsonArray *>(data));
break;
default:
if (type == qMetaTypeId<QJSValue>()) {
return QJSValuePrivate::get(*reinterpret_cast<const QJSValue*>(data))->asV8Value(this);
@ -1267,6 +1295,15 @@ bool QV8Engine::metaTypeFromJS(v8::Handle<v8::Value> value, int type, void *data
case QMetaType::QVariant:
*reinterpret_cast<QVariant*>(data) = variantFromJS(value);
return true;
case QMetaType::QJsonValue:
*reinterpret_cast<QJsonValue *>(data) = jsonValueFromJS(value);
return true;
case QMetaType::QJsonObject:
*reinterpret_cast<QJsonObject *>(data) = jsonObjectFromJS(value);
return true;
case QMetaType::QJsonArray:
*reinterpret_cast<QJsonArray *>(data) = jsonArrayFromJS(value);
return true;
default:
;
}
@ -1383,6 +1420,36 @@ QVariant QV8Engine::variantFromJS(v8::Handle<v8::Value> value)
return variantMapFromJS(value->ToObject());
}
v8::Handle<v8::Value> QV8Engine::jsonValueToJS(const QJsonValue &value)
{
return m_jsonWrapper.fromJsonValue(value);
}
QJsonValue QV8Engine::jsonValueFromJS(v8::Handle<v8::Value> value)
{
return m_jsonWrapper.toJsonValue(value);
}
v8::Local<v8::Object> QV8Engine::jsonObjectToJS(const QJsonObject &object)
{
return m_jsonWrapper.fromJsonObject(object);
}
QJsonObject QV8Engine::jsonObjectFromJS(v8::Handle<v8::Value> value)
{
return m_jsonWrapper.toJsonObject(value);
}
v8::Local<v8::Array> QV8Engine::jsonArrayToJS(const QJsonArray &array)
{
return m_jsonWrapper.fromJsonArray(array);
}
QJsonArray QV8Engine::jsonArrayFromJS(v8::Handle<v8::Value> value)
{
return m_jsonWrapper.toJsonArray(value);
}
bool QV8Engine::convertToNativeQObject(v8::Handle<v8::Value> value,
const QByteArray &targetType,
void **result)

View File

@ -80,6 +80,7 @@
#include "qv8variantwrapper_p.h"
#include "qv8valuetypewrapper_p.h"
#include "qv8sequencewrapper_p.h"
#include "qv8jsonwrapper_p.h"
QT_BEGIN_NAMESPACE
@ -415,6 +416,13 @@ public:
v8::Handle<v8::Value> variantToJS(const QVariant &value);
QVariant variantFromJS(v8::Handle<v8::Value> value);
v8::Handle<v8::Value> jsonValueToJS(const QJsonValue &value);
QJsonValue jsonValueFromJS(v8::Handle<v8::Value> value);
v8::Local<v8::Object> jsonObjectToJS(const QJsonObject &object);
QJsonObject jsonObjectFromJS(v8::Handle<v8::Value> value);
v8::Local<v8::Array> jsonArrayToJS(const QJsonArray &array);
QJsonArray jsonArrayFromJS(v8::Handle<v8::Value> value);
v8::Handle<v8::Value> metaTypeToJS(int type, const void *data);
bool metaTypeFromJS(v8::Handle<v8::Value> value, int type, void *data);
@ -477,6 +485,7 @@ protected:
QV8VariantWrapper m_variantWrapper;
QV8ValueTypeWrapper m_valueTypeWrapper;
QV8SequenceWrapper m_sequenceWrapper;
QV8JsonWrapper m_jsonWrapper;
v8::Persistent<v8::Function> m_getOwnPropertyNames;
v8::Persistent<v8::Function> m_freezeObject;

View File

@ -0,0 +1,183 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qv8jsonwrapper_p.h"
#include "qv8engine_p.h"
#include "qjsconverter_impl_p.h"
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qjsonvalue.h>
QT_BEGIN_NAMESPACE
QV8JsonWrapper::QV8JsonWrapper()
: m_engine(0)
{
}
QV8JsonWrapper::~QV8JsonWrapper()
{
}
void QV8JsonWrapper::init(QV8Engine *engine)
{
m_engine = engine;
}
void QV8JsonWrapper::destroy()
{
}
v8::Handle<v8::Value> QV8JsonWrapper::fromJsonValue(const QJsonValue &value)
{
if (value.isString())
return QJSConverter::toString(value.toString());
else if (value.isDouble())
return v8::Number::New(value.toDouble());
else if (value.isBool())
return value.toBool() ? v8::True() : v8::False();
else if (value.isArray())
return fromJsonArray(value.toArray());
else if (value.isObject())
return fromJsonObject(value.toObject());
else if (value.isNull())
return v8::Null();
else
return v8::Undefined();
}
QJsonValue QV8JsonWrapper::toJsonValue(v8::Handle<v8::Value> value)
{
if (value->IsString())
return QJsonValue(QJSConverter::toString(value.As<v8::String>()));
else if (value->IsNumber())
return QJsonValue(value->NumberValue());
else if (value->IsBoolean())
return QJsonValue(value->BooleanValue());
else if (value->IsArray())
return toJsonArray(value.As<v8::Array>());
else if (value->IsObject())
return toJsonObject(value.As<v8::Object>());
else if (value->IsNull())
return QJsonValue(QJsonValue::Null);
else
return QJsonValue(QJsonValue::Undefined);
}
v8::Local<v8::Object> QV8JsonWrapper::fromJsonObject(const QJsonObject &object)
{
v8::Local<v8::Object> v8object = v8::Object::New();
for (QJsonObject::const_iterator it = object.begin(); it != object.end(); ++it)
v8object->Set(QJSConverter::toString(it.key()), fromJsonValue(it.value()));
return v8object;
}
QJsonObject QV8JsonWrapper::toJsonObject(v8::Handle<v8::Value> value)
{
QJsonObject result;
if (!value->IsObject() || value->IsArray() || value->IsFunction())
return result;
v8::Handle<v8::Object> v8object(value.As<v8::Object>());
int hash = v8object->GetIdentityHash();
if (m_visitedConversionObjects.contains(hash)) {
// Avoid recursion.
// For compatibility with QVariant{List,Map} conversion, we return an
// empty object (and no error is thrown).
return result;
}
m_visitedConversionObjects.insert(hash);
v8::Local<v8::Array> propertyNames = m_engine->getOwnPropertyNames(v8object);
uint32_t length = propertyNames->Length();
for (uint32_t i = 0; i < length; ++i) {
v8::Local<v8::Value> name = propertyNames->Get(i);
v8::Local<v8::Value> propertyValue = v8object->Get(name);
if (!propertyValue->IsFunction())
result.insert(QJSConverter::toString(name->ToString()), toJsonValue(propertyValue));
}
m_visitedConversionObjects.remove(hash);
return result;
}
v8::Local<v8::Array> QV8JsonWrapper::fromJsonArray(const QJsonArray &array)
{
int size = array.size();
v8::Local<v8::Array> v8array = v8::Array::New(size);
for (int i = 0; i < size; i++)
v8array->Set(i, fromJsonValue(array.at(i)));
return v8array;
}
QJsonArray QV8JsonWrapper::toJsonArray(v8::Handle<v8::Value> value)
{
QJsonArray result;
if (!value->IsArray())
return result;
v8::Handle<v8::Array> v8array(value.As<v8::Array>());
int hash = v8array->GetIdentityHash();
if (m_visitedConversionObjects.contains(hash)) {
// Avoid recursion.
// For compatibility with QVariant{List,Map} conversion, we return an
// empty array (and no error is thrown).
return result;
}
m_visitedConversionObjects.insert(hash);
uint32_t length = v8array->Length();
for (uint32_t i = 0; i < length; ++i) {
v8::Local<v8::Value> element = v8array->Get(i);
if (!element->IsFunction())
result.append(toJsonValue(element));
}
m_visitedConversionObjects.remove(hash);
return result;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,93 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the QtQml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QV8JSONWRAPPER_P_H
#define QV8JSONWRAPPER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/qglobal.h>
#include <QtCore/qset.h>
#include <private/qv8_p.h>
QT_BEGIN_NAMESPACE
class QJsonValue;
class QJsonObject;
class QJsonArray;
class QV8Engine;
class QV8JsonWrapper
{
public:
QV8JsonWrapper();
~QV8JsonWrapper();
void init(QV8Engine *);
void destroy();
v8::Handle<v8::Value> fromJsonValue(const QJsonValue &value);
QJsonValue toJsonValue(v8::Handle<v8::Value> value);
v8::Local<v8::Object> fromJsonObject(const QJsonObject &object);
QJsonObject toJsonObject(v8::Handle<v8::Value> value);
v8::Local<v8::Array> fromJsonArray(const QJsonArray &array);
QJsonArray toJsonArray(v8::Handle<v8::Value> value);
private:
QV8Engine *m_engine;
QSet<int> m_visitedConversionObjects;
};
QT_END_NAMESPACE
#endif // QV8JSONWRAPPER_P_H

View File

@ -54,6 +54,7 @@
#include <private/qqmlexpression_p.h>
#include <QtQml/qjsvalue.h>
#include <QtCore/qjsonvalue.h>
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qtimer.h>
#include <QtCore/qatomic.h>
@ -659,6 +660,8 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropert
QMetaObject::metacall(object, QMetaObject::ResetProperty, property->coreIndex, a);
} else if (value->IsUndefined() && property->propType == qMetaTypeId<QVariant>()) {
PROPERTY_STORE(QVariant, QVariant());
} else if (value->IsUndefined() && property->propType == QMetaType::QJsonValue) {
PROPERTY_STORE(QJsonValue, QJsonValue(QJsonValue::Undefined));
} else if (value->IsUndefined()) {
QString error = QLatin1String("Cannot assign [undefined] to ") +
QLatin1String(QMetaType::typeName(property->propType));

View File

@ -15,6 +15,7 @@ HEADERS += \
$$PWD/qv8variantwrapper_p.h \
$$PWD/qv8variantresource_p.h \
$$PWD/qv8valuetypewrapper_p.h \
$$PWD/qv8jsonwrapper_p.h \
$$PWD/qv8include_p.h \
$$PWD/qv8worker_p.h \
$$PWD/qv8bindings_p.h \
@ -33,6 +34,7 @@ SOURCES += \
$$PWD/qv8listwrapper.cpp \
$$PWD/qv8variantwrapper.cpp \
$$PWD/qv8valuetypewrapper.cpp \
$$PWD/qv8jsonwrapper.cpp \
$$PWD/qv8include.cpp \
$$PWD/qv8worker.cpp \
$$PWD/qv8bindings.cpp \

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[123]

View File

@ -0,0 +1 @@
[true,false,null,"hello"]

View File

@ -0,0 +1 @@
[{"a":42}]

View File

@ -0,0 +1 @@
[[[42]],[]]

View File

@ -0,0 +1 @@
false

View File

@ -0,0 +1 @@
null

View File

@ -0,0 +1 @@
123

View File

@ -0,0 +1 @@
42.35

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{"foo":123}

View File

@ -0,0 +1 @@
{"a":true,"b":false,"c":null,"d":"hello"}

View File

@ -0,0 +1 @@
{"a":{"b":{"c":42}}}

View File

@ -0,0 +1 @@
{"a":[],"b":[42],"c":{"d":null}}

View File

@ -0,0 +1 @@
"hello"

View File

@ -0,0 +1 @@
true

View File

@ -0,0 +1,16 @@
CONFIG += testcase
TARGET = tst_qjsonbinding
macx:CONFIG -= app_bundle
SOURCES += tst_qjsonbinding.cpp
INCLUDEPATH += ../../shared
include (../../shared/util.pri)
# QMAKE_CXXFLAGS = -fprofile-arcs -ftest-coverage
# LIBS += -lgcov
TESTDATA = data/*
CONFIG += parallel_test
QT += core qml testlib

View File

@ -0,0 +1,535 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <QtQml/QtQml>
#include "../../shared/util.h"
Q_DECLARE_METATYPE(QJsonValue::Type)
class JsonPropertyObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QJsonValue value READ value WRITE setValue)
Q_PROPERTY(QJsonObject object READ object WRITE setObject)
Q_PROPERTY(QJsonArray array READ array WRITE setArray)
public:
QJsonValue value() const { return m_value; }
void setValue(const QJsonValue &v) { m_value = v; }
QJsonObject object() const { return m_object; }
void setObject(const QJsonObject &o) { m_object = o; }
QJsonArray array() const { return m_array; }
void setArray(const QJsonArray &a) { m_array = a; }
private:
QJsonValue m_value;
QJsonObject m_object;
QJsonArray m_array;
};
class tst_qjsonbinding : public QQmlDataTest
{
Q_OBJECT
public:
tst_qjsonbinding() {}
private slots:
void cppJsConversion_data();
void cppJsConversion();
void readValueProperty_data();
void readValueProperty();
void readObjectOrArrayProperty_data();
void readObjectOrArrayProperty();
void writeValueProperty_data();
void writeValueProperty();
void writeObjectOrArrayProperty_data();
void writeObjectOrArrayProperty();
void writeProperty_incompatibleType_data();
void writeProperty_incompatibleType();
void writeProperty_javascriptExpression_data();
void writeProperty_javascriptExpression();
private:
QByteArray readAsUtf8(const QString &fileName);
static QJsonValue valueFromJson(const QByteArray &json);
void addPrimitiveDataTestFiles();
void addObjectDataTestFiles();
void addArrayDataTestFiles();
};
QByteArray tst_qjsonbinding::readAsUtf8(const QString &fileName)
{
QFile file(testFile(fileName));
file.open(QIODevice::ReadOnly);
QTextStream stream(&file);
return stream.readAll().trimmed().toUtf8();
}
QJsonValue tst_qjsonbinding::valueFromJson(const QByteArray &json)
{
if (json.isEmpty())
return QJsonValue(QJsonValue::Undefined);
QJsonDocument doc = QJsonDocument::fromJson(json);
if (!doc.isEmpty())
return doc.isObject() ? QJsonValue(doc.object()) : QJsonValue(doc.array());
// QJsonDocument::fromJson() only handles objects and arrays...
// Wrap the JSON inside a dummy object and extract the value.
QByteArray wrappedJson = "{\"prop\":" + json + "}";
doc = QJsonDocument::fromJson(wrappedJson);
Q_ASSERT(doc.isObject());
return doc.object().value("prop");
}
void tst_qjsonbinding::addPrimitiveDataTestFiles()
{
QTest::newRow("true") << "true.json";
QTest::newRow("false") << "false.json";
QTest::newRow("null") << "null.json";
QTest::newRow("number.0") << "number.0.json";
QTest::newRow("number.1") << "number.1.json";
QTest::newRow("string.0") << "string.0.json";
QTest::newRow("undefined") << "empty.json";
}
void tst_qjsonbinding::addObjectDataTestFiles()
{
QTest::newRow("object.0") << "object.0.json";
QTest::newRow("object.1") << "object.1.json";
QTest::newRow("object.2") << "object.2.json";
QTest::newRow("object.3") << "object.3.json";
QTest::newRow("object.4") << "object.4.json";
}
void tst_qjsonbinding::addArrayDataTestFiles()
{
QTest::newRow("array.0") << "array.0.json";
QTest::newRow("array.1") << "array.1.json";
QTest::newRow("array.2") << "array.2.json";
QTest::newRow("array.3") << "array.3.json";
QTest::newRow("array.4") << "array.4.json";
}
void tst_qjsonbinding::cppJsConversion_data()
{
QTest::addColumn<QString>("fileName");
addPrimitiveDataTestFiles();
addObjectDataTestFiles();
addArrayDataTestFiles();
}
void tst_qjsonbinding::cppJsConversion()
{
QFETCH(QString, fileName);
QByteArray json = readAsUtf8(fileName);
QJsonValue jsonValue = valueFromJson(json);
QJSEngine eng;
QJSValue stringify = eng.globalObject().property("JSON").property("stringify");
QVERIFY(stringify.isCallable());
{
QJSValue jsValue = eng.toScriptValue(jsonValue);
QVERIFY(!jsValue.isVariant());
switch (jsonValue.type()) {
case QJsonValue::Null:
QVERIFY(jsValue.isNull());
break;
case QJsonValue::Bool:
QVERIFY(jsValue.isBool());
QCOMPARE(jsValue.toBool(), jsonValue.toBool());
break;
case QJsonValue::Double:
QVERIFY(jsValue.isNumber());
QCOMPARE(jsValue.toNumber(), jsonValue.toDouble());
break;
case QJsonValue::String:
QVERIFY(jsValue.isString());
QCOMPARE(jsValue.toString(), jsonValue.toString());
break;
case QJsonValue::Array:
QVERIFY(jsValue.isArray());
break;
case QJsonValue::Object:
QVERIFY(jsValue.isObject());
break;
case QJsonValue::Undefined:
QVERIFY(jsValue.isUndefined());
break;
}
if (jsValue.isUndefined()) {
QVERIFY(json.isEmpty());
} else {
QJSValue stringified = stringify.call(QJSValueList() << jsValue);
QVERIFY(!stringified.isError());
QCOMPARE(stringified.toString().toUtf8(), json);
}
QJsonValue roundtrip = qjsvalue_cast<QJsonValue>(jsValue);
// Workarounds for QTBUG-25164
if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
QVERIFY(roundtrip.isObject() && roundtrip.toObject().isEmpty());
else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
QVERIFY(roundtrip.isArray() && roundtrip.toArray().isEmpty());
else
QCOMPARE(roundtrip, jsonValue);
}
if (jsonValue.isObject()) {
QJsonObject jsonObject = jsonValue.toObject();
QJSValue jsObject = eng.toScriptValue(jsonObject);
QVERIFY(!jsObject.isVariant());
QVERIFY(jsObject.isObject());
QJSValue stringified = stringify.call(QJSValueList() << jsObject);
QVERIFY(!stringified.isError());
QCOMPARE(stringified.toString().toUtf8(), json);
QJsonObject roundtrip = qjsvalue_cast<QJsonObject>(jsObject);
QCOMPARE(roundtrip, jsonObject);
} else if (jsonValue.isArray()) {
QJsonArray jsonArray = jsonValue.toArray();
QJSValue jsArray = eng.toScriptValue(jsonArray);
QVERIFY(!jsArray.isVariant());
QVERIFY(jsArray.isArray());
QJSValue stringified = stringify.call(QJSValueList() << jsArray);
QVERIFY(!stringified.isError());
QCOMPARE(stringified.toString().toUtf8(), json);
QJsonArray roundtrip = qjsvalue_cast<QJsonArray>(jsArray);
QCOMPARE(roundtrip, jsonArray);
}
}
void tst_qjsonbinding::readValueProperty_data()
{
cppJsConversion_data();
}
void tst_qjsonbinding::readValueProperty()
{
QFETCH(QString, fileName);
QByteArray json = readAsUtf8(fileName);
QJsonValue jsonValue = valueFromJson(json);
QJSEngine eng;
JsonPropertyObject obj;
obj.setValue(jsonValue);
eng.globalObject().setProperty("obj", eng.newQObject(&obj));
QJSValue stringified = eng.evaluate(
"var v = obj.value; (typeof v == 'undefined') ? '' : JSON.stringify(v)");
QVERIFY(!stringified.isError());
QCOMPARE(stringified.toString().toUtf8(), json);
}
void tst_qjsonbinding::readObjectOrArrayProperty_data()
{
QTest::addColumn<QString>("fileName");
addObjectDataTestFiles();
addArrayDataTestFiles();
}
void tst_qjsonbinding::readObjectOrArrayProperty()
{
QFETCH(QString, fileName);
QByteArray json = readAsUtf8(fileName);
QJsonValue jsonValue = valueFromJson(json);
QVERIFY(jsonValue.isObject() || jsonValue.isArray());
QJSEngine eng;
JsonPropertyObject obj;
if (jsonValue.isObject())
obj.setObject(jsonValue.toObject());
else
obj.setArray(jsonValue.toArray());
eng.globalObject().setProperty("obj", eng.newQObject(&obj));
QJSValue stringified = eng.evaluate(
QString::fromLatin1("JSON.stringify(obj.%0)").arg(
jsonValue.isObject() ? "object" : "array"));
QVERIFY(!stringified.isError());
QCOMPARE(stringified.toString().toUtf8(), json);
}
void tst_qjsonbinding::writeValueProperty_data()
{
readValueProperty_data();
}
void tst_qjsonbinding::writeValueProperty()
{
QFETCH(QString, fileName);
QByteArray json = readAsUtf8(fileName);
QJsonValue jsonValue = valueFromJson(json);
QJSEngine eng;
JsonPropertyObject obj;
eng.globalObject().setProperty("obj", eng.newQObject(&obj));
QJSValue fun = eng.evaluate(
"(function(json) {"
" void(obj.value = (json == '') ? undefined : JSON.parse(json));"
"})");
QVERIFY(fun.isCallable());
QVERIFY(obj.value().isNull());
QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
// Workarounds for QTBUG-25164
if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
QVERIFY(obj.value().isObject() && obj.value().toObject().isEmpty());
else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
QVERIFY(obj.value().isArray() && obj.value().toArray().isEmpty());
else
QCOMPARE(obj.value(), jsonValue);
}
void tst_qjsonbinding::writeObjectOrArrayProperty_data()
{
readObjectOrArrayProperty_data();
}
void tst_qjsonbinding::writeObjectOrArrayProperty()
{
QFETCH(QString, fileName);
QByteArray json = readAsUtf8(fileName);
QJsonValue jsonValue = valueFromJson(json);
QVERIFY(jsonValue.isObject() || jsonValue.isArray());
QJSEngine eng;
JsonPropertyObject obj;
eng.globalObject().setProperty("obj", eng.newQObject(&obj));
QJSValue fun = eng.evaluate(
QString::fromLatin1(
"(function(json) {"
" void(obj.%0 = JSON.parse(json));"
"})").arg(jsonValue.isObject() ? "object" : "array")
);
QVERIFY(fun.isCallable());
QVERIFY(obj.object().isEmpty() && obj.array().isEmpty());
QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
if (jsonValue.isObject())
QCOMPARE(obj.object(), jsonValue.toObject());
else
QCOMPARE(obj.array(), jsonValue.toArray());
}
void tst_qjsonbinding::writeProperty_incompatibleType_data()
{
QTest::addColumn<QString>("property");
QTest::addColumn<QString>("expression");
QTest::newRow("value=function") << "value" << "(function(){})";
QTest::newRow("object=undefined") << "object" << "undefined";
QTest::newRow("object=null") << "object" << "null";
QTest::newRow("object=false") << "object" << "false";
QTest::newRow("object=true") << "object" << "true";
QTest::newRow("object=123") << "object" << "123";
QTest::newRow("object=42.35") << "object" << "42.35";
QTest::newRow("object='foo'") << "object" << "'foo'";
QTest::newRow("object=[]") << "object" << "[]";
QTest::newRow("object=function") << "object" << "(function(){})";
QTest::newRow("array=undefined") << "array" << "undefined";
QTest::newRow("array=null") << "array" << "null";
QTest::newRow("array=false") << "array" << "false";
QTest::newRow("array=true") << "array" << "true";
QTest::newRow("array=123") << "array" << "123";
QTest::newRow("array=42.35") << "array" << "42.35";
QTest::newRow("array='foo'") << "array" << "'foo'";
QTest::newRow("array={}") << "array" << "{}";
QTest::newRow("array=function") << "array" << "(function(){})";
}
void tst_qjsonbinding::writeProperty_incompatibleType()
{
QFETCH(QString, property);
QFETCH(QString, expression);
QJSEngine eng;
JsonPropertyObject obj;
eng.globalObject().setProperty("obj", eng.newQObject(&obj));
QJSValue ret = eng.evaluate(QString::fromLatin1("obj.%0 = %1")
.arg(property).arg(expression));
QEXPECT_FAIL("value=function", "See 'XXX TODO: uncomment the following lines' in qv8qobjectwrapper.cpp", Abort);
QEXPECT_FAIL("object=function", "See 'XXX TODO: uncomment the following lines' in qv8qobjectwrapper.cpp", Abort);
QEXPECT_FAIL("array=function", "See 'XXX TODO: uncomment the following lines' in qv8qobjectwrapper.cpp", Abort);
QVERIFY(ret.isError());
QVERIFY(ret.toString().contains("Cannot assign"));
}
void tst_qjsonbinding::writeProperty_javascriptExpression_data()
{
QTest::addColumn<QString>("property");
QTest::addColumn<QString>("expression");
QTest::addColumn<QString>("expectedJson");
// Function properties should be omitted.
QTest::newRow("value = object with function property")
<< "value" << "{ foo: function() {} }" << "{}";
QTest::newRow("object = object with function property")
<< "object" << "{ foo: function() {} }" << "{}";
QTest::newRow("array = array with function property")
<< "array" << "[function() {}]" << "[]";
// Inherited properties should not be included.
QTest::newRow("value = object with inherited property")
<< "value" << "{ __proto__: { proto_foo: 123 } }"
<< "{}";
QTest::newRow("value = object with inherited property 2")
<< "value" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
<< "{\"foo\":123}";
QTest::newRow("value = array with inherited property")
<< "value" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
<< "[]";
QTest::newRow("value = array with inherited property 2")
<< "value" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
<< "[10,20]";
QTest::newRow("object = object with inherited property")
<< "object" << "{ __proto__: { proto_foo: 123 } }"
<< "{}";
QTest::newRow("object = object with inherited property 2")
<< "object" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
<< "{\"foo\":123}";
QTest::newRow("array = array with inherited property")
<< "array" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
<< "[]";
QTest::newRow("array = array with inherited property 2")
<< "array" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
<< "[10,20]";
// Non-enumerable properties should be included.
QTest::newRow("value = object with non-enumerable property")
<< "value" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
<< "{\"foo\":123}";
QTest::newRow("object = object with non-enumerable property")
<< "object" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
<< "{\"foo\":123}";
// Cyclic data structures are permitted, but the cyclic links become
// empty objects.
QTest::newRow("value = cyclic object")
<< "value" << "(function() { var o = { foo: 123 }; o.o = o; return o; })()"
<< "{\"foo\":123,\"o\":{}}";
QTest::newRow("value = cyclic array")
<< "value" << "(function() { var a = [10, 20]; a.push(a); return a; })()"
<< "[10,20,[]]";
QTest::newRow("object = cyclic object")
<< "object" << "(function() { var o = { bar: true }; o.o = o; return o; })()"
<< "{\"bar\":true,\"o\":{}}";
QTest::newRow("array = cyclic array")
<< "array" << "(function() { var a = [30, 40]; a.unshift(a); return a; })()"
<< "[[],30,40]";
// Properties with undefined value are excluded.
QTest::newRow("value = { foo: undefined }")
<< "value" << "{ foo: undefined }" << "{}";
QTest::newRow("value = { foo: undefined, bar: 123 }")
<< "value" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
QTest::newRow("value = { foo: 456, bar: undefined }")
<< "value" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
QTest::newRow("object = { foo: undefined }")
<< "object" << "{ foo: undefined }" << "{}";
QTest::newRow("object = { foo: undefined, bar: 123 }")
<< "object" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
QTest::newRow("object = { foo: 456, bar: undefined }")
<< "object" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
// QJsonArray::append() implicitly converts undefined values to null.
QTest::newRow("value = [undefined]")
<< "value" << "[undefined]" << "[null]";
QTest::newRow("value = [undefined, 10]")
<< "value" << "[undefined, 10]" << "[null,10]";
QTest::newRow("value = [10, undefined, 20]")
<< "value" << "[10, undefined, 20]" << "[10,null,20]";
QTest::newRow("array = [undefined]")
<< "array" << "[undefined]" << "[null]";
QTest::newRow("array = [undefined, 10]")
<< "array" << "[undefined, 10]" << "[null,10]";
QTest::newRow("array = [10, undefined, 20]")
<< "array" << "[10, undefined, 20]" << "[10,null,20]";
}
void tst_qjsonbinding::writeProperty_javascriptExpression()
{
QFETCH(QString, property);
QFETCH(QString, expression);
QFETCH(QString, expectedJson);
QJSEngine eng;
JsonPropertyObject obj;
eng.globalObject().setProperty("obj", eng.newQObject(&obj));
QJSValue ret = eng.evaluate(QString::fromLatin1("obj.%0 = %1; JSON.stringify(obj.%0)")
.arg(property).arg(expression));
QVERIFY(!ret.isError());
QCOMPARE(ret.toString(), expectedJson);
}
QTEST_MAIN(tst_qjsonbinding)
#include "tst_qjsonbinding.moc"

View File

@ -8,6 +8,7 @@ PUBLICTESTS += \
qjsengine \
qjsvalue \
qjsvalueiterator \
qjsonbinding \
qmlmin \
qmlplugindump \
qqmlcomponent \