diff options
author | Kent Hansen <kent.hansen@nokia.com> | 2012-03-23 18:14:29 +0100 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-04-16 09:25:06 +0200 |
commit | e5f45d9b57bb0542ec47e5a8a4e57388b6d59d35 (patch) | |
tree | 039622ca4f22c5be93cd491d506fe49427ae9672 | |
parent | 31c5b237c4f0eb6848c7d2e3ae84232c6f8f6d26 (diff) |
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>
26 files changed, 925 insertions, 0 deletions
diff --git a/src/qml/qml/v8/qv8engine.cpp b/src/qml/qml/v8/qv8engine.cpp index 2302d0e369..8444d657ec 100644 --- a/src/qml/qml/v8/qv8engine.cpp +++ b/src/qml/qml/v8/qv8engine.cpp @@ -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) diff --git a/src/qml/qml/v8/qv8engine_p.h b/src/qml/qml/v8/qv8engine_p.h index 2eb3668501..1fc03d82e5 100644 --- a/src/qml/qml/v8/qv8engine_p.h +++ b/src/qml/qml/v8/qv8engine_p.h @@ -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; diff --git a/src/qml/qml/v8/qv8jsonwrapper.cpp b/src/qml/qml/v8/qv8jsonwrapper.cpp new file mode 100644 index 0000000000..ff8cc4faba --- /dev/null +++ b/src/qml/qml/v8/qv8jsonwrapper.cpp @@ -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 diff --git a/src/qml/qml/v8/qv8jsonwrapper_p.h b/src/qml/qml/v8/qv8jsonwrapper_p.h new file mode 100644 index 0000000000..842a3aa5d5 --- /dev/null +++ b/src/qml/qml/v8/qv8jsonwrapper_p.h @@ -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 + diff --git a/src/qml/qml/v8/qv8qobjectwrapper.cpp b/src/qml/qml/v8/qv8qobjectwrapper.cpp index ce85725642..9a5aaca6e4 100644 --- a/src/qml/qml/v8/qv8qobjectwrapper.cpp +++ b/src/qml/qml/v8/qv8qobjectwrapper.cpp @@ -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)); diff --git a/src/qml/qml/v8/v8.pri b/src/qml/qml/v8/v8.pri index 7816c84b79..52b6bf480a 100644 --- a/src/qml/qml/v8/v8.pri +++ b/src/qml/qml/v8/v8.pri @@ -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 \ diff --git a/tests/auto/qml/qjsonbinding/data/array.0.json b/tests/auto/qml/qjsonbinding/data/array.0.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/array.0.json @@ -0,0 +1 @@ +[] diff --git a/tests/auto/qml/qjsonbinding/data/array.1.json b/tests/auto/qml/qjsonbinding/data/array.1.json new file mode 100644 index 0000000000..3214bfe58c --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/array.1.json @@ -0,0 +1 @@ +[123] diff --git a/tests/auto/qml/qjsonbinding/data/array.2.json b/tests/auto/qml/qjsonbinding/data/array.2.json new file mode 100644 index 0000000000..7fd87cd054 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/array.2.json @@ -0,0 +1 @@ +[true,false,null,"hello"] diff --git a/tests/auto/qml/qjsonbinding/data/array.3.json b/tests/auto/qml/qjsonbinding/data/array.3.json new file mode 100644 index 0000000000..3b418fce74 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/array.3.json @@ -0,0 +1 @@ +[{"a":42}] diff --git a/tests/auto/qml/qjsonbinding/data/array.4.json b/tests/auto/qml/qjsonbinding/data/array.4.json new file mode 100644 index 0000000000..55e9fdce97 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/array.4.json @@ -0,0 +1 @@ +[[[42]],[]] diff --git a/tests/auto/qml/qjsonbinding/data/empty.json b/tests/auto/qml/qjsonbinding/data/empty.json new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/empty.json diff --git a/tests/auto/qml/qjsonbinding/data/false.json b/tests/auto/qml/qjsonbinding/data/false.json new file mode 100644 index 0000000000..c508d5366f --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/false.json @@ -0,0 +1 @@ +false diff --git a/tests/auto/qml/qjsonbinding/data/null.json b/tests/auto/qml/qjsonbinding/data/null.json new file mode 100644 index 0000000000..19765bd501 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/null.json @@ -0,0 +1 @@ +null diff --git a/tests/auto/qml/qjsonbinding/data/number.0.json b/tests/auto/qml/qjsonbinding/data/number.0.json new file mode 100644 index 0000000000..190a18037c --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/number.0.json @@ -0,0 +1 @@ +123 diff --git a/tests/auto/qml/qjsonbinding/data/number.1.json b/tests/auto/qml/qjsonbinding/data/number.1.json new file mode 100644 index 0000000000..c07e7a1490 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/number.1.json @@ -0,0 +1 @@ +42.35 diff --git a/tests/auto/qml/qjsonbinding/data/object.0.json b/tests/auto/qml/qjsonbinding/data/object.0.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/object.0.json @@ -0,0 +1 @@ +{} diff --git a/tests/auto/qml/qjsonbinding/data/object.1.json b/tests/auto/qml/qjsonbinding/data/object.1.json new file mode 100644 index 0000000000..bde58e7952 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/object.1.json @@ -0,0 +1 @@ +{"foo":123} diff --git a/tests/auto/qml/qjsonbinding/data/object.2.json b/tests/auto/qml/qjsonbinding/data/object.2.json new file mode 100644 index 0000000000..d6f25a1488 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/object.2.json @@ -0,0 +1 @@ +{"a":true,"b":false,"c":null,"d":"hello"} diff --git a/tests/auto/qml/qjsonbinding/data/object.3.json b/tests/auto/qml/qjsonbinding/data/object.3.json new file mode 100644 index 0000000000..9b7611740f --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/object.3.json @@ -0,0 +1 @@ +{"a":{"b":{"c":42}}} diff --git a/tests/auto/qml/qjsonbinding/data/object.4.json b/tests/auto/qml/qjsonbinding/data/object.4.json new file mode 100644 index 0000000000..377313739d --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/object.4.json @@ -0,0 +1 @@ +{"a":[],"b":[42],"c":{"d":null}} diff --git a/tests/auto/qml/qjsonbinding/data/string.0.json b/tests/auto/qml/qjsonbinding/data/string.0.json new file mode 100644 index 0000000000..3580093b9d --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/string.0.json @@ -0,0 +1 @@ +"hello" diff --git a/tests/auto/qml/qjsonbinding/data/true.json b/tests/auto/qml/qjsonbinding/data/true.json new file mode 100644 index 0000000000..27ba77ddaf --- /dev/null +++ b/tests/auto/qml/qjsonbinding/data/true.json @@ -0,0 +1 @@ +true diff --git a/tests/auto/qml/qjsonbinding/qjsonbinding.pro b/tests/auto/qml/qjsonbinding/qjsonbinding.pro new file mode 100644 index 0000000000..92e5b7a746 --- /dev/null +++ b/tests/auto/qml/qjsonbinding/qjsonbinding.pro @@ -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 diff --git a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp new file mode 100644 index 0000000000..09b256288a --- /dev/null +++ b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp @@ -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" diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 0657ea891e..3166c3b6e1 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -8,6 +8,7 @@ PUBLICTESTS += \ qjsengine \ qjsvalue \ qjsvalueiterator \ + qjsonbinding \ qmlmin \ qmlplugindump \ qqmlcomponent \ |