diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-10-21 10:26:45 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-11-05 10:12:04 +0100 |
commit | edec095cf80b62057116ce75c581b5ca5866bdcc (patch) | |
tree | a849983cff625c80f9386865988f32ad552837cd /src/corelib/serialization | |
parent | e99b0016e8b639e40d5330e0c3eaa199b48e34f4 (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.cpp | 22 | ||||
-rw-r--r-- | src/corelib/serialization/qjsonparser.cpp | 5 | ||||
-rw-r--r-- | src/corelib/serialization/qjsonvalue.cpp | 92 | ||||
-rw-r--r-- | src/corelib/serialization/qjsonvalue.h | 1 | ||||
-rw-r--r-- | src/corelib/serialization/qjsonwriter.cpp | 11 |
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: |