QJsonDocument与 IEEE 754双精度浮点数

后端开发 2026-07-10

其实我在用Qt 5,使用QJsonDocument。我需要把格式正确的小数保存到我的JSON文档中。我尝试了几种正确的格式化方法,这就是我最终使用的办法:

    static double format(double value) {
        double rounded = std::round(value * 100.0) / 100.0;
        double r = QString::number(rounded, 'f', 2).toDouble();
        return r;
    }

确实可以正确工作,但当我设置数值、创建文档并记录它时,所有小数位的值都改变了。

例如:

jsonObject["amount"] = format(px); /* px = 1.90 */
QJsonDocument jsonDocument(jsonObject);

qWarning() << "Built document: " << jsonDocument.toJson(QJsonDocument::Indented);
/* In the log, amount is = 1.8999999999999999 */

我理解为什么会这样,“1.90不能在内存中准确存储”。

但我想不出解决办法。我不能把数值按分存储(我必须使用double)。

我也不能把字符串放进json,所用的文档/API非常严格。

我在想完全不使用QJsonObject/Document/Array,而改用极简的json解析器,这样我就只保存和操作字符串。

任何帮助将不胜感激,谢谢。

解决方案

我按照问题中所说的方法解决了我的问题:

我在想完全不使用QJsonObject/Document/Array,而使用极简的JSON解析器,这样我就只保存和操作字符串。

我创建了用于解析/构建json的类,在整个过程中不进行任何转换,纯字符串操作。效果如同魔法般好,不需要对任何东西进行正则替换,或是欺骗什么。简直就是完美的解决方案!我把代码放在这里,供有同样问题的人使用!

#ifndef RAWJSON_H
#define RAWJSON_H

#include <QString>
#include <QMap>
#include <QVector>
#include <QStringList>

class RawJson
{
public:
    enum Type {
        Null,
        Bool,
        Number,
        String,
        Array,
        Object
    };

    RawJson();
    RawJson(const RawJson &other);
    RawJson(RawJson &&other) noexcept;
    RawJson &operator=(const RawJson &other);
    RawJson &operator=(RawJson &&other) noexcept;
    ~RawJson();

    static RawJson null();
    static RawJson fromBool(bool v);
    static RawJson fromNumber(const QString &raw);
    static RawJson fromString(const QString &v);
    static RawJson fromArray(const QVector<RawJson> &items = {});
    static RawJson fromObject(const QMap<QString, RawJson> &members = {});

    static RawJson parse(const QString &json);

    QString serialize(int indent = -1) const;

    Type type() const;
    bool isNull()   const;
    bool isBool()   const;
    bool isNumber()  const;
    bool isString()  const;
    bool isArray()   const;
    bool isObject()  const;

    bool        toBool(bool defaultVal = false) const;
    QString     toRawNumber(const QString &defaultVal = QString()) const;
    QString     toString(const QString &defaultVal = QString()) const;

    int         toInt(int defaultVal = 0) const;
    double      toDouble(double defaultVal = 0.0) const;

    RawJson       &operator[](const QString &key);
    const RawJson  operator[](const QString &key) const;
    bool           contains(const QString &key) const;
    QStringList    keys() const;
    int            memberCount() const;
    void           remove(const QString &key);

    RawJson       &operator[](int index);
    const RawJson  operator[](int index) const;
    void           append(const RawJson &value);
    int            count() const;
    void           removeAt(int index);

private:
    Type m_type;
    QString m_value;
    QVector<RawJson> m_array;
    QMap<QString, RawJson> m_object;

    struct Parser {
        const QString &src;
        int pos;

        Parser(const QString &s) : src(s), pos(0) {}

        RawJson      parseValue();
        RawJson      parseObject();
        RawJson      parseArray();
        QString      parseString();
        RawJson      parseNumber();
        RawJson      parseLiteral();

        void         skipWhitespace();
        QChar        peek() const;
        QChar        advance();
        void         expect(QChar c);
        [[noreturn]] void error(const QString &msg) const;
    };

    void serializeImpl(QString &out, int indent, int depth) const;
    static QString escapeString(const QString &s);
};

#endif // RAWJSON_H
#include "RawJson.h"
#include <stdexcept>

RawJson::RawJson() : m_type(Null) {}

RawJson::RawJson(const RawJson &other)
    : m_type(other.m_type), m_value(other.m_value),
      m_array(other.m_array), m_object(other.m_object) {}

