Qt 5.15.0 での意図しない挙動の変更とその対応について
2020年5月26日にリリースされた Qt 5.15.0 にそれ以前のバージョンとの動作の互換性が壊れた部分が見つかりました。
- [QTBUG-84739] Regression with dataloss
- [Development] [RFO QTBUG-84739] QJsonValue::fromVariant containing QByteArray
中身が QByteArray の QVariant を QJsonValue に変換する際の挙動が、Qt 5.15.0 で意図せずに変わってしまい、開発者メーリングリストでどう対応したらよいかの議論がなされました。
5.15.0 で変わった挙動の概要
Testcase:
const char binarydata[] = “\0\1\2\3\xff”;
QVariantMap vmap = {
{“ascii”, QByteArray(“ASCII text.\n”)},
{“latin1”, QByteArray(“R\xe9sum\xe9”)},
{“utf8”, QByteArray(“Résumé”)},
{“binary”, QByteArray(binarydata, sizeof(binarydata) – 1)}
};
QJsonObject jobj = QJsonObject::fromVariantMap(vmap);
printf(“%s”, qPrintable(QJsonDocument(jobj).toJson()));
メーリングリストの投稿より
これを Qt 5.14.2 で実行すると、以下のようになります。
{ "ascii": "ASCII text.\n", "binary": null, "latin1": "R�sum�", "utf8": "Résumé" }
Qt 5.15.0 では以下のようになります。
{ "ascii": "QVNDSUkgdGV4dC4K", "binary": "AAECA_8", "latin1": "UulzdW3p", "utf8": "UsOpc3Vtw6k" }
原因について
Qt 5.12 で Qt に CBOR の API が追加され、それ以来 Qt 内部でも JSON からの移行が進んでいます。その一環として Qt 5.15 で QJson* クラスの実装を QCbor ベースに変える という変更がなされました。
Qt が提供してる QVariant は様々なデータ型を格納することができますが、JSON や CBOR は限られた型のみをサポートしていて、QVariant からの変換時には「何かしらの方法で」それぞれがサポートしている型に変換がなされます。
QByteArray も CBOR や JSON がサポートしていない型の1つで、Qt 5.14 では UTF-8 であるという想定で文字列に変換されていました。
Qt 5.15 では CBOR の仕様に定義されている JSON への変換のルール に従って Base64URL で変換されてしまうようになったというのが現状です。
A byte string (major type 2) that is not embedded in a tag that
specifies a proposed encoding is encoded in base64url without
padding and becomes a JSON string.
4.1. Converting from CBOR to JSON
対策について
- 変更後の挙動を仕様とする
- 変更前の挙動を仕様として、元に戻す
- Qt 5 は変更前の挙動に戻し、Qt 6 は変更後の挙動を仕様とする
という案をベースに議論がなされ、最終的に Qt 5.15.1 で元の挙動に戻す という結論に達しました。
Base64 は可逆変換ですが、QByteArray を UTF-8 だと仮定して QString に変換する場合には、逆変換が効かないケースがあり、QByteArray に限らず、注意が必要な変換を伴い場合については API ドキュメントにその旨を記載をしようということになりました。