Swift Digest
SE-0489 | Swift Evolution

Improve EncodingError and DecodingError’s printed descriptions

Proposal
SE-0489
Authors
Zev Eisenberg
Review Manager
Xiaodi Wu
Status
Implemented (Swift 6.3)

01 何が問題だったのか

EncodingErrorDecodingError には独自のデバッグ向けの説明が定義されておらず、print などで文字列化したときの出力は、デフォルトの enum の文字列表現がそのまま使われていました。情報自体は揃っているのですが、1行に詰め込まれた長い文字列の中に埋もれてしまい、人間が読み解くのに手間がかかります。

たとえば次のようなネストした Codable モデルを考えます。

struct Person: Codable {
  var name: String
  var home: Home
}

struct Home: Codable {
  var city: String
  var country: Country
}

struct Country: Codable {
  var name: String
  var population: Int
}

深い位置にあるフィールド population を欠いた JSON をデコードしようとすると、

let jsonData = Data("""
[
  {
    "name": "Ada Lovelace",
    "home": {
      "city": "London",
      "country": {
        "name": "England"
      }
    }
  }
]
""".utf8)

do {
  _ = try JSONDecoder().decode([Person].self, from: jsonData)
} catch {
  print(error)
}

このコードは次のような出力を出します。

keyNotFound(CodingKeys(stringValue: "population", intValue: nil), Swift.DecodingError.Context(codingPath: [_CodingKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "home", intValue: nil), CodingKeys(stringValue: "country", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"population\", intValue: nil) (\"population\")."), underlyingError: nil)

ここには「キーが見つからないエラーであること」「欠けているキーが "population" であること」「そこへ至るパスが [0]"home""country" であること」「underlying error は無いこと」といった必要な情報がすべて含まれています。しかし読みにくく、慣れていない開発者にはログのノイズのように見えてしまい、有用な情報が含まれていることにすら気づけないことがあります。

02 どのように解決されるのか

EncodingErrorDecodingErrorCustomDebugStringConvertible に適合させ、人間が読みやすい debugDescription を提供します。

@available(SwiftStdlib 6.2, *)
extension EncodingError: CustomDebugStringConvertible {
  public var debugDescription: String {...}
}

@available(SwiftStdlib 6.2, *)
extension DecodingError: CustomDebugStringConvertible {
  public var debugDescription: String {...}
}

これにより、print(error)String(reflecting: error) で得られる出力が、エラーの種類・対象キー・コーディングパス・underlying error などを整理した形で表示されるようになります。具体的なフォーマットは仕様としては規定されておらず、将来変わる可能性があります。エラーの内容をプログラムから判定したい場合は、これまで通り EncodingError / DecodingError の値を直接 switch などで分解してください。

CustomStringConvertible ではなく CustomDebugStringConvertible

CustomStringConvertibledescription)ではなく CustomDebugStringConvertibledebugDescription)への適合が選ばれています。これは「この整形済みの文字列はデバッグ目的のものであり、ユーザー向け表示やログのパース対象として依存すべきではない」というニュアンスを伝えるためです。

バックデプロイの扱い

CustomDebugStringConvertible への適合自体はバックデプロイされません。そのため、ABI 安定なプラットフォームで古い標準ライブラリ上で動くアプリでは、新しい整形済みの出力は得られず、従来通りの出力のままになります。

Future Directions(今後の見通し)

提案では今回のスコープ外として、次の方向性が示されています。いずれも実現を約束するものではありません。

  • Foundation 側のエンコーダ/デコーダの改善JSONDecoder などが生成する debugDescription には、今回標準ライブラリ側で出すようになった情報と重複する部分があります。新しい標準の出力が利用できることを前提に、Foundation 側でもより簡潔な表現に置き換えることが考えられます。CodingKey のデフォルトの description をそのまま使わず、エンコーダ/デコーダ側で短い表現を組み立てる、といった方向です。
  • エラー位置周辺のソースの表示:デコードエラーの際に、コーディングパスだけでなく原データの該当箇所付近を一緒に出すことも検討されています。ただし、これを実現するには DecodingError の公開インターフェースを拡張してより多くのコンテキストを保持できるようにする必要があり、Codable の後継 API を設計する文脈で改めて考える話とされています。