1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <QtTest/QtTest>
29#include <QtQml/QtQml>
30#include "../../shared/util.h"
31
32Q_DECLARE_METATYPE(QJsonValue::Type)
33
34class JsonPropertyObject : public QObject
35{
36 Q_OBJECT
37 Q_PROPERTY(QJsonValue value READ value WRITE setValue)
38 Q_PROPERTY(QJsonObject object READ object WRITE setObject)
39 Q_PROPERTY(QJsonArray array READ array WRITE setArray)
40public:
41 QJsonValue value() const { return m_value; }
42 void setValue(const QJsonValue &v) { m_value = v; }
43 QJsonObject object() const { return m_object; }
44 void setObject(const QJsonObject &o) { m_object = o; }
45 QJsonArray array() const { return m_array; }
46 void setArray(const QJsonArray &a) { m_array = a; }
47
48private:
49 QJsonValue m_value;
50 QJsonObject m_object;
51 QJsonArray m_array;
52};
53
54class tst_qjsonbinding : public QQmlDataTest
55{
56 Q_OBJECT
57public:
58 tst_qjsonbinding() {}
59
60private slots:
61 void cppJsConversion_data();
62 void cppJsConversion();
63
64 void readValueProperty_data();
65 void readValueProperty();
66 void readObjectOrArrayProperty_data();
67 void readObjectOrArrayProperty();
68
69 void writeValueProperty_data();
70 void writeValueProperty();
71 void writeObjectOrArrayProperty_data();
72 void writeObjectOrArrayProperty();
73
74 void writeProperty_incompatibleType_data();
75 void writeProperty_incompatibleType();
76
77 void writeProperty_javascriptExpression_data();
78 void writeProperty_javascriptExpression();
79
80private:
81 QByteArray readAsUtf8(const QString &fileName);
82 static QJsonValue valueFromJson(const QByteArray &json);
83
84 void addPrimitiveDataTestFiles();
85 void addObjectDataTestFiles();
86 void addArrayDataTestFiles();
87};
88
89QByteArray tst_qjsonbinding::readAsUtf8(const QString &fileName)
90{
91 QFile file(testFile(fileName));
92 file.open(flags: QIODevice::ReadOnly);
93 QTextStream stream(&file);
94 return stream.readAll().trimmed().toUtf8();
95}
96
97QJsonValue tst_qjsonbinding::valueFromJson(const QByteArray &json)
98{
99 if (json.isEmpty())
100 return QJsonValue(QJsonValue::Undefined);
101
102 QJsonDocument doc = QJsonDocument::fromJson(json);
103 if (!doc.isEmpty())
104 return doc.isObject() ? QJsonValue(doc.object()) : QJsonValue(doc.array());
105
106 // QJsonDocument::fromJson() only handles objects and arrays...
107 // Wrap the JSON inside a dummy object and extract the value.
108 QByteArray wrappedJson = "{\"prop\":" + json + '}';
109 doc = QJsonDocument::fromJson(json: wrappedJson);
110 Q_ASSERT(doc.isObject());
111 return doc.object().value(key: "prop");
112}
113
114void tst_qjsonbinding::addPrimitiveDataTestFiles()
115{
116 QTest::newRow(dataTag: "true") << "true.json";
117 QTest::newRow(dataTag: "false") << "false.json";
118
119 QTest::newRow(dataTag: "null") << "null.json";
120
121 QTest::newRow(dataTag: "number.0") << "number.0.json";
122 QTest::newRow(dataTag: "number.1") << "number.1.json";
123
124 QTest::newRow(dataTag: "string.0") << "string.0.json";
125
126 QTest::newRow(dataTag: "undefined") << "empty.json";
127}
128
129void tst_qjsonbinding::addObjectDataTestFiles()
130{
131 QTest::newRow(dataTag: "object.0") << "object.0.json";
132 QTest::newRow(dataTag: "object.1") << "object.1.json";
133 QTest::newRow(dataTag: "object.2") << "object.2.json";
134 QTest::newRow(dataTag: "object.3") << "object.3.json";
135 QTest::newRow(dataTag: "object.4") << "object.4.json";
136}
137
138void tst_qjsonbinding::addArrayDataTestFiles()
139{
140 QTest::newRow(dataTag: "array.0") << "array.0.json";
141 QTest::newRow(dataTag: "array.1") << "array.1.json";
142 QTest::newRow(dataTag: "array.2") << "array.2.json";
143 QTest::newRow(dataTag: "array.3") << "array.3.json";
144 QTest::newRow(dataTag: "array.4") << "array.4.json";
145}
146
147void tst_qjsonbinding::cppJsConversion_data()
148{
149 QTest::addColumn<QString>(name: "fileName");
150
151 addPrimitiveDataTestFiles();
152 addObjectDataTestFiles();
153 addArrayDataTestFiles();
154}
155
156void tst_qjsonbinding::cppJsConversion()
157{
158 QFETCH(QString, fileName);
159
160 QByteArray json = readAsUtf8(fileName);
161 QJsonValue jsonValue = valueFromJson(json);
162
163 QJSEngine eng;
164 QJSValue stringify = eng.globalObject().property(name: "JSON").property(name: "stringify");
165 QVERIFY(stringify.isCallable());
166
167 {
168 QJSValue jsValue = eng.toScriptValue(value: jsonValue);
169 QVERIFY(!jsValue.isVariant());
170 switch (jsonValue.type()) {
171 case QJsonValue::Null:
172 QVERIFY(jsValue.isNull());
173 break;
174 case QJsonValue::Bool:
175 QVERIFY(jsValue.isBool());
176 QCOMPARE(jsValue.toBool(), jsonValue.toBool());
177 break;
178 case QJsonValue::Double:
179 QVERIFY(jsValue.isNumber());
180 QCOMPARE(jsValue.toNumber(), jsonValue.toDouble());
181 break;
182 case QJsonValue::String:
183 QVERIFY(jsValue.isString());
184 QCOMPARE(jsValue.toString(), jsonValue.toString());
185 break;
186 case QJsonValue::Array:
187 QVERIFY(jsValue.isArray());
188 break;
189 case QJsonValue::Object:
190 QVERIFY(jsValue.isObject());
191 break;
192 case QJsonValue::Undefined:
193 QVERIFY(jsValue.isUndefined());
194 break;
195 }
196
197 if (jsValue.isUndefined()) {
198 QVERIFY(json.isEmpty());
199 } else {
200 QJSValue stringified = stringify.call(args: QJSValueList() << jsValue);
201 QVERIFY(!stringified.isError());
202 QCOMPARE(stringified.toString().toUtf8(), json);
203 }
204
205 QJsonValue roundtrip = qjsvalue_cast<QJsonValue>(value: jsValue);
206 // Workarounds for QTBUG-25164
207 if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
208 QVERIFY(roundtrip.isObject() && roundtrip.toObject().isEmpty());
209 else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
210 QVERIFY(roundtrip.isArray() && roundtrip.toArray().isEmpty());
211 else
212 QCOMPARE(roundtrip, jsonValue);
213 }
214
215 if (jsonValue.isObject()) {
216 QJsonObject jsonObject = jsonValue.toObject();
217 QJSValue jsObject = eng.toScriptValue(value: jsonObject);
218 QVERIFY(!jsObject.isVariant());
219 QVERIFY(jsObject.isObject());
220
221 QJSValue stringified = stringify.call(args: QJSValueList() << jsObject);
222 QVERIFY(!stringified.isError());
223 QCOMPARE(stringified.toString().toUtf8(), json);
224
225 QJsonObject roundtrip = qjsvalue_cast<QJsonObject>(value: jsObject);
226 QCOMPARE(roundtrip, jsonObject);
227 } else if (jsonValue.isArray()) {
228 QJsonArray jsonArray = jsonValue.toArray();
229 QJSValue jsArray = eng.toScriptValue(value: jsonArray);
230 QVERIFY(!jsArray.isVariant());
231 QVERIFY(jsArray.isArray());
232
233 QJSValue stringified = stringify.call(args: QJSValueList() << jsArray);
234 QVERIFY(!stringified.isError());
235 QCOMPARE(stringified.toString().toUtf8(), json);
236
237 QJsonArray roundtrip = qjsvalue_cast<QJsonArray>(value: jsArray);
238 QCOMPARE(roundtrip, jsonArray);
239 }
240}
241
242void tst_qjsonbinding::readValueProperty_data()
243{
244 cppJsConversion_data();
245}
246
247void tst_qjsonbinding::readValueProperty()
248{
249 QFETCH(QString, fileName);
250
251 QByteArray json = readAsUtf8(fileName);
252 QJsonValue jsonValue = valueFromJson(json);
253
254 QJSEngine eng;
255 JsonPropertyObject obj;
256 obj.setValue(jsonValue);
257 eng.globalObject().setProperty(name: "obj", value: eng.newQObject(object: &obj));
258 QJSValue stringified = eng.evaluate(
259 program: "var v = obj.value; (typeof v == 'undefined') ? '' : JSON.stringify(v)");
260 QVERIFY(!stringified.isError());
261 QCOMPARE(stringified.toString().toUtf8(), json);
262}
263
264void tst_qjsonbinding::readObjectOrArrayProperty_data()
265{
266 QTest::addColumn<QString>(name: "fileName");
267
268 addObjectDataTestFiles();
269 addArrayDataTestFiles();
270}
271
272void tst_qjsonbinding::readObjectOrArrayProperty()
273{
274 QFETCH(QString, fileName);
275
276 QByteArray json = readAsUtf8(fileName);
277 QJsonValue jsonValue = valueFromJson(json);
278 QVERIFY(jsonValue.isObject() || jsonValue.isArray());
279
280 QJSEngine eng;
281 JsonPropertyObject obj;
282 if (jsonValue.isObject())
283 obj.setObject(jsonValue.toObject());
284 else
285 obj.setArray(jsonValue.toArray());
286 eng.globalObject().setProperty(name: "obj", value: eng.newQObject(object: &obj));
287
288 QJSValue stringified = eng.evaluate(
289 program: QString::fromLatin1(str: "JSON.stringify(obj.%0)").arg(
290 a: jsonValue.isObject() ? "object" : "array"));
291 QVERIFY(!stringified.isError());
292 QCOMPARE(stringified.toString().toUtf8(), json);
293}
294
295void tst_qjsonbinding::writeValueProperty_data()
296{
297 readValueProperty_data();
298}
299
300void tst_qjsonbinding::writeValueProperty()
301{
302 QFETCH(QString, fileName);
303
304 QByteArray json = readAsUtf8(fileName);
305 QJsonValue jsonValue = valueFromJson(json);
306
307 QJSEngine eng;
308 JsonPropertyObject obj;
309 eng.globalObject().setProperty(name: "obj", value: eng.newQObject(object: &obj));
310
311 QJSValue fun = eng.evaluate(
312 program: "(function(json) {"
313 " void(obj.value = (json == '') ? undefined : JSON.parse(json));"
314 "})");
315 QVERIFY(fun.isCallable());
316
317 QVERIFY(obj.value().isNull());
318 QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
319
320 // Workarounds for QTBUG-25164
321 if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
322 QVERIFY(obj.value().isObject() && obj.value().toObject().isEmpty());
323 else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
324 QVERIFY(obj.value().isArray() && obj.value().toArray().isEmpty());
325 else
326 QCOMPARE(obj.value(), jsonValue);
327}
328
329void tst_qjsonbinding::writeObjectOrArrayProperty_data()
330{
331 readObjectOrArrayProperty_data();
332}
333
334void tst_qjsonbinding::writeObjectOrArrayProperty()
335{
336 QFETCH(QString, fileName);
337
338 QByteArray json = readAsUtf8(fileName);
339 QJsonValue jsonValue = valueFromJson(json);
340 QVERIFY(jsonValue.isObject() || jsonValue.isArray());
341
342 QJSEngine eng;
343 JsonPropertyObject obj;
344 eng.globalObject().setProperty(name: "obj", value: eng.newQObject(object: &obj));
345
346 QJSValue fun = eng.evaluate(
347 program: QString::fromLatin1(
348 str: "(function(json) {"
349 " void(obj.%0 = JSON.parse(json));"
350 "})").arg(a: jsonValue.isObject() ? "object" : "array")
351 );
352 QVERIFY(fun.isCallable());
353
354 QVERIFY(obj.object().isEmpty() && obj.array().isEmpty());
355 QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
356
357 if (jsonValue.isObject())
358 QCOMPARE(obj.object(), jsonValue.toObject());
359 else
360 QCOMPARE(obj.array(), jsonValue.toArray());
361}
362
363void tst_qjsonbinding::writeProperty_incompatibleType_data()
364{
365 QTest::addColumn<QString>(name: "property");
366 QTest::addColumn<QString>(name: "expression");
367
368 QTest::newRow(dataTag: "value=function") << "value" << "(function(){})";
369
370 QTest::newRow(dataTag: "object=undefined") << "object" << "undefined";
371 QTest::newRow(dataTag: "object=null") << "object" << "null";
372 QTest::newRow(dataTag: "object=false") << "object" << "false";
373 QTest::newRow(dataTag: "object=true") << "object" << "true";
374 QTest::newRow(dataTag: "object=123") << "object" << "123";
375 QTest::newRow(dataTag: "object=42.35") << "object" << "42.35";
376 QTest::newRow(dataTag: "object='foo'") << "object" << "'foo'";
377 QTest::newRow(dataTag: "object=[]") << "object" << "[]";
378 QTest::newRow(dataTag: "object=function") << "object" << "(function(){})";
379
380 QTest::newRow(dataTag: "array=undefined") << "array" << "undefined";
381 QTest::newRow(dataTag: "array=null") << "array" << "null";
382 QTest::newRow(dataTag: "array=false") << "array" << "false";
383 QTest::newRow(dataTag: "array=true") << "array" << "true";
384 QTest::newRow(dataTag: "array=123") << "array" << "123";
385 QTest::newRow(dataTag: "array=42.35") << "array" << "42.35";
386 QTest::newRow(dataTag: "array='foo'") << "array" << "'foo'";
387 QTest::newRow(dataTag: "array={}") << "array" << "{}";
388 QTest::newRow(dataTag: "array=function") << "array" << "(function(){})";
389}
390
391void tst_qjsonbinding::writeProperty_incompatibleType()
392{
393 QFETCH(QString, property);
394 QFETCH(QString, expression);
395
396 QJSEngine eng;
397 JsonPropertyObject obj;
398 eng.globalObject().setProperty(name: "obj", value: eng.newQObject(object: &obj));
399
400 QJSValue ret = eng.evaluate(program: QString::fromLatin1(str: "obj.%0 = %1")
401 .arg(a: property).arg(a: expression));
402 QVERIFY(ret.isError());
403 QVERIFY(ret.toString().contains("Cannot assign"));
404}
405
406void tst_qjsonbinding::writeProperty_javascriptExpression_data()
407{
408 QTest::addColumn<QString>(name: "property");
409 QTest::addColumn<QString>(name: "expression");
410 QTest::addColumn<QString>(name: "expectedJson");
411
412 // Function properties should be omitted.
413 QTest::newRow(dataTag: "value = object with function property")
414 << "value" << "{ foo: function() {} }" << "{}";
415 QTest::newRow(dataTag: "object = object with function property")
416 << "object" << "{ foo: function() {} }" << "{}";
417 QTest::newRow(dataTag: "array = array with function property")
418 << "array" << "[function() {}]" << "[null]";
419
420 // Inherited properties should not be included.
421 QTest::newRow(dataTag: "value = object with inherited property")
422 << "value" << "{ __proto__: { proto_foo: 123 } }"
423 << "{}";
424 QTest::newRow(dataTag: "value = object with inherited property 2")
425 << "value" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
426 << "{\"foo\":123}";
427 QTest::newRow(dataTag: "value = array with inherited property")
428 << "value" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
429 << "[]";
430 QTest::newRow(dataTag: "value = array with inherited property 2")
431 << "value" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
432 << "[10,20]";
433
434 QTest::newRow(dataTag: "object = object with inherited property")
435 << "object" << "{ __proto__: { proto_foo: 123 } }"
436 << "{}";
437 QTest::newRow(dataTag: "object = object with inherited property 2")
438 << "object" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
439 << "{\"foo\":123}";
440 QTest::newRow(dataTag: "array = array with inherited property")
441 << "array" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
442 << "[]";
443 QTest::newRow(dataTag: "array = array with inherited property 2")
444 << "array" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
445 << "[10,20]";
446
447 // Non-enumerable properties should not be included.
448 QTest::newRow(dataTag: "value = object with non-enumerable property")
449 << "value" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
450 << "{}";
451 QTest::newRow(dataTag: "object = object with non-enumerable property")
452 << "object" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
453 << "{}";
454
455 // Cyclic data structures are permitted, but the cyclic links become
456 // empty objects.
457 QTest::newRow(dataTag: "value = cyclic object")
458 << "value" << "(function() { var o = { foo: 123 }; o.o = o; return o; })()"
459 << "{\"foo\":123,\"o\":{}}";
460 QTest::newRow(dataTag: "value = cyclic array")
461 << "value" << "(function() { var a = [10, 20]; a.push(a); return a; })()"
462 << "[10,20,[]]";
463 QTest::newRow(dataTag: "object = cyclic object")
464 << "object" << "(function() { var o = { bar: true }; o.o = o; return o; })()"
465 << "{\"bar\":true,\"o\":{}}";
466 QTest::newRow(dataTag: "array = cyclic array")
467 << "array" << "(function() { var a = [30, 40]; a.unshift(a); return a; })()"
468 << "[[],30,40]";
469
470 // Properties with undefined value are excluded.
471 QTest::newRow(dataTag: "value = { foo: undefined }")
472 << "value" << "{ foo: undefined }" << "{}";
473 QTest::newRow(dataTag: "value = { foo: undefined, bar: 123 }")
474 << "value" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
475 QTest::newRow(dataTag: "value = { foo: 456, bar: undefined }")
476 << "value" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
477
478 QTest::newRow(dataTag: "object = { foo: undefined }")
479 << "object" << "{ foo: undefined }" << "{}";
480 QTest::newRow(dataTag: "object = { foo: undefined, bar: 123 }")
481 << "object" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
482 QTest::newRow(dataTag: "object = { foo: 456, bar: undefined }")
483 << "object" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
484
485 // QJsonArray::append() implicitly converts undefined values to null.
486 QTest::newRow(dataTag: "value = [undefined]")
487 << "value" << "[undefined]" << "[null]";
488 QTest::newRow(dataTag: "value = [undefined, 10]")
489 << "value" << "[undefined, 10]" << "[null,10]";
490 QTest::newRow(dataTag: "value = [10, undefined, 20]")
491 << "value" << "[10, undefined, 20]" << "[10,null,20]";
492
493 QTest::newRow(dataTag: "array = [undefined]")
494 << "array" << "[undefined]" << "[null]";
495 QTest::newRow(dataTag: "array = [undefined, 10]")
496 << "array" << "[undefined, 10]" << "[null,10]";
497 QTest::newRow(dataTag: "array = [10, undefined, 20]")
498 << "array" << "[10, undefined, 20]" << "[10,null,20]";
499}
500
501void tst_qjsonbinding::writeProperty_javascriptExpression()
502{
503 QFETCH(QString, property);
504 QFETCH(QString, expression);
505 QFETCH(QString, expectedJson);
506
507 QJSEngine eng;
508 JsonPropertyObject obj;
509 eng.globalObject().setProperty(name: "obj", value: eng.newQObject(object: &obj));
510
511 QJSValue ret = eng.evaluate(program: QString::fromLatin1(str: "obj.%0 = %1; JSON.stringify(obj.%0)")
512 .arg(a: property).arg(a: expression));
513 QVERIFY(!ret.isError());
514 QCOMPARE(ret.toString(), expectedJson);
515}
516
517QTEST_MAIN(tst_qjsonbinding)
518
519#include "tst_qjsonbinding.moc"
520

source code of qtdeclarative/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp