はじめに
アプリで利用するAPIの種類が増えてくると、APIによってAPIレスポンスの形式が異なるという課題が発生します。
例えばこのような価格を返す複数のAPIがあったとします。
{ "price": 10000 }
{ "price": "10000" }
{ "price": "10,000円" }
特に複数APIのマッシュアップをするような大規模サービスでは、ドメイン毎にコード規約やレスポンス形式の考え方が異なるため、同じ価格を表現するレスポンスでもこのようなことが発生しがちです。これらのレスポンス形式のゆらぎを吸収するPrice型をCodableでつくってみます。
Price型をつくる
上記の課題を解決するために、以下のようなPrice型を定義してみます。
Price型はDecodableに準拠し、init(from decoder: Decoder) throws
メソッドを実装することでデコード処理をカスタマイズしています。
struct Price: RawRepresentable, Decodable {
typealias RawValue = Int
let rawValue: Int
init?(rawValue: Self.RawValue) {
self.rawValue = rawValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// String型からInt型に変換する
// 後述するextractNumber関数を用いて文字列から数字のみを取り出す
if let stringValue = try? container.decode(String.self), let intValue = Int(stringValue.extractNumber) {
self.rawValue = intValue
return
}
let intValue = try container.decode(Int.self)
self.rawValue = intValue
}
}
文字列から数字のみを取り出すextractNumber関数は以下のように実装しています。
extension String {
var extractNumber: String {
return trimmingCharacters(in: CharacterSet.decimalDigits.inverted)
.replacingOccurrences(of: ",", with: "")
}
}
Price型を利用してデコードする
以下のようなJSONレスポンスを想定します。
{
"price1": 1000,
"price2": "2000",
"price3": "3,000円",
"price4": "¥4,000"
}
以下のように、レスポンスの価格の表現形式によらず、Price型としてデコードができます。
let jsonString = """
{
"price1": 1000,
"price2": "2000",
"price3": "3,000円",
"price4": "¥4,000"
}
"""
let data = jsonString.data(using: .utf8)!
// レスポンスの価格の表現形式によらず、Price型としてデコード
struct Item: Decodable {
let price1: Price
let price2: Price
let price3: Price
let price4: Price
}
do {
let item = try JSONDecoder().decode(Item.self, from: data)
print(item)
} catch {
print(error)
}
UI表示のためにさらに便利にする
せっかくPrice型という独自型を定義したので、価格表示のための文字列を返すロジックを実装します。ここでは、価格は3桁区切りのカンマをつけ、〜円の形式で表示するというドメインロジックがあると想定します。
Int型を拡張子し、3桁区切りのカンマを付与した文字列を返すwithComma
プロパティを実装します。それを用いて、Price型にformattedString
プロパティを実装します。
extension Int {
/// 3桁区切りのカンマを付与した文字列を返す
var withComma: String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter.string(from: NSNumber(value: self)) ?? "\(self)"
}
}
struct Price: RawRepresentable, Decodable {
/// 3桁区切りのカンマをつけ、〜円の形式にした文字列
var formattedString: String {
return rawValue.withComma + "円"
}
// 〜中略〜
}
formattedString
を用いることで、以下のように価格表示のフォーマットが簡単にできます。
print(item.price1.formattedString) // "1,000円"
print(item.price2.formattedString) // "2,000円"
print(item.price3.formattedString) // "3,000円"
print(item.price4.formattedString) // "4,000円"