RawJson::RawJson(RawJson &&other) noexcept
    : m_type(other.m_type), m_value(std::move(other.m_value)),
      m_array(std::move(other.m_array)), m_object(std::move(other.m_object))
{
    other.m_type = Null;
}

RawJson &RawJson::operator=(const RawJson &other)
{
    if (this != &other) {
        m_type   = other.m_type;
        m_value  = other.m_value;
        m_array  = other.m_array;
        m_object = other.m_object;
    }
    return *this;
}

RawJson &RawJson::operator=(RawJson &&other) noexcept
{
    if (this != &other) {
        m_type   = other.m_type;
        m_value  = std::move(other.m_value);
        m_array  = std::move(other.m_array);
        m_object = std::move(other.m_object);
        other.m_type = Null;
    }
    return *this;
}

RawJson::~RawJson() = default;

RawJson RawJson::null()
{
    return RawJson();
}

RawJson RawJson::fromBool(bool v)
{
    RawJson j;
    j.m_type  = Bool;
    j.m_value = v ? QStringLiteral("true") : QStringLiteral("false");
    return j;
}

RawJson RawJson::fromNumber(const QString &raw)
{
    RawJson j;
    j.m_type  = Number;
    j.m_value = raw;
    return j;
}

RawJson RawJson::fromString(const QString &v)
{
    RawJson j;
    j.m_type  = String;
    j.m_value = v;
    return j;
}

RawJson RawJson::fromArray(const QVector<RawJson> &items)
{
    RawJson j;
    j.m_type  = Array;
    j.m_array = items;
    return j;
}

RawJson RawJson::fromObject(const QMap<QString, RawJson> &members)
{
    RawJson j;
    j.m_type   = Object;
    j.m_object = members;
    return j;
}


RawJson::Type RawJson::type()     const { return m_type; }
bool RawJson::isNull()   const { return m_type == Null; }
bool RawJson::isBool()   const { return m_type == Bool; }
bool RawJson::isNumber() const { return m_type == Number; }
bool RawJson::isString() const { return m_type == String; }
bool RawJson::isArray()  const { return m_type == Array; }
bool RawJson::isObject() const { return m_type == Object; }

bool RawJson::toBool(bool defaultVal) const
{
    if (m_type != Bool) return defaultVal;
    return m_value == QStringLiteral("true");
}

QString RawJson::toRawNumber(const QString &defaultVal) const
{
    if (m_type != Number) return defaultVal;
    return m_value;
}

QString RawJson::toString(const QString &defaultVal) const
{
    if (m_type != String) return defaultVal;
    return m_value;
}

int RawJson::toInt(int defaultVal) const
{
    if (m_type != Number) return defaultVal;
    bool ok = false;
    int v = m_value.toInt(&ok);
    return ok ? v : defaultVal;
}

double RawJson::toDouble(double defaultVal) const
{
    if (m_type != Number) return defaultVal;
    bool ok = false;
    double v = m_value.toDouble(&ok);
    return ok ? v : defaultVal;
}

RawJson &RawJson::operator[](const QString &key)
{
    if (m_type != Object) {
        m_type = Object;
        m_value.clear();
        m_array.clear();
    }
    return m_object[key];
}

const RawJson RawJson::operator[](const QString &key) const
{
    if (m_type != Object) return RawJson();
    return m_object.value(key, RawJson());
}

bool RawJson::contains(const QString &key) const
{
    return m_type == Object && m_object.contains(key);
}

QStringList RawJson::keys() const
{
    if (m_type != Object) return {};
    return m_object.keys();
}

int RawJson::memberCount() const
{
    return m_type == Object ? m_object.size() : 0;
}

void RawJson::remove(const QString &key)
{
    if (m_type == Object) m_object.remove(key);
}

RawJson &RawJson::operator[](int index)
{
    return m_array[index];
}

const RawJson RawJson::operator[](int index) const
{
    if (m_type != Array || index < 0 || index >= m_array.size())
        return RawJson();
    return m_array.at(index);
}

void RawJson::append(const RawJson &value)
{
    if (m_type != Array) {
        m_type = Array;
        m_value.clear();
        m_object.clear();
    }
    m_array.append(value);
}

int RawJson::count() const
{
    return m_type == Array ? m_array.size() : 0;
}

void RawJson::removeAt(int index)
{
    if (m_type == Array && index >= 0 && index < m_array.size())
        m_array.removeAt(index);
}

