Codable cheat sheet
Codable
は Swift 4.0 の基本機能の 1 つで、Swift データ型と JSON 間の信じられないほどスムーズな変換機能をもたらしました。 この記事では、一般的な質問に対する回答と一般的な問題を解決するために、Codable
を使用した簡単なコード サンプルを提供したいと思います。
SPONSORED アプリ内の購読インフラを構築し維持することは困難なことです。 幸いなことに、より良い方法があります。 RevenueCat を使用すると、数ヶ月ではなく数時間でアプリにサブスクリプションを実装できるため、アプリの開発に戻ることができます。
Try it for free
Sponsor Hacking with Swift and reach the world’s largest Swift community!
Encoding and decoding JSON
Let’s start with the basics: converting some JSON into Swift structs.
First, here’s some JSON to work with:
let json = """"""let data = Data(json.utf8)
The last line converts it to a Data
object because that’s what Codable
decoders work with.The first line’s in a JSON.
次に、完成したデータを保持する Swift 構造体を定義する必要があります。
struct User: Codable { var name: String var age: Int}
では、先に進んでデコードを実行できます。
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
それは、JSON の最初のユーザーの名前である “Paul” と表示します。 たとえば、JSON で “first_name” を取得し、それを firstName
プロパティに変換する必要があるかもしれません。
さて、ここで一つの明白な解決策は、同じ命名規則を使用するように JSON または Swift タイプを変更するだけですが、ここではそれをするつもりはありません。
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
これを動作させるために、JSON デコーダーで 1 つのプロパティのみを変更する必要があります。
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
これは、Swift に、大文字小文字の名前 (names_written_like_this) をラクダ形の名前 (namesWrittenLikeThis) にマッピングするように指示します。
異なるキー名をマッピングする
Swift のプロパティと完全に異なる JSON キーがある場合、CodingKeys
列挙型を使用してそれらをマッピングすることができます。
この JSON を見てみましょう。
let json = """"""
これらのキー名はあまりよくありません。そして、実際には、そのデータを次のような構造体に変換したいと思います。 これは、プロパティ名 (enum のケース) と JSON 名 (enum の値) の両方を同時に指定できるように、生の値に文字列を使用する通常の enum です。 また、CodingKey
プロトコルに準拠する必要があり、これにより Codable
プロトコルで動作します。
そこで、この enum を struct:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
に追加すると、予定どおり JSON をデコードできるようになります。
Note: 列挙型を CodingKeys
と呼び、プロトコルを CodingKey
と呼びます。
ISO-8601 日付を扱う
インターネット上で日付を扱う方法は多数ありますが、ISO-8601 が最も一般的です。 これは YYYY-MM-DD 形式で完全な日付を、次に時間情報の開始を示す文字 “T”、次に HH:MM:SS 形式で時間を、そして最後にタイムゾーンをエンコードします。 タイムゾーンの「Z」は「Zulu time」の略で、一般的にはUTCを意味します。
Codable
は日付変換を内蔵しており、ISO-8601を扱うことができます。 つまり、この JSON が与えられると、次のようにデコードできます:
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
これは ISO-8601 日付解析を可能にし、1999-04-03T17:30:31Z から Date
インスタンスに変換し、さらにスネークケースからキャメルケースへの変換も扱えます。
-
.deferredToDate
フォーマットは Apple 独自の日付フォーマットで、2001 年 1 月 1 日からの秒とミリ秒の数を追跡します。 -
.millisecondsSince1970
フォーマットは、1970年1月1日からの秒数とミリ秒を記録します。 7012> -
.secondsSince1970
フォーマットは、1970年1月1日からの整数秒の数を記録します。 これはオンラインでは非常に一般的で、ISO-8601 に次ぐものです。
Working with custom dates
If your date format does not match one of the built-in options, don’t despair, don’t help you know the way of the date format: Codable は、あなたが作成した日付フォーマッターに基づいてカスタム日付を解析できます。
たとえば、この JSON は、学生が大学を卒業した日を追跡します。 幸いなことに、次のように、日付デコード戦略として事前に設定された DateFormatter
インスタンスを提供することができます:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Working with weird dates
時には、DateFormatter
でもそれを扱うことができないほど奇妙な日付を取得することがあります。 たとえば、1970 年 1 月 1 日から経過した日数を使用して日付を格納する JSON を受け取るかもしれません。
let json = """"""
これを動作させるには、日付のカスタム デコーダーを作成する必要があります。 他のすべてはまだ Codable
によって処理されます – 私たちは、日付部分を処理するカスタムクロージャを提供しているだけです。 しかし、それではサマータイムやその他の日付の問題を考慮できないので、より良い解決策は DateComponents
と Calendar
を次のように使うことです:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .custom { decoder in // pull out the number of days from Codable let container = try decoder.singleValueContainer() let numberOfDays = try container.decode(Int.self) // create a start date of Jan 1st 1970, then a DateComponents instance for our JSON days let startDate = Date(timeIntervalSince1970: 0) var components = DateComponents() components.day = numberOfDays // create a Calendar and use it to measure the difference between the two let calendar = Calendar(identifier: .gregorian) return calendar.date(byAdding: components, to: startDate) ?? Date()}
Warning: 多くの日付を解析する必要がある場合、このクロージャーは 1 つ 1 つの日付に対して実行されることを覚えておいてください – 早くしてください。 たとえば、
let json = """"""
Codable
は、関係を明確に記述できる限り、これをうまく処理できます。
これを行う最も簡単な方法は、次のように入れ子構造体を使うことです。
Parsing hierarchical data the hard way
階層的なデータをフラット構造体にパースしたい場合 – つまり、「フラット構造体へのパース」。 を書けるようにしたい場合は、自分でパースする必要があります。
まず、最終的に作成したい構造体を作成します。
struct User: Codable { var firstName: String var lastName: String var age: Int}
次に、データが階層構造のどこにあるのかを示すコーディング キーを定義する必要があります。
JSON をもう一度見てみましょう。
let json = """"""
見てわかるように、ルートには「name」というキーと「age」というキーがあるので、これをルート コード キーとして追加する必要があります。 これを構造体に入れます。
enum CodingKeys: String, CodingKey { case name, age}
「name」の内部には、さらに「first_name」と「last_name」という 2 つのキーがありましたので、この 2 つのキーに対応するコーディング キーを作成します。
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
さて、ここからが難しいところですが、この型のためのカスタムイニシャライザーとカスタムエンコードメソッドを書かなければなりません。 この空のメソッドを構造体に追加することから始めます。
init(from decoder: Decoder) throws {}
その内部で、最初に行う必要があるのは、次のように CodingKeys
列挙型のキーを使用して読み取ることができるコンテナを引き出すことです。 これは、型安全な方法で行われます。デコードしたい型 (年齢の場合は Int.self
) と CodingKeys
列挙型のキー名を指定します:
age = try container.decode(Int.self, forKey: .age)
次に、名前データを読み込むために 1 レベル下げる必要があります。 先ほど見たように、「name」は CodingKeys
列挙型の最上位のキーですが、実際には他の値の入れ子になっており、その中を読み取る必要があります。
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
そして最後に、.firstName
および .lastName
キーに対応する 2 つの文字列を読み取ることができます:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
これでカスタムのイニシャライザーは終了ですが、まだ 1 つのメソッドを記述する必要があります。 encode(to:)
. これは、先ほど書いたイニシャライザーの逆で、プロパティを必要に応じて入れ子にしたコンテナーに変換する仕事です。 これで、firstName
プロパティを直接読み取ることができるようになり、より快適になりました!
SPONSORED アプリ内の購読インフラを構築し維持することは困難です。 幸いなことに、より良い方法があります。 RevenueCat を使用すると、数ヶ月ではなく数時間でアプリにサブスクリプションを実装できるため、アプリの開発に戻ることができます。
無料で試す
スポンサーになって、Hacking with Swift で世界最大の Swift コミュニティに参加しましょう!
。
Leave a Reply