QJsonDocument与 IEEE 754双精度浮点数
其实我在用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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。