summaryrefslogtreecommitdiffstats
path: root/src/corelib/serialization
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2019-10-21 10:26:45 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-11-05 10:12:04 +0100
commitedec095cf80b62057116ce75c581b5ca5866bdcc (patch)
treea849983cff625c80f9386865988f32ad552837cd /src/corelib/serialization
parente99b0016e8b639e40d5330e0c3eaa199b48e34f4 (diff)
Fix 64-bit integer support in QtJSON
Fixes parsing writing and pass-through of integers with higher precision than double can handle. Note this adds extra precision compared to JavaScript, but the JSON files read and written this way are still valid, and the extra precision in reading and writing this way is used by many JSON libraries. Fixes: QTBUG-28560 Change-Id: I30b2415c928d1c34c8cb4e4c6218602095e7e8aa Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src/corelib/serialization')
-rw-r--r--src/corelib/serialization/qjsoncbor.cpp22
-rw-r--r--src/corelib/serialization/qjsonparser.cpp5
-rw-r--r--src/corelib/serialization/qjsonvalue.cpp92
-rw-r--r--src/corelib/serialization/qjsonvalue.h1
-rw-r--r--src/corelib/serialization/qjsonwriter.cpp11
5 files changed, 88 insertions, 43 deletions
diff --git a/src/corelib/serialization/qjsoncbor.cpp b/src/corelib/serialization/qjsoncbor.cpp
index 7136a163ee..997cef0106 100644
--- a/src/corelib/serialization/qjsoncbor.cpp
+++ b/src/corelib/serialization/qjsoncbor.cpp
@@ -262,8 +262,7 @@ QJsonValue qt_convertToJson(QCborContainerPrivate *d, qsizetype idx)
const auto &e = d->elements.at(idx);
switch (e.type) {
case QCborValue::Integer:
- return QJsonPrivate::Value::fromTrustedCbor(e.value);
-
+ return QJsonValue(e.value);
case QCborValue::ByteArray:
case QCborValue::String:
case QCborValue::SimpleType:
@@ -370,7 +369,7 @@ QJsonValue QCborValue::toJsonValue() const
return false;
case Integer:
- return QJsonPrivate::Value::fromTrustedCbor(n);
+ return QJsonPrivate::Value::fromTrustedCbor(*this);
case True:
return true;
@@ -615,11 +614,9 @@ QCborValue QCborValue::fromJsonValue(const QJsonValue &v)
case QJsonValue::Bool:
return v.toBool();
case QJsonValue::Double: {
- qint64 i;
- const double dbl = v.toDouble();
- if (convertDoubleTo(dbl, &i))
- return i;
- return dbl;
+ if (v.t == Integer)
+ return v.toInteger();
+ return v.toDouble();
}
case QJsonValue::String:
return v.toString();
@@ -667,9 +664,7 @@ static void appendVariant(QCborContainerPrivate *d, const QVariant &variant)
\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 \l quint64 \li Integer, or Double if outside the range of qint64
\row \li \c float, \c double \li Double
\row \li \l QByteArray \li ByteArray
\row \li \l QDateTime \li DateTime
@@ -713,9 +708,12 @@ QCborValue QCborValue::fromVariant(const QVariant &variant)
case QMetaType::UShort:
case QVariant::Int:
case QVariant::LongLong:
- case QVariant::ULongLong:
case QVariant::UInt:
return variant.toLongLong();
+ case QVariant::ULongLong:
+ if (variant.toULongLong() <= static_cast<uint64_t>(std::numeric_limits<qint64>::max()))
+ return variant.toLongLong();
+ Q_FALLTHROUGH();
case QMetaType::Float:
case QVariant::Double:
return variant.toDouble();
diff --git a/src/corelib/serialization/qjsonparser.cpp b/src/corelib/serialization/qjsonparser.cpp
index 6d0a92e094..aab8112d7f 100644
--- a/src/corelib/serialization/qjsonparser.cpp
+++ b/src/corelib/serialization/qjsonparser.cpp
@@ -709,10 +709,11 @@ bool Parser::parseNumber()
// frac = decimal-point 1*DIGIT
if (json < end && *json == '.') {
- isInt = false;
++json;
- while (json < end && *json >= '0' && *json <= '9')
+ while (json < end && *json >= '0' && *json <= '9') {
+ isInt = isInt && *json == '0';
++json;
+ }
}
// exp = e [ minus / plus ] 1*DIGIT
diff --git a/src/corelib/serialization/qjsonvalue.cpp b/src/corelib/serialization/qjsonvalue.cpp
index 033e438580..db06a33a9f 100644
--- a/src/corelib/serialization/qjsonvalue.cpp
+++ b/src/corelib/serialization/qjsonvalue.cpp
@@ -154,7 +154,9 @@ QJsonValue::QJsonValue(bool b)
QJsonValue::QJsonValue(double v)
: d(nullptr)
{
- if (convertDoubleTo(v, &n)) {
+ // Convert to integer if the number is an integer and changing wouldn't
+ // introduce additional digit precision not present in the double.
+ if (convertDoubleTo<qint64>(v, &n, false /* allow_precision_upgrade */)) {
t = QCborValue::Integer;
} else {
memcpy(&n, &v, sizeof(n));
@@ -449,12 +451,18 @@ QJsonValue QJsonValue::fromVariant(const QVariant &variant)
return QJsonValue(Null);
case QVariant::Bool:
return QJsonValue(variant.toBool());
+ case QMetaType::Short:
+ case QMetaType::UShort:
case QVariant::Int:
- case QMetaType::Float:
- case QVariant::Double:
+ case QVariant::UInt:
case QVariant::LongLong:
+ return QJsonValue(variant.toLongLong());
case QVariant::ULongLong:
- case QVariant::UInt:
+ if (variant.toULongLong() <= static_cast<uint64_t>(std::numeric_limits<qint64>::max()))
+ return QJsonValue(variant.toLongLong());
+ Q_FALLTHROUGH();
+ case QMetaType::Float:
+ case QVariant::Double:
return QJsonValue(variant.toDouble());
case QVariant::String:
return QJsonValue(variant.toString());
@@ -504,7 +512,7 @@ QJsonValue QJsonValue::fromVariant(const QVariant &variant)
\value Null QMetaType::Nullptr
\value Bool QMetaType::Bool
- \value Double QMetaType::Double
+ \value Double QMetaType::Double or QMetaType::LongLong
\value String QString
\value Array QVariantList
\value Object QVariantMap
@@ -520,6 +528,7 @@ QVariant QJsonValue::toVariant() const
case QCborValue::False:
return false;
case QCborValue::Integer:
+ return toInteger();
case QCborValue::Double:
return toDouble();
case QCborValue::String:
@@ -548,7 +557,8 @@ QVariant QJsonValue::toVariant() const
\value Null A Null value
\value Bool A boolean value. Use toBool() to convert to a bool.
- \value Double A double. Use toDouble() to convert to a double.
+ \value Double A number value. Use toDouble() to convert to a double,
+ or toInteger() to convert to a qint64.
\value String A string. Use toString() to convert to a QString.
\value Array An array. Use toArray() to convert to a QJsonArray.
\value Object An object. Use toObject() to convert to a QJsonObject.
@@ -613,18 +623,43 @@ int QJsonValue::toInt(int defaultValue) const
{
switch (t) {
case QCborValue::Double: {
- const double dbl = toDouble();
int dblInt;
- convertDoubleTo<int>(dbl, &dblInt);
- return dbl == dblInt ? dblInt : defaultValue;
+ if (convertDoubleTo<int>(toDouble(), &dblInt))
+ return dblInt;
+ break;
}
case QCborValue::Integer:
- return (n <= qint64(std::numeric_limits<int>::max())
- && n >= qint64(std::numeric_limits<int>::min()))
- ? n : defaultValue;
+ if (qint64(int(n)) == n)
+ return int(n);
+ break;
default:
- return defaultValue;
+ break;
}
+ return defaultValue;
+}
+
+/*!
+ \since 6.0
+ Converts the value to an integer and returns it.
+
+ If type() is not Double or the value is not a whole number
+ representable as qint64, the \a defaultValue will be returned.
+ */
+qint64 QJsonValue::toInteger(qint64 defaultValue) const
+{
+ switch (t) {
+ case QCborValue::Integer:
+ return n;
+ case QCborValue::Double: {
+ qint64 dblInt;
+ if (convertDoubleTo<qint64>(toDouble(), &dblInt))
+ return dblInt;
+ break;
+ }
+ default:
+ break;
+ }
+ return defaultValue;
}
/*!
@@ -641,7 +676,7 @@ double QJsonValue::toDouble(double defaultValue) const
return d;
}
case QCborValue::Integer:
- return n;
+ return double(n);
default:
return defaultValue;
}
@@ -787,8 +822,13 @@ const QJsonValue QJsonValue::operator[](int i) const
*/
bool QJsonValue::operator==(const QJsonValue &other) const
{
- if (t != other.t)
+ if (t != other.t) {
+ if (isDouble() && other.isDouble()) {
+ // One value Cbor integer, one Cbor double, should interact as doubles.
+ return toDouble() == other.toDouble();
+ }
return false;
+ }
switch (t) {
case QCborValue::Undefined:
@@ -929,32 +969,38 @@ uint qHash(const QJsonValue &value, uint seed)
QDebug operator<<(QDebug dbg, const QJsonValue &o)
{
QDebugStateSaver saver(dbg);
- switch (o.type()) {
- case QJsonValue::Undefined:
+ switch (o.t) {
+ case QCborValue::Undefined:
dbg << "QJsonValue(undefined)";
break;
- case QJsonValue::Null:
+ case QCborValue::Null:
dbg << "QJsonValue(null)";
break;
- case QJsonValue::Bool:
+ case QCborValue::True:
+ case QCborValue::False:
dbg.nospace() << "QJsonValue(bool, " << o.toBool() << ')';
break;
- case QJsonValue::Double:
+ case QCborValue::Integer:
+ dbg.nospace() << "QJsonValue(double, " << o.toInteger() << ')';
+ break;
+ case QCborValue::Double:
dbg.nospace() << "QJsonValue(double, " << o.toDouble() << ')';
break;
- case QJsonValue::String:
+ case QCborValue::String:
dbg.nospace() << "QJsonValue(string, " << o.toString() << ')';
break;
- case QJsonValue::Array:
+ case QCborValue::Array:
dbg.nospace() << "QJsonValue(array, ";
dbg << o.toArray();
dbg << ')';
break;
- case QJsonValue::Object:
+ case QCborValue::Map:
dbg.nospace() << "QJsonValue(object, ";
dbg << o.toObject();
dbg << ')';
break;
+ default:
+ Q_UNREACHABLE();
}
return dbg;
}
diff --git a/src/corelib/serialization/qjsonvalue.h b/src/corelib/serialization/qjsonvalue.h
index 5adcd64176..bd8bf14baf 100644
--- a/src/corelib/serialization/qjsonvalue.h
+++ b/src/corelib/serialization/qjsonvalue.h
@@ -112,6 +112,7 @@ public:
bool toBool(bool defaultValue = false) const;
int toInt(int defaultValue = 0) const;
+ qint64 toInteger(qint64 defaultValue = 0) const;
double toDouble(double defaultValue = 0) const;
QString toString() const;
QString toString(const QString &defaultValue) const;
diff --git a/src/corelib/serialization/qjsonwriter.cpp b/src/corelib/serialization/qjsonwriter.cpp
index 627d1bbd62..31fb16c112 100644
--- a/src/corelib/serialization/qjsonwriter.cpp
+++ b/src/corelib/serialization/qjsonwriter.cpp
@@ -138,15 +138,14 @@ static void valueToJson(const QCborValue &v, QByteArray &json, int indent, bool
json += "false";
break;
case QCborValue::Integer:
+ json += QByteArray::number(v.toInteger());
+ break;
case QCborValue::Double: {
const double d = v.toDouble();
- if (qIsFinite(d)) {
- quint64 absInt;
- json += QByteArray::number(d, convertDoubleTo(std::abs(d), &absInt) ? 'f' : 'g',
- QLocale::FloatingPointShortest);
- } else {
+ if (qIsFinite(d))
+ json += QByteArray::number(d, 'g', QLocale::FloatingPointShortest);
+ else
json += "null"; // +INF || -INF || NaN (see RFC4627#section2.4)
- }
break;
}
case QCborValue::String: