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 | |
43 | Q_DECLARE_METATYPE(QCborValue) |
44 | |
45 | class tst_QCborValue_Json : public QObject |
46 | { |
47 | Q_OBJECT |
48 | |
49 | private 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 | |
66 | void 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 | |
139 | void 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 | |
177 | void 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 | |
200 | void 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 | |
211 | void 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 | |
220 | void 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 | |
249 | void 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 | |
265 | void 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 | |
280 | void 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 | |
319 | void 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 | |
346 | QTEST_MAIN(tst_QCborValue_Json) |
347 | |
348 | #include "tst_qcborvalue_json.moc" |
349 | |