まとめ
propertyWrapperを利用することで、JSONの文字列を安全にURLに変換してCodableのstructを生成できるようになります。
概要
JSONをSwiftのStructに落とし込む時にCodableを使うことがあると思います。
StructをCodableに対応すると、JSONのデータをJSONDecoderを通して、数値、文字列、Boolなどに変換したStructに変換することができます。
しかし、URLの場合は、JSONの文字列がinit(string:)
の結果でnilになるような文字列の場合、DecodingError
となってstructとして生成できなくなってしまいます。
例えば以下のようなstructです。
struct MyUser: Codable {
let id: Int
let name: String
let url: URL
}
これに対し、urlが問題なくURLとして生成できる文字列であればstruct生成されますが、urlがURLではない文字列がくると、structが作られず、JSONDecoderがエラーを吐いて処理が失敗します。
let data = """
{
"id": 1234,
"name": "Hello World!",
"url": ""
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let user = try decoder.decode(MyUser.self, from: data) //DecodingError
print(user.url)
対応
まず、JSONからURLに変換する用の、Codableを付与したpropertyWrapper structを用意します。
JsonUrl
として用意しました。
init処理では、decoderによってデコードされたwrappedValueのStringをprojectedValueとしてURLに設定します。
@propertyWrapper
struct JsonUrl: Codable {
var wrappedValue: String
var projectedValue: URL?
init(from decoder: Decoder) throws {
wrappedValue = try String(from: decoder)
projectedValue = URL(string: wrappedValue)
}
}
次に、URLとして設定するプロパティに@JsonUrl
を付与し、Stringにします。
letはvarにしないとビルドが通りません。
struct MyUser: Codable {
let id: Int
let name: String
@JsonUrl
var url: String
}
あとは、structを利用する側は、URLとして利用する場合は$を付与して利用すればOKです。
decoderがエラーを出すことなく処理が可能です。
let data = """
{
"id": 1234,
"name": "Hello World!",
"url": ""
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let user = try decoder.decode(MyUser.self, from: data)
print(user.$url) // nil
過去のSwift JSON+Codableリンク