RawJson RawJson::parse(const QString &json)
{
    Parser p(json);
    RawJson result = p.parseValue();
    p.skipWhitespace();
    if (p.pos < p.src.size())
        p.error(QStringLiteral("Caractères inattendus après la valeur JSON"));
    return result;
}

void RawJson::Parser::skipWhitespace()
{
    while (pos < src.size()) {
        QChar c = src.at(pos);
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
            ++pos;
        else
            break;
    }
}

QChar RawJson::Parser::peek() const
{
    if (pos >= src.size()) return QChar(0);
    return src.at(pos);
}

QChar RawJson::Parser::advance()
{
    if (pos >= src.size()) error(QStringLiteral("Fin de chaîne inattendue"));
    return src.at(pos++);
}

void RawJson::Parser::expect(QChar c)
{
    skipWhitespace();
    QChar got = advance();
    if (got != c)
        error(QStringLiteral("Attendu '%1', obtenu '%2'").arg(c).arg(got));
}

void RawJson::Parser::error(const QString &msg) const
{
    throw QString(QStringLiteral("RawJson parse error (pos %1): %2").arg(pos).arg(msg));
}

RawJson RawJson::Parser::parseValue()
{
    skipWhitespace();
    if (pos >= src.size()) error(QStringLiteral("Valeur attendue, fin de chaîne"));

    QChar c = peek();
    if (c == '"')                return RawJson::fromString(parseString());
    if (c == '{')                return parseObject();
    if (c == '[')                return parseArray();
    if (c == '-' || c.isDigit()) return parseNumber();
    return parseLiteral();
}

QString RawJson::Parser::parseString()
{
    expect('"');
    QString result;
    result.reserve(64);

    while (true) {
        if (pos >= src.size()) error(QStringLiteral("Chaîne non fermée"));
        QChar c = src.at(pos++);

        if (c == '"') return result;

        if (c == '\\') {
            if (pos >= src.size()) error(QStringLiteral("Séquence d'échappement incomplète"));
            QChar esc = src.at(pos++);
            switch (esc.unicode()) {
            case '"':  result += '"';  break;
            case '\\': result += '\\'; break;
            case '/':  result += '/';  break;
            case 'b':  result += '\b'; break;
            case 'f':  result += '\f'; break;
            case 'n':  result += '\n'; break;
            case 'r':  result += '\r'; break;
            case 't':  result += '\t'; break;
            case 'u': {
                if (pos + 4 > src.size())
                    error(QStringLiteral("Séquence \\uXXXX incomplète"));
                QString hex = src.mid(pos, 4);
                pos += 4;
                bool ok = false;
                ushort code = hex.toUShort(&ok, 16);
                if (!ok) error(QStringLiteral("Séquence \\u invalide: ") + hex);

                if (code >= 0xD800 && code <= 0xDBFF) {
                    if (pos + 6 > src.size() || src.at(pos) != '\\' || src.at(pos + 1) != 'u')
                        error(QStringLiteral("Low surrogate attendu après high surrogate"));
                    pos += 2;
                    QString hex2 = src.mid(pos, 4);
                    pos += 4;
                    ushort code2 = hex2.toUShort(&ok, 16);
                    if (!ok || code2 < 0xDC00 || code2 > 0xDFFF)
                        error(QStringLiteral("Low surrogate invalide: ") + hex2);
                    uint cp = 0x10000 + ((uint(code) - 0xD800) << 10) + (uint(code2) - 0xDC00);
                    result += QString::fromUcs4(&cp, 1);
                } else {
                    result += QChar(code);
                }
                break;
            }
            default:
                error(QStringLiteral("Séquence d'échappement inconnue: \\") + esc);
            }
        } else {
            result += c;
        }
    }
}

RawJson RawJson::Parser::parseObject()
{
    expect('{');
    QMap<QString, RawJson> members;

    skipWhitespace();
    if (peek() == '}') { advance(); return RawJson::fromObject(members); }

    while (true) {
        skipWhitespace();
        if (peek() != '"') error(QStringLiteral("Clé de chaîne attendue dans l'objet"));
        QString key = parseString();
        expect(':');
        RawJson val = parseValue();
        members.insert(key, val);

        skipWhitespace();
        QChar c = advance();
        if (c == '}') break;
        if (c != ',') error(QStringLiteral("',' ou '}' attendu dans l'objet"));
    }
    return RawJson::fromObject(members);
}

