1/****************************************************************************
2**
3** Copyright (C) 2018 Intel Corporation.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtCore/qcborvalue.h>
41#include <QtTest>
42
43Q_DECLARE_METATYPE(QCborValue)
44
45class tst_QCborValue_Json : public QObject
46{
47 Q_OBJECT
48
49private slots:
50 void toVariant_data();
51 void toVariant();
52 void toJson_data() { toVariant_data(); }
53 void toJson();
54 void taggedByteArrayToJson_data();
55 void taggedByteArrayToJson();
56
57 void fromVariant_data() { toVariant_data(); }
58 void fromVariant();
59 void fromJson_data();
60 void fromJson();
61
62 void nonStringKeysInMaps_data();
63 void nonStringKeysInMaps();
64};
65
66void tst_QCborValue_Json::toVariant_data()
67{
68 QTest::addColumn<QCborValue>(name: "v");
69 QTest::addColumn<QVariant>(name: "variant");
70 QTest::addColumn<QJsonValue>(name: "json");
71 QDateTime dt = QDateTime::currentDateTimeUtc();
72 QUuid uuid = QUuid::createUuid();
73
74 QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>();
75 auto add = [me](const QCborValue &v, const QVariant &exp, const QJsonValue &json) {
76 auto addRow = [=]() -> QTestData & {
77 const char *typeString = me.valueToKey(value: v.type());
78 if (v.type() == QCborValue::Integer)
79 return QTest::addRow(format: "Integer:%lld", exp.toLongLong());
80 if (v.type() == QCborValue::Double)
81 return QTest::addRow(format: "Double:%g", exp.toDouble());
82 if (v.type() == QCborValue::ByteArray || v.type() == QCborValue::String)
83 return QTest::addRow(format: "%s:%d", typeString, exp.toString().size());
84 if (v.type() >= 0x10000)
85 return QTest::newRow(dataTag: exp.typeName());
86 return QTest::newRow(dataTag: typeString);
87 };
88 addRow() << v << exp << json;
89 };
90
91 // good JSON matching:
92 add(QCborValue(), QVariant(), QJsonValue::Null);
93 add(nullptr, QVariant::fromValue(value: nullptr), QJsonValue::Null);
94 add(false, false, false);
95 add(true, true, true);
96 add(0, 0, 0);
97 add(1, 1, 1);
98 add(-1, -1, -1);
99 add(0., 0., 0.);
100 add(1.25, 1.25, 1.25);
101 add(-1.25, -1.25, -1.25);
102 add("Hello", "Hello", "Hello");
103
104 // converts to string in JSON:
105 add(QByteArray("Hello"), QByteArray("Hello"), "SGVsbG8");
106 add(QCborValue(dt), dt, dt.toString(format: Qt::ISODateWithMs));
107 add(QCborValue(QUrl("http://example.com/{q}")), QUrl("http://example.com/{q}"),
108 "http://example.com/%7Bq%7D"); // note the encoded form in JSON
109 add(QCborValue(QRegularExpression(".")), QRegularExpression("."), ".");
110 add(QCborValue(uuid), uuid, uuid.toString(mode: QUuid::WithoutBraces));
111
112 // not valid in JSON
113 QTest::newRow(dataTag: "simpletype") << QCborValue(QCborSimpleType(255))
114 << QVariant::fromValue(value: QCborSimpleType(255))
115 << QJsonValue("simple(255)");
116 QTest::newRow(dataTag: "Double:inf") << QCborValue(qInf())
117 << QVariant(qInf())
118 << QJsonValue();
119 QTest::newRow(dataTag: "Double:-inf") << QCborValue(-qInf())
120 << QVariant(-qInf())
121 << QJsonValue();
122 QTest::newRow(dataTag: "Double:nan") << QCborValue(qQNaN())
123 << QVariant(qQNaN())
124 << QJsonValue();
125
126 // large integral values lose precision in JSON
127 QTest::newRow(dataTag: "Integer:max") << QCborValue(std::numeric_limits<qint64>::max())
128 << QVariant(std::numeric_limits<qint64>::max())
129 << QJsonValue(std::numeric_limits<qint64>::max());
130 QTest::newRow(dataTag: "Integer:min") << QCborValue(std::numeric_limits<qint64>::min())
131 << QVariant(std::numeric_limits<qint64>::min())
132 << QJsonValue(std::numeric_limits<qint64>::min());
133
134 // empty arrays and maps
135 add(QCborArray(), QVariantList(), QJsonArray());
136 add(QCborMap(), QVariantMap(), QJsonObject());
137}
138
139void tst_QCborValue_Json::toVariant()
140{
141 QFETCH(QCborValue, v);
142 QFETCH(QVariant, variant);
143
144 if (qIsNaN(d: variant.toDouble())) {
145 // because NaN != NaN, QVariant(NaN) != QVariant(NaN), so we
146 // only need to compare the classification
147 QVERIFY(qIsNaN(v.toVariant().toDouble()));
148
149 // the rest of this function depends on the variant comparison
150 return;
151 }
152
153 QCOMPARE(v.toVariant(), variant);
154 if (variant.isValid()) {
155 QVariant variant2 = QVariant::fromValue(value: v);
156 QVERIFY(variant2.canConvert(variant.userType()));
157 QVERIFY(variant2.convert(variant.userType()));
158 QCOMPARE(variant2, variant);
159 }
160
161 // tags get ignored:
162 QCOMPARE(QCborValue(QCborKnownTags::Signature, v).toVariant(), variant);
163
164 // make arrays with this item
165 QCOMPARE(QCborArray({v}).toVariantList(), QVariantList({variant}));
166 QCOMPARE(QCborArray({v, v}).toVariantList(), QVariantList({variant, variant}));
167
168 // and maps
169 QCOMPARE(QCborMap({{"foo", v}}).toVariantMap(), QVariantMap({{"foo", variant}}));
170 QCOMPARE(QCborMap({{"foo", v}}).toVariantHash(), QVariantHash({{"foo", variant}}));
171
172 // finally, mixed
173 QCOMPARE(QCborArray{QCborMap({{"foo", v}})}.toVariantList(),
174 QVariantList{QVariantMap({{"foo", variant}})});
175}
176
177void tst_QCborValue_Json::toJson()
178{
179 QFETCH(QCborValue, v);
180 QFETCH(QJsonValue, json);
181
182 QCOMPARE(v.toJsonValue(), json);
183 QCOMPARE(QVariant::fromValue(v).toJsonValue(), json);
184
185 // most tags get ignored:
186 QCOMPARE(QCborValue(QCborKnownTags::Signature, v).toJsonValue(), json);
187
188 // make arrays with this item
189 QCOMPARE(QCborArray({v}).toJsonArray(), QJsonArray({json}));
190 QCOMPARE(QCborArray({v, v}).toJsonArray(), QJsonArray({json, json}));
191
192 // and maps
193 QCOMPARE(QCborMap({{"foo", v}}).toJsonObject(), QJsonObject({{"foo", json}}));
194
195 // finally, mixed
196 QCOMPARE(QCborArray{QCborMap({{"foo", v}})}.toJsonArray(),
197 QJsonArray{QJsonObject({{"foo", json}})});
198}
199
200void tst_QCborValue_Json::taggedByteArrayToJson_data()
201{
202 QTest::addColumn<QCborValue>(name: "v");
203 QTest::addColumn<QJsonValue>(name: "json");
204
205 QByteArray data("\xff\x01");
206 QTest::newRow(dataTag: "base64url") << QCborValue(QCborKnownTags::ExpectedBase64url, data) << QJsonValue("_wE");
207 QTest::newRow(dataTag: "base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << QJsonValue("/wE=");
208 QTest::newRow(dataTag: "hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << QJsonValue("ff01");
209}
210
211void tst_QCborValue_Json::taggedByteArrayToJson()
212{
213 QFETCH(QCborValue, v);
214 QFETCH(QJsonValue, json);
215
216 QCOMPARE(v.toJsonValue(), json);
217 QCOMPARE(QCborArray({v}).toJsonArray(), QJsonArray({json}));
218}
219
220void tst_QCborValue_Json::fromVariant()
221{
222 QFETCH(QCborValue, v);
223 QFETCH(QVariant, variant);
224
225 QCOMPARE(QCborValue::fromVariant(variant), v);
226 QCOMPARE(variant.value<QCborValue>(), v);
227
228 // try arrays
229 QCOMPARE(QCborArray::fromVariantList({variant}), QCborArray{v});
230 QCOMPARE(QCborArray::fromVariantList({variant, variant}), QCborArray({v, v}));
231
232 if (variant.type() == QVariant::String) {
233 QString s = variant.toString();
234 QCOMPARE(QCborArray::fromStringList({s}), QCborArray{v});
235 QCOMPARE(QCborArray::fromStringList({s, s}), QCborArray({v, v}));
236 }
237
238 // maps...
239 QVariantMap map{{"foo", variant}};
240 QCOMPARE(QCborMap::fromVariantMap(map), QCborMap({{"foo", v}}));
241 QCOMPARE(QCborMap::fromVariantHash({{"foo", variant}}), QCborMap({{"foo", v}}));
242
243 // nested
244 QVariantMap outer{{"bar", QVariantList{0, map, true}}};
245 QCOMPARE(QCborMap::fromVariantMap(outer),
246 QCborMap({{"bar", QCborArray{0, QCborMap{{"foo", v}}, true}}}));
247}
248
249void tst_QCborValue_Json::fromJson_data()
250{
251 QTest::addColumn<QCborValue>(name: "v");
252 QTest::addColumn<QJsonValue>(name: "json");
253
254 QTest::newRow(dataTag: "null") << QCborValue(QCborValue::Null) << QJsonValue(QJsonValue::Null);
255 QTest::newRow(dataTag: "false") << QCborValue(false) << QJsonValue(false);
256 QTest::newRow(dataTag: "true") << QCborValue(true) << QJsonValue(true);
257 QTest::newRow(dataTag: "0") << QCborValue(0) << QJsonValue(0.);
258 QTest::newRow(dataTag: "1") << QCborValue(1) << QJsonValue(1);
259 QTest::newRow(dataTag: "1.5") << QCborValue(1.5) << QJsonValue(1.5);
260 QTest::newRow(dataTag: "string") << QCborValue("Hello") << QJsonValue("Hello");
261 QTest::newRow(dataTag: "array") << QCborValue(QCborValue::Array) << QJsonValue(QJsonValue::Array);
262 QTest::newRow(dataTag: "map") << QCborValue(QCborValue::Map) << QJsonValue(QJsonValue::Object);
263}
264
265void tst_QCborValue_Json::fromJson()
266{
267 QFETCH(QCborValue, v);
268 QFETCH(QJsonValue, json);
269
270 QCOMPARE(QCborValue::fromJsonValue(json), v);
271 QCOMPARE(QVariant(json).value<QCborValue>(), v);
272 QCOMPARE(QCborArray::fromJsonArray({json}), QCborArray({v}));
273 QCOMPARE(QCborArray::fromJsonArray({json, json}), QCborArray({v, v}));
274 QCOMPARE(QCborMap::fromJsonObject({{"foo", json}}), QCborMap({{"foo", v}}));
275
276 // confirm we can roundtrip back to JSON
277 QCOMPARE(QCborValue::fromJsonValue(json).toJsonValue(), json);
278}
279
280void tst_QCborValue_Json::nonStringKeysInMaps_data()
281{
282 QTest::addColumn<QCborValue>(name: "key");
283 QTest::addColumn<QString>(name: "converted");
284
285 auto add = [](const char *str, const QCborValue &v) {
286 QTest::newRow(dataTag: str) << v << str;
287 };
288 add("0", 0);
289 add("-1", -1);
290 add("false", false);
291 add("true", true);
292 add("null", nullptr);
293 add("undefined", {}); // should this be ""?
294 add("simple(255)", QCborSimpleType(255));
295 add("2.5", 2.5);
296
297 QByteArray data("\xff\x01");
298 QTest::newRow(dataTag: "bytearray") << QCborValue(data) << "_wE";
299 QTest::newRow(dataTag: "base64url") << QCborValue(QCborKnownTags::ExpectedBase64url, data) << "_wE";
300 QTest::newRow(dataTag: "base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE=";
301 QTest::newRow(dataTag: "hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01";
302
303 QTest::newRow(dataTag: "emptyarray") << QCborValue(QCborValue::Array) << "[]";
304 QTest::newRow(dataTag: "emptymap") << QCborValue(QCborValue::Map) << "{}";
305 QTest::newRow(dataTag: "array") << QCborValue(QCborArray{1, true, 2.5, "Hello"})
306 << "[1, true, 2.5, \"Hello\"]";
307 QTest::newRow(dataTag: "map") << QCborValue(QCborMap{{"Hello", 0}, {0, "Hello"}})
308 << "{\"Hello\": 0, 0: \"Hello\"}";
309
310 QDateTime dt = QDateTime::currentDateTimeUtc();
311 QUrl url("https://example.com");
312 QUuid uuid = QUuid::createUuid();
313 QTest::newRow(dataTag: "QDateTime") << QCborValue(dt) << dt.toString(format: Qt::ISODateWithMs);
314 QTest::newRow(dataTag: "QUrl") << QCborValue(url) << url.toString(options: QUrl::FullyEncoded);
315 QTest::newRow(dataTag: "QRegularExpression") << QCborValue(QRegularExpression(".*")) << ".*";
316 QTest::newRow(dataTag: "QUuid") << QCborValue(uuid) << uuid.toString(mode: QUuid::WithoutBraces);
317}
318
319void tst_QCborValue_Json::nonStringKeysInMaps()
320{
321 QFETCH(QCborValue, key);
322 QFETCH(QString, converted);
323
324 QCborMap m;
325 m.insert(key, value_: 0);
326
327 {
328 QVariantMap vm = m.toVariantMap();
329 auto it = vm.begin();
330 QVERIFY(it != vm.end());
331 QCOMPARE(it.key(), converted);
332 QCOMPARE(it.value(), 0);
333 QCOMPARE(++it, vm.end());
334 }
335
336 {
337 QJsonObject o = m.toJsonObject();
338 auto it = o.begin();
339 QVERIFY(it != o.end());
340 QCOMPARE(it.key(), converted);
341 QCOMPARE(it.value(), 0);
342 QCOMPARE(++it, o.end());
343 }
344}
345
346QTEST_MAIN(tst_QCborValue_Json)
347
348#include "tst_qcborvalue_json.moc"
349

source code of qtbase/tests/auto/corelib/serialization/qcborvalue_json/tst_qcborvalue_json.cpp