Qt 5.15.0 での意図しない挙動の変更とその対応について

2020年5月26日にリリースされた Qt 5.15.0 にそれ以前のバージョンとの動作の互換性が壊れた部分が見つかりました。

中身が 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

対策について

  1. 変更後の挙動を仕様とする
  2. 変更前の挙動を仕様として、元に戻す
  3. Qt 5 は変更前の挙動に戻し、Qt 6 は変更後の挙動を仕様とする

という案をベースに議論がなされ、最終的に Qt 5.15.1 で元の挙動に戻す という結論に達しました。

Base64 は可逆変換ですが、QByteArray を UTF-8 だと仮定して QString に変換する場合には、逆変換が効かないケースがあり、QByteArray に限らず、注意が必要な変換を伴い場合については API ドキュメントにその旨を記載をしようということになりました。

おすすめ