RawJson RawJson::Parser::parseArray()
{
    expect('[');
    QVector<RawJson> items;

    skipWhitespace();
    if (peek() == ']') { advance(); return RawJson::fromArray(items); }

    while (true) {
        items.append(parseValue());

        skipWhitespace();
        QChar c = advance();
        if (c == ']') break;
        if (c != ',') error(QStringLiteral("',' ou ']' attendu dans le tableau"));
    }
    return RawJson::fromArray(items);
}

RawJson RawJson::Parser::parseNumber()
{
    int start = pos;

    if (peek() == '-') ++pos;

    if (pos >= src.size()) error(QStringLiteral("Nombre incomplet"));
    if (peek() == '0') {
        ++pos;
    } else if (peek().isDigit()) {
        while (pos < src.size() && src.at(pos).isDigit()) ++pos;
    } else {
        error(QStringLiteral("Chiffre attendu dans le nombre"));
    }

    if (pos < src.size() && peek() == '.') {
        ++pos;
        if (pos >= src.size() || !src.at(pos).isDigit())
            error(QStringLiteral("Chiffre attendu après '.'"));
        while (pos < src.size() && src.at(pos).isDigit()) ++pos;
    }

    if (pos < src.size() && (peek() == 'e' || peek() == 'E')) {
        ++pos;
        if (pos < src.size() && (peek() == '+' || peek() == '-')) ++pos;
        if (pos >= src.size() || !src.at(pos).isDigit())
            error(QStringLiteral("Chiffre attendu dans l'exposant"));
        while (pos < src.size() && src.at(pos).isDigit()) ++pos;
    }

    return RawJson::fromNumber(src.mid(start, pos - start));
}

RawJson RawJson::Parser::parseLiteral()
{
    if (src.midRef(pos, 4) == QStringLiteral("true"))  { pos += 4; return RawJson::fromBool(true); }
    if (src.midRef(pos, 5) == QStringLiteral("false")) { pos += 5; return RawJson::fromBool(false); }
    if (src.midRef(pos, 4) == QStringLiteral("null"))  { pos += 4; return RawJson::null(); }
    error(QStringLiteral("Valeur inattendue"));
    return RawJson();
}

QString RawJson::serialize(int indent) const
{
    QString out;
    out.reserve(256);
    serializeImpl(out, indent, 0);
    if (indent >= 0) out += '\n';
    return out;
}

void RawJson::serializeImpl(QString &out, int indent, int depth) const
{
    auto newline = [&]() {
        if (indent < 0) return;
        out += '\n';
        out += QString(indent * (depth + 1), ' ');
    };
    auto closeline = [&]() {
        if (indent < 0) return;
        out += '\n';
        out += QString(indent * depth, ' ');
    };

    switch (m_type) {
    case Null:
        out += QStringLiteral("null");
        break;

    case Bool:
        out += m_value;
        break;

    case Number:
        out += m_value;
        break;

    case String:
        out += '"';
        out += escapeString(m_value);
        out += '"';
        break;

    case Array:
        if (m_array.isEmpty()) {
            out += QStringLiteral("[]");
        } else {
            out += '[';
            for (int i = 0; i < m_array.size(); ++i) {
                if (i > 0) out += ',';
                newline();
                m_array.at(i).serializeImpl(out, indent, depth + 1);
            }
            closeline();
            out += ']';
        }
        break;

    case Object:
        if (m_object.isEmpty()) {
            out += QStringLiteral("{}");
        } else {
            out += '{';
            bool first = true;
            for (auto it = m_object.constBegin(); it != m_object.constEnd(); ++it) {
                if (!first) out += ',';
                first = false;
                newline();
                out += '"';
                out += escapeString(it.key());
                out += QStringLiteral("\":");
                if (indent >= 0) out += ' ';
                it.value().serializeImpl(out, indent, depth + 1);
            }
            closeline();
            out += '}';
        }
        break;
    }
}

