LoginSignup
1
2

More than 1 year has passed since last update.

Swift JSON+Codableで不正なURL表現の文字列があった場合のデコードエラーを防ぐ

Posted at

URLのCodable

JSONなどのデータをSwiftのStructに落とし込む時、Codableが使われます。
structの各プロパティをCodableに対応したクラスにすることで、JSONをstructに変換することができます。

Codableに対応したクラスですが、Appleのドキュメントに、Encoding and Decoding Custom Types というものがあります。
ここには、String, Int, Double, Date, Data, そしてURLが対応していると列挙されています。
なので、struct中にURLがあっても、その文字列がURLで表現される場合、正しくURLとして変換されます。
以下はURLを含む文字列の例です。

JSONデータ
let data = """
{
  "id": 1234,
  "name": "Hello World!",
  "url": "http://example.com"
}
""".data(using: .utf8)
struct MyData: Codable {
    let id: Int
    let name: String
    let url: URL
}

let ret = try! JSONDecoder().decode(MyData.self, from: data!)
print(ret) //MyData(id: 1234, name: "Hello World!", url: http://example.com)

問題

しかし、この文字列→URL変換処理は、URL(string:)で変換できることが条件となっているので、URLの部分の文字列が空文字や日本語を含むURL、またはURLとは看做さない文字列がJSONに含まれていると、JSONそのものがDecodingErrorになってしまいます。
この挙動はstructのURLがURL?とOptionalで定義してあっても同様です。

JSONデータ
let data = """
{
  "id": 1234,
  "name": "Hello World!",
  "url": ""
}
""".data(using: .utf8)
let ret = try! JSONDecoder().decode(MyData.self, from: data!) 
// Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "url", intValue: nil)], debugDescription: "Invalid URL string.", underlyingError: nil))

対処

この問題を対処するために、一度Codableに対応した別クラス SafeCodableUrl を挟んでデコードを行うようにしてみます。
URLにできる文字列がJSONに入っていた場合、SafeCodableUrlのvalueにURLが存在し、URLにできない文字列の場合、valueはnilとなり、より安全にJSONが扱えるようになります。

struct SafeCodableUrl: Codable {
    let value: URL?
    init(from decoder: Decoder) throws {
        let container = try? decoder.singleValueContainer()
        if let string = try? container?.decode(String.self) {
            self.value = URL(string: string)
        } else {
            self.value = nil
        }
    }
}
JSONデータ
let data = """
{
  "id": 1234,
  "name": "Hello World!",
  "url": ""
}
""".data(using: .utf8)
struct MyData: Codable {
    let id: Int
    let name: String
    let url: SafeCodableUrl //文字列がURLではない場合もデコードエラーにならない
}

let ret = try! JSONDecoder().decode(MyData.self, from: data!)
print(ret.url.value) //nil
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2