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を含む文字列の例です。
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で定義してあっても同様です。
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
}
}
}
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