QCborValue: add support for QVariant and JSON conversions
Plus QStringList. Change-Id: I39332e0a867442d58082fffd1508dfb9b540af23 Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>bb10
parent
49c9377421
commit
c4ed96513e
|
|
@ -247,6 +247,12 @@ public:
|
|||
QCborArray &operator<<(const QCborValue &v)
|
||||
{ append(v); return *this; }
|
||||
|
||||
static QCborArray fromStringList(const QStringList &list);
|
||||
static QCborArray fromVariantList(const QVariantList &list);
|
||||
static QCborArray fromJsonArray(const QJsonArray &array);
|
||||
QVariantList toVariantList() const;
|
||||
QJsonArray toJsonArray() const;
|
||||
|
||||
private:
|
||||
void detach(qsizetype reserve = 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -295,6 +295,13 @@ public:
|
|||
}
|
||||
iterator insert(value_type v) { return insert(v.first, v.second); }
|
||||
|
||||
static QCborMap fromVariantMap(const QVariantMap &map);
|
||||
static QCborMap fromVariantHash(const QVariantHash &hash);
|
||||
static QCborMap fromJsonObject(const QJsonObject &o);
|
||||
QVariantMap toVariantMap() const;
|
||||
QVariantHash toVariantHash() const;
|
||||
QJsonObject toJsonObject() const;
|
||||
|
||||
private:
|
||||
void detach(qsizetype reserve = 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -262,6 +262,11 @@ public:
|
|||
{ return compare(other) < 0; }
|
||||
#endif
|
||||
|
||||
static QCborValue fromVariant(const QVariant &variant);
|
||||
QVariant toVariant() const;
|
||||
static QCborValue fromJsonValue(const QJsonValue &v);
|
||||
QJsonValue toJsonValue() const;
|
||||
|
||||
static QCborValue fromCbor(QCborStreamReader &reader);
|
||||
static QCborValue fromCbor(const QByteArray &ba, QCborParserError *error = nullptr);
|
||||
static QCborValue fromCbor(const char *data, qsizetype len, QCborParserError *error = nullptr)
|
||||
|
|
@ -392,6 +397,9 @@ public:
|
|||
{ return compare(other) < 0; }
|
||||
#endif
|
||||
|
||||
QVariant toVariant() const { return concrete().toVariant(); }
|
||||
QJsonValue toJsonValue() const;
|
||||
|
||||
QByteArray toCbor(QCborValue::EncodingOptions opt = QCborValue::NoTransformation)
|
||||
{ return concrete().toCbor(opt); }
|
||||
void toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt = QCborValue::NoTransformation);
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ struct Element
|
|||
}
|
||||
};
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Element::ValueFlags)
|
||||
Q_STATIC_ASSERT(sizeof(Element) == 16);
|
||||
|
||||
struct ByteData
|
||||
{
|
||||
|
|
@ -112,8 +113,6 @@ struct ByteData
|
|||
QStringView asStringView() const{ return QStringView(utf16(), len / 2); }
|
||||
QString asQStringRaw() const { return QString::fromRawData(utf16(), len / 2); }
|
||||
};
|
||||
|
||||
Q_STATIC_ASSERT(sizeof(Element) == 16);
|
||||
Q_STATIC_ASSERT(std::is_pod<ByteData>::value);
|
||||
} // namespace QtCbor
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,948 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qcborvalue.h"
|
||||
#include "qcborvalue_p.h"
|
||||
|
||||
#include "qcborarray.h"
|
||||
#include "qcbormap.h"
|
||||
#include "qjson_p.h"
|
||||
|
||||
#include <private/qnumeric_p.h>
|
||||
#include <quuid.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace QtCbor;
|
||||
|
||||
static QJsonValue fpToJson(double v)
|
||||
{
|
||||
return qt_is_finite(v) ? QJsonValue(v) : QJsonValue();
|
||||
}
|
||||
|
||||
static QString simpleTypeString(QCborValue::Type t)
|
||||
{
|
||||
int simpleType = t - QCborValue::SimpleType;
|
||||
if (unsigned(simpleType) < 0x100)
|
||||
return QString::fromLatin1("simple(%1)").arg(simpleType);
|
||||
|
||||
// if we got here, we got an unknown type
|
||||
qWarning("QCborValue: found unknown type 0x%x", t);
|
||||
return QString();
|
||||
|
||||
}
|
||||
|
||||
static QString encodeByteArray(const QCborContainerPrivate *d, qsizetype idx, QCborTag encoding)
|
||||
{
|
||||
const ByteData *b = d->byteData(idx);
|
||||
if (!b)
|
||||
return QString();
|
||||
|
||||
QByteArray data = QByteArray::fromRawData(b->byte(), b->len);
|
||||
if (encoding == QCborKnownTags::ExpectedBase16)
|
||||
data = data.toHex();
|
||||
else if (encoding == QCborKnownTags::ExpectedBase64)
|
||||
data = data.toBase64();
|
||||
else
|
||||
data = data.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
return QString::fromLatin1(data, data.size());
|
||||
}
|
||||
|
||||
static QString makeString(const QCborContainerPrivate *d, qsizetype idx);
|
||||
|
||||
static QString maybeEncodeTag(const QCborContainerPrivate *d)
|
||||
{
|
||||
qint64 tag = d->elements.at(0).value;
|
||||
const Element &e = d->elements.at(1);
|
||||
const ByteData *b = d->byteData(e);
|
||||
|
||||
switch (tag) {
|
||||
case qint64(QCborKnownTags::DateTimeString):
|
||||
case qint64(QCborKnownTags::Url):
|
||||
if (e.type == QCborValue::String)
|
||||
return makeString(d, 1);
|
||||
break;
|
||||
|
||||
case qint64(QCborKnownTags::ExpectedBase64url):
|
||||
case qint64(QCborKnownTags::ExpectedBase64):
|
||||
case qint64(QCborKnownTags::ExpectedBase16):
|
||||
if (e.type == QCborValue::ByteArray)
|
||||
return encodeByteArray(d, 1, QCborTag(tag));
|
||||
break;
|
||||
|
||||
case qint64(QCborKnownTags::Uuid):
|
||||
if (e.type == QCborValue::ByteArray && b->len == sizeof(QUuid))
|
||||
return QUuid::fromRfc4122(b->asByteArrayView()).toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
// don't know what to do, bail out
|
||||
return QString();
|
||||
}
|
||||
|
||||
static QString encodeTag(const QCborContainerPrivate *d)
|
||||
{
|
||||
QString s;
|
||||
if (!d || d->elements.size() != 2)
|
||||
return s; // invalid (incomplete?) tag state
|
||||
|
||||
s = maybeEncodeTag(d);
|
||||
if (s.isNull()) {
|
||||
// conversion failed, ignore the tag and convert the tagged value
|
||||
s = makeString(d, 1);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static Q_NEVER_INLINE QString makeString(const QCborContainerPrivate *d, qsizetype idx)
|
||||
{
|
||||
const auto &e = d->elements.at(idx);
|
||||
|
||||
switch (e.type) {
|
||||
case QCborValue::Integer:
|
||||
return QString::number(qint64(e.value));
|
||||
|
||||
case QCborValue::Double:
|
||||
return QString::number(e.fpvalue());
|
||||
|
||||
case QCborValue::ByteArray:
|
||||
return encodeByteArray(d, idx, QCborTag(QCborKnownTags::ExpectedBase64url));
|
||||
|
||||
case QCborValue::String:
|
||||
return d->stringAt(idx);
|
||||
|
||||
case QCborValue::Array:
|
||||
case QCborValue::Map:
|
||||
return d->valueAt(idx).toDiagnosticNotation(QCborValue::Compact);
|
||||
|
||||
case QCborValue::SimpleType:
|
||||
break;
|
||||
|
||||
case QCborValue::False:
|
||||
return QStringLiteral("false");
|
||||
|
||||
case QCborValue::True:
|
||||
return QStringLiteral("true");
|
||||
|
||||
case QCborValue::Null:
|
||||
return QStringLiteral("null");
|
||||
|
||||
case QCborValue::Undefined:
|
||||
return QStringLiteral("undefined");
|
||||
|
||||
case QCborValue::Invalid:
|
||||
return QString();
|
||||
|
||||
case QCborValue::Tag:
|
||||
case QCborValue::DateTime:
|
||||
case QCborValue::Url:
|
||||
case QCborValue::RegularExpression:
|
||||
case QCborValue::Uuid:
|
||||
return encodeTag(e.flags & Element::IsContainer ? e.container : nullptr);
|
||||
}
|
||||
|
||||
// maybe it's a simple type
|
||||
return simpleTypeString(e.type);
|
||||
}
|
||||
|
||||
static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx);
|
||||
|
||||
static QJsonArray convertToJsonArray(const QCborContainerPrivate *d)
|
||||
{
|
||||
QJsonArray a;
|
||||
if (d) {
|
||||
for (qsizetype idx = 0; idx < d->elements.size(); ++idx)
|
||||
a.append(convertToJson(d, idx));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static QJsonObject convertToJsonObject(const QCborContainerPrivate *d)
|
||||
{
|
||||
QJsonObject o;
|
||||
if (d) {
|
||||
for (qsizetype idx = 0; idx < d->elements.size(); idx += 2)
|
||||
o.insert(makeString(d, idx), convertToJson(d, idx + 1));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
static QJsonValue convertExtendedTypeToJson(const QCborContainerPrivate *d)
|
||||
{
|
||||
qint64 tag = d->elements.at(0).value;
|
||||
|
||||
switch (tag) {
|
||||
case qint64(QCborKnownTags::Url):
|
||||
// use the fullly-encoded URL form
|
||||
if (d->elements.at(1).type == QCborValue::String)
|
||||
return QUrl::fromEncoded(d->byteData(1)->asByteArrayView()).toString(QUrl::FullyEncoded);
|
||||
Q_FALLTHROUGH();
|
||||
|
||||
case qint64(QCborKnownTags::DateTimeString):
|
||||
case qint64(QCborKnownTags::ExpectedBase64url):
|
||||
case qint64(QCborKnownTags::ExpectedBase64):
|
||||
case qint64(QCborKnownTags::ExpectedBase16):
|
||||
case qint64(QCborKnownTags::Uuid): {
|
||||
// use the string conversion
|
||||
QString s = maybeEncodeTag(d);
|
||||
if (!s.isNull())
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// for all other tags, ignore it and return the converted tagged item
|
||||
return convertToJson(d, 1);
|
||||
}
|
||||
|
||||
static QJsonValue convertToJson(const QCborContainerPrivate *d, qsizetype idx)
|
||||
{
|
||||
// encoding the container itself
|
||||
if (idx == -QCborValue::Array)
|
||||
return convertToJsonArray(d);
|
||||
if (idx == -QCborValue::Map)
|
||||
return convertToJsonObject(d);
|
||||
if (idx < 0) {
|
||||
// tag-like type
|
||||
if (!d || d->elements.size() != 2)
|
||||
return QJsonValue::Undefined; // invalid state
|
||||
return convertExtendedTypeToJson(d);
|
||||
}
|
||||
|
||||
// an element in the container
|
||||
const auto &e = d->elements.at(idx);
|
||||
switch (e.type) {
|
||||
case QCborValue::Integer:
|
||||
return qint64(e.value);
|
||||
|
||||
case QCborValue::ByteArray:
|
||||
case QCborValue::String:
|
||||
case QCborValue::SimpleType:
|
||||
// make string
|
||||
break;
|
||||
|
||||
case QCborValue::Array:
|
||||
case QCborValue::Map:
|
||||
case QCborValue::Tag:
|
||||
case QCborValue::DateTime:
|
||||
case QCborValue::Url:
|
||||
case QCborValue::RegularExpression:
|
||||
case QCborValue::Uuid:
|
||||
// recurse
|
||||
return convertToJson(e.flags & Element::IsContainer ? e.container : nullptr, -e.type);
|
||||
|
||||
case QCborValue::Null:
|
||||
return QJsonValue();
|
||||
|
||||
case QCborValue::Undefined:
|
||||
case QCborValue::Invalid:
|
||||
return QJsonValue(QJsonValue::Undefined);
|
||||
|
||||
case QCborValue::False:
|
||||
return false;
|
||||
|
||||
case QCborValue::True:
|
||||
return true;
|
||||
|
||||
case QCborValue::Double:
|
||||
return fpToJson(e.fpvalue());
|
||||
}
|
||||
|
||||
return makeString(d, idx);
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts this QCborValue object to an equivalent representation in JSON and
|
||||
returns it as a QJsonValue.
|
||||
|
||||
Please note that CBOR contains a richer and wider type set than JSON, so
|
||||
some information may be lost in this conversion. The following table
|
||||
compares CBOR types to JSON types and indicates whether information may be
|
||||
lost or not.
|
||||
|
||||
\table
|
||||
\header \li CBOR Type \li JSON Type \li Comments
|
||||
\row \li Bool \li Bool \li No data loss possible
|
||||
\row \li Double \li Number \li Infinities and NaN will be converted to Null;
|
||||
no data loss for other values
|
||||
\row \li Integer \li Number \li Data loss possible in the conversion if the
|
||||
integer is larger than 2\sup{53} or smaller
|
||||
than -2\sup{53}.
|
||||
\row \li Null \li Null \li No data loss possible
|
||||
\row \li Undefined \li Null \li Type information lost
|
||||
\row \li String \li String \li No data loss possible
|
||||
\row \li Byte Array \li String \li Converted to a lossless encoding like Base64url,
|
||||
but the distinction between strings and byte
|
||||
arrays is lost
|
||||
\row \li Other simple types \li String \li Type information lost
|
||||
\row \li Array \li Array \li Conversion applies to each contained value
|
||||
\row \li Map \li Object \li Keys are converted to string; values converted
|
||||
according to this table
|
||||
\row \li Tags and extended types \li Special \li The tag number itself is lost and the tagged
|
||||
value is converted to JSON
|
||||
\endtable
|
||||
|
||||
For information on the conversion of CBOR map keys to string, see
|
||||
QCborMap::toJsonObject().
|
||||
|
||||
If this QCborValue contains the undefined value, this function will return
|
||||
an undefined QJsonValue too. Note that JSON does not support undefined
|
||||
values and undefined QJsonValues are an extension to the specification.
|
||||
They cannot be held in a QJsonArray or QJsonObject, but can be returned
|
||||
from functions to indicate a failure. For all other intents and purposes,
|
||||
they are the same as null.
|
||||
|
||||
\section3 Special handling of tags and extended types
|
||||
|
||||
Some tags are handled specially and change the transformation of the tagged
|
||||
value from CBOR to JSON. The following table lists those special cases:
|
||||
|
||||
\table
|
||||
\header \li Tag \li CBOR type \li Transformation
|
||||
\row \li ExpectedBase64url \li Byte array \li Encodes the byte array as Base64url
|
||||
\row \li ExpectedBase64 \li Byte array \li Encodes the byte array as Base64
|
||||
\row \li ExpectedBase16 \li Byte array \li Encodes the byte array as hex
|
||||
\row \li Url \li Url and String \li Uses QUrl::toEncoded() to normalize the
|
||||
encoding to the URL's fully encoded format
|
||||
\row \li Uuid \li Uuid and Byte array \li Uses QUuid::toString() to create
|
||||
the string representation
|
||||
\endtable
|
||||
|
||||
\sa fromJsonValue(), toVariant(), QCborArray::toJsonArray(), QCborMap::toJsonObject()
|
||||
*/
|
||||
QJsonValue QCborValue::toJsonValue() const
|
||||
{
|
||||
if (container)
|
||||
return convertToJson(container, n < 0 ? -type() : n);
|
||||
|
||||
// simple values
|
||||
switch (type()) {
|
||||
case Integer:
|
||||
return n;
|
||||
|
||||
case Null:
|
||||
return QJsonValue();
|
||||
|
||||
case False:
|
||||
return false;
|
||||
|
||||
case True:
|
||||
return true;
|
||||
|
||||
case Double:
|
||||
return fpToJson(fp_helper());
|
||||
|
||||
case SimpleType:
|
||||
break;
|
||||
|
||||
case Undefined:
|
||||
case Invalid:
|
||||
return QJsonValue(QJsonValue::Undefined);
|
||||
|
||||
case ByteArray:
|
||||
case String:
|
||||
// empty strings
|
||||
return QString();
|
||||
|
||||
case Array:
|
||||
// empty array
|
||||
return QJsonArray();
|
||||
|
||||
case Map:
|
||||
// empty map
|
||||
return QJsonObject();
|
||||
|
||||
case Tag:
|
||||
case DateTime:
|
||||
case Url:
|
||||
case RegularExpression:
|
||||
case Uuid:
|
||||
Q_UNREACHABLE();
|
||||
return QJsonValue::Undefined;
|
||||
}
|
||||
|
||||
return simpleTypeString(type());
|
||||
}
|
||||
|
||||
QJsonValue QCborValueRef::toJsonValue() const
|
||||
{
|
||||
return convertToJson(d, i);
|
||||
}
|
||||
|
||||
/*!
|
||||
Recursively converts every \l QCborValue element in this array to JSON
|
||||
using QCborValue::toJsonValue() and returns the corresponding QJsonArray
|
||||
composed of those elements.
|
||||
|
||||
Please note that CBOR contains a richer and wider type set than JSON, so
|
||||
some information may be lost in this conversion. For more details on what
|
||||
conversions are applied, see QCborValue::toJsonValue().
|
||||
|
||||
\sa fromJsonArray(), QCborValue::toJsonValue(), QCborMap::toJsonObject(), toVariantList()
|
||||
*/
|
||||
QJsonArray QCborArray::toJsonArray() const
|
||||
{
|
||||
return convertToJsonArray(d.data());
|
||||
}
|
||||
|
||||
/*!
|
||||
Recursively converts every \l QCborValue value in this array to JSON using
|
||||
QCborValue::toJsonValue() and creates a string key for all keys that aren't
|
||||
strings, then returns the corresponding QJsonObject composed of those
|
||||
associations.
|
||||
|
||||
Please note that CBOR contains a richer and wider type set than JSON, so
|
||||
some information may be lost in this conversion. For more details on what
|
||||
conversions are applied, see QCborValue::toJsonValue().
|
||||
|
||||
\section3 Map key conversion to string
|
||||
|
||||
JSON objects are defined as having string keys, unlike CBOR, so the
|
||||
conversion of a QCborMap to QJsonObject will imply a step of
|
||||
"stringification" of the key values. The conversion will use the special
|
||||
handling of tags and extended types from above and will also convert the
|
||||
rest of the types as follows:
|
||||
|
||||
\table
|
||||
\header \li Type \li Transformation
|
||||
\row \li Bool \li "true" and "false"
|
||||
\row \li Null \li "null"
|
||||
\row \li Undefined \li "undefined"
|
||||
\row \li Integer \li The decimal string form of the number
|
||||
\row \li Double \li The decimal string form of the number
|
||||
\row \li Byte array \li Unless tagged differently (see above), encoded as
|
||||
Base64url
|
||||
\row \li Array \li Replaced by the compact form of its
|
||||
\l{QCborValue::toDiagnosticNotation()}{Diagnostic notation}
|
||||
\row \li Map \li Replaced by the compact form of its
|
||||
\l{QCborValue::toDiagnosticNotation()}{Diagnostic notation}
|
||||
\row \li Tags and extended types \li Tag number is dropped and the tagged value is converted
|
||||
to string
|
||||
\endtable
|
||||
|
||||
\sa fromJsonObject(), QCborValue::toJsonValue(), QCborArray::toJsonArray(), toVariantMap()
|
||||
*/
|
||||
QJsonObject QCborMap::toJsonObject() const
|
||||
{
|
||||
return convertToJsonObject(d.data());
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts this value to a native Qt type and returns the corresponding QVariant.
|
||||
|
||||
The following table lists the mapping performed between \l{Type}{QCborValue
|
||||
types} and \l{QMetaType::Type}{Qt meta types}.
|
||||
|
||||
\table
|
||||
\header \li CBOR Type \li Qt or C++ type \li Notes
|
||||
\row \li Integer \li \l qint64 \li
|
||||
\row \li Double \li \c double \li
|
||||
\row \li Bool \li \c bool \li
|
||||
\row \li Null \li \c std::nullptr_t \li
|
||||
\row \li Undefined \li no type (QVariant()) \li
|
||||
\row \li Byte array \li \l QByteArray \li
|
||||
\row \li String \li \l QString \li
|
||||
\row \li Array \li \l QVariantList \li Recursively converts all values
|
||||
\row \li Map \li \l QVariantMap \li Key types are "stringified"
|
||||
\row \li Other simple types \li \l QCborSimpleType \li
|
||||
\row \li DateTime \li \l QDateTime \li
|
||||
\row \li Url \li \l QUrl \li
|
||||
\row \li RegularExpression \li \l QRegularExpression \li
|
||||
\row \li Uuid \li \l QUuid \li
|
||||
\row \li Other tags \li Special \li The tag is ignored and the tagged
|
||||
value is converted using this
|
||||
function
|
||||
\endtable
|
||||
|
||||
Note that values in both CBOR Maps and Arrays are converted recursively
|
||||
using this function too and placed in QVariantMap and QVariantList instead.
|
||||
You will not find QCborMap and QCborArray stored inside the QVariants.
|
||||
|
||||
QVariantMaps have string keys, unlike CBOR, so the conversion of a QCborMap
|
||||
to QVariantMap will imply a step of "stringification" of the key values.
|
||||
See QCborMap::toJsonObject() for details.
|
||||
|
||||
\sa fromVariant(), toJsonValue(), QCborArray::toVariantList(), QCborMap::toVariantMap()
|
||||
*/
|
||||
QVariant QCborValue::toVariant() const
|
||||
{
|
||||
switch (type()) {
|
||||
case Integer:
|
||||
return toInteger();
|
||||
|
||||
case Double:
|
||||
return toDouble();
|
||||
|
||||
case SimpleType:
|
||||
break;
|
||||
|
||||
case False:
|
||||
case True:
|
||||
return isTrue();
|
||||
|
||||
case Null:
|
||||
return QVariant::fromValue(nullptr);
|
||||
|
||||
case Undefined:
|
||||
return QVariant();
|
||||
|
||||
case ByteArray:
|
||||
return toByteArray();
|
||||
|
||||
case String:
|
||||
return toString();
|
||||
|
||||
case Array:
|
||||
return toArray().toVariantList();
|
||||
|
||||
case Map:
|
||||
return toMap().toVariantMap();
|
||||
|
||||
case Tag:
|
||||
// ignore tags
|
||||
return taggedValue().toVariant();
|
||||
|
||||
case DateTime:
|
||||
return toDateTime();
|
||||
|
||||
case Url:
|
||||
return toUrl();
|
||||
|
||||
case RegularExpression:
|
||||
return toRegularExpression();
|
||||
|
||||
case Uuid:
|
||||
return toUuid();
|
||||
|
||||
case Invalid:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (isSimpleType())
|
||||
return QVariant::fromValue(toSimpleType());
|
||||
|
||||
Q_UNREACHABLE();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts the JSON value contained in \a v into its corresponding CBOR value
|
||||
and returns it. There is no data loss in converting from JSON to CBOR, as
|
||||
the CBOR type set is richer than JSON's. Additionally, values converted to
|
||||
CBOR using this function can be converted back to JSON using toJsonValue()
|
||||
with no data loss.
|
||||
|
||||
The following table lists the mapping of JSON types to CBOR types:
|
||||
|
||||
\table
|
||||
\header \li JSON Type \li CBOR Type
|
||||
\row \li Bool \li Bool
|
||||
\row \li Number \li Integer (if the number has no fraction and is in the \l qint64
|
||||
range) or Double
|
||||
\row \li String \li String
|
||||
\row \li Array \li Array
|
||||
\row \li Object \li Map
|
||||
\row \li Null \li Null
|
||||
\endtable
|
||||
|
||||
\l QJsonValue can also be undefined, indicating a previous operation that
|
||||
failed to complete (for example, searching for a key not present in an
|
||||
object). Undefined values are not JSON types and may not appear in JSON
|
||||
arrays and objects, but this function does return the QCborValue undefined
|
||||
value if the corresponding QJsonValue is undefined.
|
||||
|
||||
\sa toJsonValue(), fromVariant(), QCborArray::fromJsonArray(), QCborMap::fromJsonObject()
|
||||
*/
|
||||
QCborValue QCborValue::fromJsonValue(const QJsonValue &v)
|
||||
{
|
||||
switch (v.type()) {
|
||||
case QJsonValue::Bool:
|
||||
return v.b;
|
||||
case QJsonValue::Double:
|
||||
if (v.dbl == qint64(v.dbl))
|
||||
return qint64(v.dbl);
|
||||
return v.dbl;
|
||||
case QJsonValue::String:
|
||||
return v.toString();
|
||||
case QJsonValue::Array:
|
||||
return QCborArray::fromJsonArray(v.toArray());
|
||||
case QJsonValue::Object:
|
||||
return QCborMap::fromJsonObject(v.toObject());
|
||||
case QJsonValue::Null:
|
||||
return nullptr;
|
||||
case QJsonValue::Undefined:
|
||||
break;
|
||||
}
|
||||
return QCborValue();
|
||||
}
|
||||
|
||||
static void appendVariant(QCborContainerPrivate *d, const QVariant &variant)
|
||||
{
|
||||
// Handle strings and byte arrays directly, to avoid creating a temporary
|
||||
// dummy container to hold their data.
|
||||
int type = variant.userType();
|
||||
if (type == QVariant::String) {
|
||||
d->append(variant.toString());
|
||||
} else if (type == QVariant::ByteArray) {
|
||||
QByteArray ba = variant.toByteArray();
|
||||
d->appendByteData(ba.constData(), ba.size(), QCborValue::ByteArray);
|
||||
} else {
|
||||
// For everything else, use the function below.
|
||||
d->append(QCborValue::fromVariant(variant));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts the QVariant \a variant into QCborValue and returns it.
|
||||
|
||||
QVariants may contain a large list of different meta types, many of which
|
||||
have no corresponding representation in CBOR. That includes all
|
||||
user-defined meta types. When preparing transmission using CBOR, it is
|
||||
suggested to encode carefully each value to prevent loss of representation.
|
||||
|
||||
The following table lists the conversion this function will apply:
|
||||
|
||||
\table
|
||||
\header \li Qt (C++) type \li CBOR type
|
||||
\row \li invalid (QVariant()) \li Undefined
|
||||
\row \li \c bool \li Bool
|
||||
\row \li \c std::nullptr_t \li Null
|
||||
\row \li \c short, \c ushort, \c int, \c uint, \l qint64 \li Integer
|
||||
\row \li \l quint64 \li Integer, but they are cast to \c qint64 first so
|
||||
values higher than 2\sup{63}-1 (\c INT64_MAX) will
|
||||
be wrapped to negative
|
||||
\row \li \c float, \c double \li Double
|
||||
\row \li \l QByteArray \li ByteArray
|
||||
\row \li \l QDateTime \li DateTime
|
||||
\row \li \l QCborSimpleType \li Simple type
|
||||
\row \li \l QJsonArray \li Array, converted using QCborArray::formJsonArray()
|
||||
\row \li \l QJsonDocument \li Array or Map
|
||||
\row \li \l QJsonObject \li Map, converted using QCborMap::fromJsonObject()
|
||||
\row \li \l QJsonValue \li converted using fromJsonValue()
|
||||
\row \li \l QRegularExpression \li RegularExpression
|
||||
\row \li \l QString \li String
|
||||
\row \li \l QStringList \li Array
|
||||
\row \li \l QVariantHash \li Map
|
||||
\row \li \l QVariantList \li Array
|
||||
\row \li \l QVariantMap \li Map
|
||||
\row \li \l QUrl \li Url
|
||||
\row \li \l QUuid \li Uuid
|
||||
\endtable
|
||||
|
||||
For any other types, this function will return Null if the QVariant itself
|
||||
is null, and otherwise will try to convert to string using
|
||||
QVariant::toString(). If the conversion to string fails, this function
|
||||
returns Undefined.
|
||||
|
||||
Please note that the conversions via QVariant::toString() are subject to
|
||||
change at any time. QCborValue may be extended in the future to support
|
||||
more types, which will result in a change in how this function performs
|
||||
conversions.
|
||||
|
||||
\sa toVariant(), fromJsonValue(), QCborArray::toVariantList(), QCborMap::toVariantMap()
|
||||
*/
|
||||
QCborValue QCborValue::fromVariant(const QVariant &variant)
|
||||
{
|
||||
switch (variant.userType()) {
|
||||
case QVariant::Invalid:
|
||||
return {};
|
||||
case QMetaType::Nullptr:
|
||||
return nullptr;
|
||||
case QVariant::Bool:
|
||||
return variant.toBool();
|
||||
case QMetaType::Short:
|
||||
case QMetaType::UShort:
|
||||
case QVariant::Int:
|
||||
case QVariant::LongLong:
|
||||
case QVariant::ULongLong:
|
||||
case QVariant::UInt:
|
||||
return variant.toLongLong();
|
||||
case QMetaType::Float:
|
||||
case QVariant::Double:
|
||||
return variant.toDouble();
|
||||
case QVariant::String:
|
||||
return variant.toString();
|
||||
case QVariant::StringList:
|
||||
return QCborArray::fromStringList(variant.toStringList());
|
||||
case QVariant::ByteArray:
|
||||
return variant.toByteArray();
|
||||
case QVariant::DateTime:
|
||||
return QCborValue(variant.toDateTime());
|
||||
case QVariant::Url:
|
||||
return QCborValue(variant.toUrl());
|
||||
case QVariant::Uuid:
|
||||
return QCborValue(variant.toUuid());
|
||||
case QVariant::List:
|
||||
return QCborArray::fromVariantList(variant.toList());
|
||||
case QVariant::Map:
|
||||
return QCborMap::fromVariantMap(variant.toMap());
|
||||
case QVariant::Hash:
|
||||
return QCborMap::fromVariantHash(variant.toHash());
|
||||
#ifndef QT_BOOTSTRAPPED
|
||||
case QVariant::RegularExpression:
|
||||
return QCborValue(variant.toRegularExpression());
|
||||
case QMetaType::QJsonValue:
|
||||
return fromJsonValue(variant.toJsonValue());
|
||||
case QMetaType::QJsonObject:
|
||||
return QCborMap::fromJsonObject(variant.toJsonObject());
|
||||
case QMetaType::QJsonArray:
|
||||
return QCborArray::fromJsonArray(variant.toJsonArray());
|
||||
case QMetaType::QJsonDocument: {
|
||||
QJsonDocument doc = variant.toJsonDocument();
|
||||
if (doc.isArray())
|
||||
return QCborArray::fromJsonArray(doc.array());
|
||||
return QCborMap::fromJsonObject(doc.object());
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (variant.userType() == qMetaTypeId<QCborSimpleType>())
|
||||
return variant.value<QCborSimpleType>();
|
||||
|
||||
if (variant.isNull())
|
||||
return QCborValue(nullptr);
|
||||
|
||||
QString string = variant.toString();
|
||||
if (string.isNull())
|
||||
return QCborValue(); // undefined
|
||||
return string;
|
||||
}
|
||||
|
||||
/*!
|
||||
Recursively converts each \l QCborValue in this array using
|
||||
QCborValue::toVariant() and returns the QVariantList composed of the
|
||||
converted items.
|
||||
|
||||
Conversion to \l QVariant is not completely lossless. Please see the
|
||||
documentation in QCborValue::toVariant() for more information.
|
||||
|
||||
\sa fromVariantList(), fromStringList(), toJsonArray(),
|
||||
QCborValue::toVariant(), QCborMap::toVariantMap()
|
||||
*/
|
||||
QVariantList QCborArray::toVariantList() const
|
||||
{
|
||||
QVariantList retval;
|
||||
retval.reserve(size());
|
||||
for (qsizetype i = 0; i < size(); ++i)
|
||||
retval.append(d->valueAt(i).toVariant());
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a QCborArray containing all the strings found in the \a list list.
|
||||
|
||||
\sa fromVariantList(), fromJsonArray()
|
||||
*/
|
||||
QCborArray QCborArray::fromStringList(const QStringList &list)
|
||||
{
|
||||
QCborArray a;
|
||||
a.detach(list.size());
|
||||
for (const QString &s : list)
|
||||
a.d->append(s);
|
||||
return a;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts all the items in the \a list to CBOR using
|
||||
QCborValue::fromVariant() and returns the array composed of those elements.
|
||||
|
||||
Conversion from \l QVariant is not completely lossless. Please see the
|
||||
documentation in QCborValue::fromVariant() for more information.
|
||||
|
||||
\sa toVariantList(), fromStringList(), fromJsonArray(), QCborMap::fromVariantMap()
|
||||
*/
|
||||
QCborArray QCborArray::fromVariantList(const QVariantList &list)
|
||||
{
|
||||
QCborArray a;
|
||||
a.detach(list.size());
|
||||
for (const QVariant &v : list)
|
||||
appendVariant(a.d.data(), v);
|
||||
return a;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts all JSON items found in the \a array array to CBOR using
|
||||
QCborValue::fromJson(), and returns the CBOR array composed of those
|
||||
elements.
|
||||
|
||||
This conversion is lossless, as the CBOR type system is a superset of
|
||||
JSON's. Moreover, the array returned by this function can be converted back
|
||||
to the original \a array by using toJsonArray().
|
||||
|
||||
\sa toJsonArray(), toVariantList(), QCborValue::fromJsonValue(), QCborMap::fromJsonObject()
|
||||
*/
|
||||
QCborArray QCborArray::fromJsonArray(const QJsonArray &array)
|
||||
{
|
||||
QCborArray a;
|
||||
a.detach(array.size());
|
||||
for (const QJsonValue v : array) {
|
||||
if (v.isString())
|
||||
a.d->append(v.toString());
|
||||
else
|
||||
a.d->append(QCborValue::fromJsonValue(v));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts the CBOR values to QVariant using QCborValue::toVariant() and
|
||||
"stringifies" all the CBOR keys in this map, returning the QVariantMap that
|
||||
results from that association list.
|
||||
|
||||
QVariantMaps have string keys, unlike CBOR, so the conversion of a QCborMap
|
||||
to QVariantMap will imply a step of "stringification" of the key values.
|
||||
See QCborMap::toJsonObject() for details.
|
||||
|
||||
In addition, the conversion to \l QVariant is not completely lossless.
|
||||
Please see the documentation in QCborValue::toVariant() for more
|
||||
information.
|
||||
|
||||
\sa fromVariantMap(), toVariantHash(), toJsonObject(), QCborValue::toVariant(),
|
||||
QCborArray::toVariantList()
|
||||
*/
|
||||
QVariantMap QCborMap::toVariantMap() const
|
||||
{
|
||||
QVariantMap retval;
|
||||
for (qsizetype i = 0; i < 2 * size(); i += 2)
|
||||
retval.insert(makeString(d.data(), i), d->valueAt(i + 1).toVariant());
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts the CBOR values to QVariant using QCborValue::toVariant() and
|
||||
"stringifies" all the CBOR keys in this map, returning the QVariantHash that
|
||||
results from that association list.
|
||||
|
||||
QVariantMaps have string keys, unlike CBOR, so the conversion of a QCborMap
|
||||
to QVariantMap will imply a step of "stringification" of the key values.
|
||||
See QCborMap::toJsonObject() for details.
|
||||
|
||||
In addition, the conversion to \l QVariant is not completely lossless.
|
||||
Please see the documentation in QCborValue::toVariant() for more
|
||||
information.
|
||||
|
||||
\sa fromVariantHash(), toVariantMap(), toJsonObject(), QCborValue::toVariant(),
|
||||
QCborArray::toVariantList()
|
||||
*/
|
||||
QVariantHash QCborMap::toVariantHash() const
|
||||
{
|
||||
QVariantHash retval;
|
||||
for (qsizetype i = 0; i < 2 * size(); i += 2)
|
||||
retval.insert(makeString(d.data(), i), d->valueAt(i + 1).toVariant());
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts all the items in \a map to CBOR using QCborValue::fromVariant()
|
||||
and returns the map composed of those elements.
|
||||
|
||||
Conversion from \l QVariant is not completely lossless. Please see the
|
||||
documentation in QCborValue::fromVariant() for more information.
|
||||
|
||||
\sa toVariantMap(), fromVariantHash(), fromJsonObject(), QCborValue::fromVariant()
|
||||
*/
|
||||
QCborMap QCborMap::fromVariantMap(const QVariantMap &map)
|
||||
{
|
||||
QCborMap m;
|
||||
m.detach(map.size());
|
||||
QCborContainerPrivate *d = m.d.data();
|
||||
|
||||
auto it = map.begin();
|
||||
auto end = map.end();
|
||||
for ( ; it != end; ++it) {
|
||||
d->append(it.key());
|
||||
appendVariant(d, it.value());
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts all the items in \a hash to CBOR using QCborValue::fromVariant()
|
||||
and returns the map composed of those elements.
|
||||
|
||||
Conversion from \l QVariant is not completely lossless. Please see the
|
||||
documentation in QCborValue::fromVariant() for more information.
|
||||
|
||||
\sa toVariantHash(), fromVariantMap(), fromJsonObject(), QCborValue::fromVariant()
|
||||
*/
|
||||
QCborMap QCborMap::fromVariantHash(const QVariantHash &hash)
|
||||
{
|
||||
QCborMap m;
|
||||
m.detach(hash.size());
|
||||
QCborContainerPrivate *d = m.d.data();
|
||||
|
||||
auto it = hash.begin();
|
||||
auto end = hash.end();
|
||||
for ( ; it != end; ++it) {
|
||||
d->append(it.key());
|
||||
appendVariant(d, it.value());
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
/*!
|
||||
Converts all JSON items found in the \a obj object to CBOR using
|
||||
QCborValue::fromJson(), and returns the map composed of those elements.
|
||||
|
||||
This conversion is lossless, as the CBOR type system is a superset of
|
||||
JSON's. Moreover, the map returned by this function can be converted back
|
||||
to the original \a obj by using toJsonObject().
|
||||
|
||||
\sa toJsonObject(), toVariantMap(), QCborValue::fromJsonValue(), QCborArray::fromJsonArray()
|
||||
*/
|
||||
QCborMap QCborMap::fromJsonObject(const QJsonObject &obj)
|
||||
{
|
||||
QCborMap m;
|
||||
m.detach(obj.size());
|
||||
QCborContainerPrivate *d = m.d.data();
|
||||
|
||||
auto it = obj.begin();
|
||||
auto end = obj.end();
|
||||
for ( ; it != end; ++it) {
|
||||
d->append(it.key());
|
||||
if (it.value().isString())
|
||||
d->append(it.value().toString());
|
||||
else
|
||||
d->append(QCborValue::fromJsonValue(it.value()));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
@ -150,6 +150,7 @@ private:
|
|||
friend class QJsonPrivate::Value;
|
||||
friend class QJsonArray;
|
||||
friend class QJsonObject;
|
||||
friend class QCborValue;
|
||||
friend Q_CORE_EXPORT QDebug operator<<(QDebug, const QJsonValue &);
|
||||
|
||||
QJsonValue(QJsonPrivate::Data *d, QJsonPrivate::Base *b, const QJsonPrivate::Value& v);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ SOURCES += \
|
|||
serialization/qcborvalue.cpp \
|
||||
serialization/qdatastream.cpp \
|
||||
serialization/qjson.cpp \
|
||||
serialization/qjsoncbor.cpp \
|
||||
serialization/qjsondocument.cpp \
|
||||
serialization/qjsonobject.cpp \
|
||||
serialization/qjsonarray.cpp \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
QT = core testlib
|
||||
TARGET = tst_qcborvalue_json
|
||||
CONFIG += testcase
|
||||
SOURCES += \
|
||||
tst_qcborvalue_json.cpp
|
||||
|
||||
DEFINES += SRCDIR=\\\"$$PWD/\\\"
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2018 Intel Corporation.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 3 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 3 requirements
|
||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 2.0 or (at your option) the GNU General
|
||||
** Public license version 3 or any later version approved by the KDE Free
|
||||
** Qt Foundation. The licenses are as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||
** included in the packaging of this file. Please review the following
|
||||
** information to ensure the GNU General Public License requirements will
|
||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include <QtCore/qcborvalue.h>
|
||||
#include <QtTest>
|
||||
|
||||
Q_DECLARE_METATYPE(QCborValue)
|
||||
|
||||
class tst_QCborValue_Json : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void toVariant_data();
|
||||
void toVariant();
|
||||
void toJson_data() { toVariant_data(); }
|
||||
void toJson();
|
||||
void taggedByteArrayToJson_data();
|
||||
void taggedByteArrayToJson();
|
||||
|
||||
void fromVariant_data() { toVariant_data(); }
|
||||
void fromVariant();
|
||||
void fromJson_data();
|
||||
void fromJson();
|
||||
|
||||
void nonStringKeysInMaps_data();
|
||||
void nonStringKeysInMaps();
|
||||
};
|
||||
|
||||
void tst_QCborValue_Json::toVariant_data()
|
||||
{
|
||||
QTest::addColumn<QCborValue>("v");
|
||||
QTest::addColumn<QVariant>("variant");
|
||||
QTest::addColumn<QJsonValue>("json");
|
||||
QDateTime dt = QDateTime::currentDateTimeUtc();
|
||||
QUuid uuid = QUuid::createUuid();
|
||||
|
||||
QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>();
|
||||
auto add = [me](const QCborValue &v, const QVariant &exp, const QJsonValue &json) {
|
||||
auto addRow = [=]() -> QTestData & {
|
||||
const char *typeString = me.valueToKey(v.type());
|
||||
if (v.type() == QCborValue::Integer)
|
||||
return QTest::addRow("Integer:%lld", exp.toLongLong());
|
||||
if (v.type() == QCborValue::Double)
|
||||
return QTest::addRow("Double:%g", exp.toDouble());
|
||||
if (v.type() == QCborValue::ByteArray || v.type() == QCborValue::String)
|
||||
return QTest::addRow("%s:%d", typeString, exp.toString().size());
|
||||
if (v.type() >= 0x10000)
|
||||
return QTest::newRow(exp.typeName());
|
||||
return QTest::newRow(typeString);
|
||||
};
|
||||
addRow() << v << exp << json;
|
||||
};
|
||||
|
||||
// good JSON matching:
|
||||
add(QCborValue(), QVariant(), QJsonValue::Undefined);
|
||||
add(nullptr, QVariant::fromValue(nullptr), QJsonValue::Null);
|
||||
add(false, false, false);
|
||||
add(true, true, true);
|
||||
add(0, 0, 0);
|
||||
add(1, 1, 1);
|
||||
add(-1, -1, -1);
|
||||
add(0., 0., 0.);
|
||||
add(1.25, 1.25, 1.25);
|
||||
add(-1.25, -1.25, -1.25);
|
||||
add("Hello", "Hello", "Hello");
|
||||
|
||||
// converts to string in JSON:
|
||||
add(QByteArray("Hello"), QByteArray("Hello"), "SGVsbG8");
|
||||
add(QCborValue(dt), dt, dt.toString(Qt::ISODateWithMs));
|
||||
add(QCborValue(QUrl("http://example.com/{q}")), QUrl("http://example.com/{q}"),
|
||||
"http://example.com/%7Bq%7D"); // note the encoded form in JSON
|
||||
add(QCborValue(QRegularExpression(".")), QRegularExpression("."), ".");
|
||||
add(QCborValue(uuid), uuid, uuid.toString(QUuid::WithoutBraces));
|
||||
|
||||
// not valid in JSON
|
||||
QTest::newRow("simpletype") << QCborValue(QCborSimpleType(255))
|
||||
<< QVariant::fromValue(QCborSimpleType(255))
|
||||
<< QJsonValue("simple(255)");
|
||||
QTest::newRow("Double:inf") << QCborValue(qInf())
|
||||
<< QVariant(qInf())
|
||||
<< QJsonValue();
|
||||
QTest::newRow("Double:-inf") << QCborValue(-qInf())
|
||||
<< QVariant(-qInf())
|
||||
<< QJsonValue();
|
||||
QTest::newRow("Double:nan") << QCborValue(qQNaN())
|
||||
<< QVariant(qQNaN())
|
||||
<< QJsonValue();
|
||||
|
||||
// large integral values lose precision in JSON
|
||||
QTest::newRow("Integer:max") << QCborValue(std::numeric_limits<qint64>::max())
|
||||
<< QVariant(std::numeric_limits<qint64>::max())
|
||||
<< QJsonValue(std::numeric_limits<qint64>::max());
|
||||
QTest::newRow("Integer:min") << QCborValue(std::numeric_limits<qint64>::min())
|
||||
<< QVariant(std::numeric_limits<qint64>::min())
|
||||
<< QJsonValue(std::numeric_limits<qint64>::min());
|
||||
|
||||
// empty arrays and maps
|
||||
add(QCborArray(), QVariantList(), QJsonArray());
|
||||
add(QCborMap(), QVariantMap(), QJsonObject());
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::toVariant()
|
||||
{
|
||||
QFETCH(QCborValue, v);
|
||||
QFETCH(QVariant, variant);
|
||||
|
||||
if (qIsNaN(variant.toDouble())) {
|
||||
// because NaN != NaN, QVariant(NaN) != QVariant(NaN), so we
|
||||
// only need to compare the classification
|
||||
QVERIFY(qIsNaN(v.toVariant().toDouble()));
|
||||
|
||||
// the rest of this function depends on the variant comparison
|
||||
return;
|
||||
}
|
||||
|
||||
QCOMPARE(v.toVariant(), variant);
|
||||
|
||||
// tags get ignored:
|
||||
QCOMPARE(QCborValue(QCborKnownTags::Signature, v).toVariant(), variant);
|
||||
|
||||
// make arrays with this item
|
||||
QCOMPARE(QCborArray({v}).toVariantList(), QVariantList({variant}));
|
||||
QCOMPARE(QCborArray({v, v}).toVariantList(), QVariantList({variant, variant}));
|
||||
|
||||
// and maps
|
||||
QCOMPARE(QCborMap({{"foo", v}}).toVariantMap(), QVariantMap({{"foo", variant}}));
|
||||
QCOMPARE(QCborMap({{"foo", v}}).toVariantHash(), QVariantHash({{"foo", variant}}));
|
||||
|
||||
// finally, mixed
|
||||
QCOMPARE(QCborArray{QCborMap({{"foo", v}})}.toVariantList(),
|
||||
QVariantList{QVariantMap({{"foo", variant}})});
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::toJson()
|
||||
{
|
||||
QFETCH(QCborValue, v);
|
||||
QFETCH(QJsonValue, json);
|
||||
|
||||
QCOMPARE(v.toJsonValue(), json);
|
||||
|
||||
// most tags get ignored:
|
||||
QCOMPARE(QCborValue(QCborKnownTags::Signature, v).toJsonValue(), json);
|
||||
|
||||
// make arrays with this item
|
||||
QCOMPARE(QCborArray({v}).toJsonArray(), QJsonArray({json}));
|
||||
QCOMPARE(QCborArray({v, v}).toJsonArray(), QJsonArray({json, json}));
|
||||
|
||||
// and maps
|
||||
QCOMPARE(QCborMap({{"foo", v}}).toJsonObject(), QJsonObject({{"foo", json}}));
|
||||
|
||||
// finally, mixed
|
||||
QCOMPARE(QCborArray{QCborMap({{"foo", v}})}.toJsonArray(),
|
||||
QJsonArray{QJsonObject({{"foo", json}})});
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::taggedByteArrayToJson_data()
|
||||
{
|
||||
QTest::addColumn<QCborValue>("v");
|
||||
QTest::addColumn<QJsonValue>("json");
|
||||
|
||||
QByteArray data("\xff\x01");
|
||||
QTest::newRow("base64url") << QCborValue(QCborKnownTags::ExpectedBase64url, data) << QJsonValue("_wE");
|
||||
QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << QJsonValue("/wE=");
|
||||
QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << QJsonValue("ff01");
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::taggedByteArrayToJson()
|
||||
{
|
||||
QFETCH(QCborValue, v);
|
||||
QFETCH(QJsonValue, json);
|
||||
|
||||
QCOMPARE(v.toJsonValue(), json);
|
||||
QCOMPARE(QCborArray({v}).toJsonArray(), QJsonArray({json}));
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::fromVariant()
|
||||
{
|
||||
QFETCH(QCborValue, v);
|
||||
QFETCH(QVariant, variant);
|
||||
|
||||
QCOMPARE(QCborValue::fromVariant(variant), v);
|
||||
|
||||
// try arrays
|
||||
QCOMPARE(QCborArray::fromVariantList({variant}), QCborArray{v});
|
||||
QCOMPARE(QCborArray::fromVariantList({variant, variant}), QCborArray({v, v}));
|
||||
|
||||
if (variant.type() == QVariant::String) {
|
||||
QString s = variant.toString();
|
||||
QCOMPARE(QCborArray::fromStringList({s}), QCborArray{v});
|
||||
QCOMPARE(QCborArray::fromStringList({s, s}), QCborArray({v, v}));
|
||||
}
|
||||
|
||||
// maps...
|
||||
QVariantMap map{{"foo", variant}};
|
||||
QCOMPARE(QCborMap::fromVariantMap(map), QCborMap({{"foo", v}}));
|
||||
QCOMPARE(QCborMap::fromVariantHash({{"foo", variant}}), QCborMap({{"foo", v}}));
|
||||
|
||||
// nested
|
||||
QVariantMap outer{{"bar", QVariantList{0, map, true}}};
|
||||
QCOMPARE(QCborMap::fromVariantMap(outer),
|
||||
QCborMap({{"bar", QCborArray{0, QCborMap{{"foo", v}}, true}}}));
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::fromJson_data()
|
||||
{
|
||||
QTest::addColumn<QCborValue>("v");
|
||||
QTest::addColumn<QJsonValue>("json");
|
||||
|
||||
QTest::newRow("null") << QCborValue(QCborValue::Null) << QJsonValue(QJsonValue::Null);
|
||||
QTest::newRow("false") << QCborValue(false) << QJsonValue(false);
|
||||
QTest::newRow("true") << QCborValue(true) << QJsonValue(true);
|
||||
QTest::newRow("0") << QCborValue(0) << QJsonValue(0.);
|
||||
QTest::newRow("1") << QCborValue(1) << QJsonValue(1);
|
||||
QTest::newRow("1.5") << QCborValue(1.5) << QJsonValue(1.5);
|
||||
QTest::newRow("string") << QCborValue("Hello") << QJsonValue("Hello");
|
||||
QTest::newRow("array") << QCborValue(QCborValue::Array) << QJsonValue(QJsonValue::Array);
|
||||
QTest::newRow("map") << QCborValue(QCborValue::Map) << QJsonValue(QJsonValue::Object);
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::fromJson()
|
||||
{
|
||||
QFETCH(QCborValue, v);
|
||||
QFETCH(QJsonValue, json);
|
||||
|
||||
QCOMPARE(QCborValue::fromJsonValue(json), v);
|
||||
QCOMPARE(QCborArray::fromJsonArray({json}), QCborArray({v}));
|
||||
QCOMPARE(QCborArray::fromJsonArray({json, json}), QCborArray({v, v}));
|
||||
QCOMPARE(QCborMap::fromJsonObject({{"foo", json}}), QCborMap({{"foo", v}}));
|
||||
|
||||
// confirm we can roundtrip back to JSON
|
||||
QCOMPARE(QCborValue::fromJsonValue(json).toJsonValue(), json);
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::nonStringKeysInMaps_data()
|
||||
{
|
||||
QTest::addColumn<QCborValue>("key");
|
||||
QTest::addColumn<QString>("converted");
|
||||
|
||||
auto add = [](const char *str, const QCborValue &v) {
|
||||
QTest::newRow(str) << v << str;
|
||||
};
|
||||
add("0", 0);
|
||||
add("-1", -1);
|
||||
add("false", false);
|
||||
add("true", true);
|
||||
add("null", nullptr);
|
||||
add("undefined", {}); // should this be ""?
|
||||
add("simple(255)", QCborSimpleType(255));
|
||||
add("2.5", 2.5);
|
||||
|
||||
QByteArray data("\xff\x01");
|
||||
QTest::newRow("bytearray") << QCborValue(data) << "_wE";
|
||||
QTest::newRow("base64url") << QCborValue(QCborKnownTags::ExpectedBase64url, data) << "_wE";
|
||||
QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE=";
|
||||
QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01";
|
||||
|
||||
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[ ]";
|
||||
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{ }";
|
||||
QTest::newRow("array") << QCborValue(QCborArray{1, true, 2.5, "Hello"})
|
||||
<< "[ 1, true, 2.5, \"Hello\" ]";
|
||||
QTest::newRow("map") << QCborValue(QCborMap{{"Hello", 0}, {0, "Hello"}})
|
||||
<< "{ \"Hello\": 0, 0: \"Hello\" }";
|
||||
|
||||
QDateTime dt = QDateTime::currentDateTimeUtc();
|
||||
QUrl url("https://example.com");
|
||||
QUuid uuid = QUuid::createUuid();
|
||||
QTest::newRow("QDateTime") << QCborValue(dt) << dt.toString(Qt::ISODateWithMs);
|
||||
QTest::newRow("QUrl") << QCborValue(url) << url.toString(QUrl::FullyEncoded);
|
||||
QTest::newRow("QRegularExpression") << QCborValue(QRegularExpression(".*")) << ".*";
|
||||
QTest::newRow("QUuid") << QCborValue(uuid) << uuid.toString(QUuid::WithoutBraces);
|
||||
}
|
||||
|
||||
void tst_QCborValue_Json::nonStringKeysInMaps()
|
||||
{
|
||||
QFETCH(QCborValue, key);
|
||||
QFETCH(QString, converted);
|
||||
|
||||
QCborMap m;
|
||||
m.insert(key, 0);
|
||||
|
||||
{
|
||||
QVariantMap vm = m.toVariantMap();
|
||||
auto it = vm.begin();
|
||||
QVERIFY(it != vm.end());
|
||||
QCOMPARE(it.key(), converted);
|
||||
QCOMPARE(it.value(), 0);
|
||||
QCOMPARE(++it, vm.end());
|
||||
}
|
||||
|
||||
{
|
||||
QJsonObject o = m.toJsonObject();
|
||||
auto it = o.begin();
|
||||
QVERIFY(it != o.end());
|
||||
QCOMPARE(it.key(), converted);
|
||||
QCOMPARE(it.value(), 0);
|
||||
QCOMPARE(++it, o.end());
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QCborValue_Json)
|
||||
|
||||
#include "tst_qcborvalue_json.moc"
|
||||
|
|
@ -4,6 +4,7 @@ SUBDIRS = \
|
|||
qcborstreamreader \
|
||||
qcborstreamwriter \
|
||||
qcborvalue \
|
||||
qcborvalue_json \
|
||||
qdatastream \
|
||||
qtextstream \
|
||||
qxmlstream
|
||||
|
|
|
|||
Loading…
Reference in New Issue