最近のRestfulなAPIなら無いと思いますが、業務で別の会社がサーバを担当するケースでは稀にそういったクソみたいなAPIにブチ当たる場合があります~~(というか現在進行系でぶち当たってて頭を抱えてる状態😂)~~。
例えばこんな感じ
{
"ResponseHeader": {
"status": "0",
"message": "",
"validationErrorMessages": []
},
"itemList": [
{
"name": "hoge"
}
]
}
いやもうResponseHeader
ってなんやねんってツッコミは置いといて、
こういうJSONをCodable
で扱おうとするとCodingKey
を用意しないといけなくて面倒です。
struct Response: Codable {
let responseHeader: ResponseHeader
let itemList: [Item]
enum CodingKeys: String, CodingKey {
case responseHeader = "ResponseHeader"
case itemList
}
}
しかし、JSONDecoder
のkeyDecodingStrategy
を使えばなんとかできるのではなかろうかと思って検証してみました。
JSONEncoder.KeyEncodingStrategy.custom(@escaping ([CodingKey]) -> CodingKey)
についてはAppleのドキュメントとこの記事が参考になります。
↑を元にまずはResponseKey
を作ります。
struct ResponseKey: CodingKey, ExpressibleByStringLiteral {
typealias StringLiteralType = String
var stringValue: String
var intValue: Int?
init(stringLiteral value: String) {
self.stringValue = value
self.intValue = nil
}
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
そしてJSONDecoder
をこんな感じで設定してあげます。
var decoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom { keys in
// 先頭文字を小文字にする
let key = keys.last!.stringValue.lowercasingFirst
return ResponseKey(stringLiteral: key)
}
return decoder
}
こうすることで、このdecoder
を使ってデコードするJSONは全てのキーを先頭小文字に変換して扱ってくれるようになります。
つまり、キャメルケースであろうとパスカルケースであろうと問答無用でキャメルケースとして扱うわけです。
これで、上のResponse
はCodeingKey
を定義することなく、シンプルに
struct Response: Codable {
let responseHeader: ResponseHeader
let itemList: [Item]
}
と書くことができるようになります。
ちなみにJSONDecoder.KeyDecodingStrategy.custom
で用意するクロージャがなぜ[CodingKey] -> CodingKey
なのかはドキュメントを読んでみてもいまいちピンと来ませんでした。
なぜだ...🤔