QString RawJson::escapeString(const QString &s)
{
    QString out;
    out.reserve(s.size() + 8);
    for (int i = 0; i < s.size(); ++i) {
        QChar c = s.at(i);
        switch (c.unicode()) {
        case '"':  out += QStringLiteral("\\\""); break;
        case '\\': out += QStringLiteral("\\\\"); break;
        case '\b': out += QStringLiteral("\\b");  break;
        case '\f': out += QStringLiteral("\\f");  break;
        case '\n': out += QStringLiteral("\\n");  break;
        case '\r': out += QStringLiteral("\\r");  break;
        case '\t': out += QStringLiteral("\\t");  break;
        default:
            if (c.unicode() < 0x20) {
                out += QStringLiteral("\\u%1").arg(c.unicode(), 4, 16, QLatin1Char('0'));
            } else {
                out += c;
            }
        }
    }
    return out;
}

使用方法如下:

RawJson ticket = RawJson::fromObject();

ticket["id"] = RawJson::fromNumber("XXXXX");
ticket["reference"] = RawJson::fromString("XXXXXXXX");
ticket["status"] = RawJson::fromString("in_progress");
ticket["priority"] = RawJson::fromNumber("2");
ticket["created_at"] = RawJson::fromString("2000-01-01T09:32:17.000Z");
ticket["sla_deadline"] = RawJson::fromString("2000-01-02T09:32:17.000Z");
ticket["is_escalated"] = RawJson::fromBool(false);

RawJson client = RawJson::fromObject();
client["id"] = RawJson::fromNumber("XXXX");
client["name"] = RawJson::fromString("XXXXX");
client["email"] = RawJson::fromString("[email protected]");
client["phone"] = RawJson::fromString("+33 6 52 58 74 15");
client["contract_value"] = RawJson::fromNumber("24999.95");
ticket["client"] = client;

RawJson assignee = RawJson::fromObject();
assignee["id"] = RawJson::fromNumber("X");
assignee["username"] = RawJson::fromString("XXXXX");
assignee["role"] = RawJson::fromString("XXXXX");
ticket["assignee"] = assignee;

RawJson equipment = RawJson::fromObject();
equipment["serial"] = RawJson::fromString("XXXXXX");
equipment["model"] = RawJson::fromString("XXXXXXXX");
equipment["firmware"] = RawJson::fromString("XXXX");
equipment["latitude"] = RawJson::fromNumber("XXXX");
equipment["longitude"] = RawJson::fromNumber("XXXX");
ticket["equipment"] = equipment;

RawJson notes = RawJson::fromArray();

RawJson note1 = RawJson::fromObject();
note1["id"] = RawJson::fromNumber("XXXX");
note1["author"] = RawJson::fromString("XXXXX");
note1["timestamp"] = RawJson::fromString("2000-01-01T09:32:17.000Z");
note1["type"] = RawJson::fromString("auto");
note1["content"] = RawJson::fromString("XXXXXX");
note1["attachments"] = RawJson::fromArray();
notes.append(note1);

RawJson note2 = RawJson::fromObject();
note2["id"] = RawJson::fromNumber("XXX");
note2["author"] = RawJson::fromString("XXXXX");
note2["timestamp"] = RawJson::fromString("2000-01-01T10:15:44.000Z");
note2["type"] = RawJson::fromString("internal");
note2["content"] = RawJson::fromString("XXXXXXXXXXXX");

RawJson attachments = RawJson::fromArray();
RawJson att1 = RawJson::fromObject();
att1["filename"] = RawJson::fromString("diag_0x4F.log");
att1["size_bytes"] = RawJson::fromNumber("184729");
att1["mime"] = RawJson::fromString("text/plain");
attachments.append(att1);
RawJson att2 = RawJson::fromObject();
att2["filename"] = RawJson::fromString("photo_terminal.jpg");
att2["size_bytes"] = RawJson::fromNumber("2431876");
att2["mime"] = RawJson::fromString("image/jpeg");
attachments.append(att2);
note2["attachments"] = attachments;

notes.append(note2);
ticket["notes"] = notes;

RawJson metrics = RawJson::fromObject();
metrics["response_time_hours"] = RawJson::fromNumber("0.7214");
metrics["resolution_progress"] = RawJson::fromNumber("0.333333333333333333");
metrics["cost_estimate"] = RawJson::fromNumber("149.90");
metrics["tax_rate"] = RawJson::fromNumber("0.20");
ticket["metrics"] = metrics;

RawJson tags = RawJson::fromArray();
tags.append(RawJson::fromString("nfc"));
tags.append(RawJson::fromString("hardware"));
tags.append(RawJson::fromString("on-site-required"));
ticket["tags"] = tags;

QString json = ticket.serialize(2);
qDebug().noquote() << json;
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章