0Pricing
Swift Academy · レッスン

異種JSONのデコード

ポリモーフィックで動的なJSON構造を処理します。

「異種JSONのデコード」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

異種JSONとは

APIによっては、type フィールドで判別される、異なる構造のオブジェクトを含む配列を返します。これらを1つのSwift型にデコードするには、まずその判別フィールドを確認する必要があります。

import Foundation

let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
print("Each element carries a type discriminator")

列挙型でモデル化する

Swiftでは、関連値を持つ列挙型が自然なモデルになります。JSONの構造ごとに1つのケースを定義します。デコーダーは判別フィールドに基づいてケースを選択します。

import Foundation

enum Block {
    case text(String)
    case number(Int)
}

print("Enum models the variants")

判別キーを定義する

判別フィールド(ここでは type)と、読み取る必要のあるペイロードのキーを含む CodingKeys 列挙型を追加します。

import Foundation

enum CodingKeys: String, CodingKey {
    case type
    case value
}

print(CodingKeys.type.stringValue, CodingKeys.value.stringValue)

判別フィールドを読み取る

init(from:) では、まず type の文字列をデコードし、その値で分岐して対応するペイロードをデコードします。

import Foundation

enum Block: Decodable {
    case text(String)
    case number(Int)
    enum CodingKeys: String, CodingKey { case type, value }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        let type = try c.decode(String.self, forKey: .type)
        switch type {
        case "text": self = .text(try c.decode(String.self, forKey: .value))
        case "number": self = .number(try c.decode(Int.self, forKey: .value))
        default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown type")
        }
    }
}

let b = try JSONDecoder().decode(Block.self, from: "{\"type\":\"text\",\"value\":\"hi\"}".data(using: .utf8)!)
if case let .text(s) = b { print(s) }

混在した配列をデコードする

列挙型が Decodable になると、[Block].self をデコードするだけで、異なる構造が混在した配列全体を1回の呼び出しで処理できます。

import Foundation

enum Block: Decodable {
    case text(String)
    case number(Int)
    enum CodingKeys: String, CodingKey { case type, value }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        switch try c.decode(String.self, forKey: .type) {
        case "text": self = .text(try c.decode(String.self, forKey: .value))
        case "number": self = .number(try c.decode(Int.self, forKey: .value))
        default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown")
        }
    }
}

let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
let blocks = try JSONDecoder().decode([Block].self, from: json.data(using: .utf8)!)
print(blocks.count)

結果をパターンマッチングする

デコードした後、列挙型に対して分岐し、型安全な方法で各バリエーションを処理します。

import Foundation

enum Block: Decodable {
    case text(String)
    case number(Int)
    enum CodingKeys: String, CodingKey { case type, value }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        switch try c.decode(String.self, forKey: .type) {
        case "text": self = .text(try c.decode(String.self, forKey: .value))
        case "number": self = .number(try c.decode(Int.self, forKey: .value))
        default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown")
        }
    }
}

let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
for b in try JSONDecoder().decode([Block].self, from: json.data(using: .utf8)!) {
    switch b {
    case .text(let s): print("text:", s)
    case .number(let n): print("number:", n)
    }
}

ネストされたペイロード

各バリエーションがより複雑なデータを持つ場合は、単一の値ではなく、ケースごとにネストされた Codable 構造体をデコードします。

import Foundation

struct ImagePayload: Decodable { var url: String; var width: Int }
enum Block: Decodable {
    case image(ImagePayload)
    enum CodingKeys: String, CodingKey { case type }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        switch try c.decode(String.self, forKey: .type) {
        case "image": self = .image(try ImagePayload(from: decoder))
        default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown")
        }
    }
}

let json = "{\"type\":\"image\",\"url\":\"a.png\",\"width\":100}"
if case let .image(p) = try JSONDecoder().decode(Block.self, from: json.data(using: .utf8)!) {
    print(p.url, p.width)
}

未知の型を適切に処理する

未知の判別値でエラーを投げる代わりに、フォールバックケースに対応付けることができます。これにより、サーバーに新しい型が追加されてもクライアントがクラッシュしません。

import Foundation

enum Block: Decodable {
    case text(String)
    case unknown
    enum CodingKeys: String, CodingKey { case type, value }
    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        switch try c.decode(String.self, forKey: .type) {
        case "text": self = .text(try c.decode(String.self, forKey: .value))
        default: self = .unknown
        }
    }
}

let b = try JSONDecoder().decode(Block.self, from: "{\"type\":\"video\"}".data(using: .utf8)!)
if case .unknown = b { print("fell back to unknown") }

適切な判別フィールドを選ぶ

判別フィールドには、安定していて必須のものを選ぶ必要があります。既知の型名を表す String 列挙型を使うと、switch文をすべてのケースに対応させやすく、読みやすくなります。

import Foundation

enum Kind: String, Decodable { case text, number }

let data = "\"text\"".data(using: .utf8)!
let k = try JSONDecoder().decode(Kind.self, from: data)
print(k == .text)

多相的な値をエンコードする

エンコードとデコードを往復させるには、encode(to:) も実装します。判別フィールドとバリエーションのペイロードを、元の形式で書き出します。

import Foundation

enum Block: Encodable {
    case text(String)
    case number(Int)
    enum CodingKeys: String, CodingKey { case type, value }
    func encode(to encoder: Encoder) throws {
        var c = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .text(let s):
            try c.encode("text", forKey: .type)
            try c.encode(s, forKey: .value)
        case .number(let n):
            try c.encode("number", forKey: .type)
            try c.encode(n, forKey: .value)
        }
    }
}

let data = try JSONEncoder().encode(Block.number(42))
print(String(data: data, encoding: .utf8)!)

全体を組み立てる

完全な多相モデルでは、混在した配列をデコードし、各バリエーションに応じて分岐し、同じ構造に再びエンコードできます。柔軟なAPIに対応する堅牢なパターンです。

import Foundation

enum Block: Codable {
    case text(String)
    case number(Int)
    enum CodingKeys: String, CodingKey { case type, value }
    init(from d: Decoder) throws {
        let c = try d.container(keyedBy: CodingKeys.self)
        switch try c.decode(String.self, forKey: .type) {
        case "text": self = .text(try c.decode(String.self, forKey: .value))
        default: self = .number(try c.decode(Int.self, forKey: .value))
        }
    }
    func encode(to e: Encoder) throws {
        var c = e.container(keyedBy: CodingKeys.self)
        switch self {
        case .text(let s): try c.encode("text", forKey: .type); try c.encode(s, forKey: .value)
        case .number(let n): try c.encode("number", forKey: .type); try c.encode(n, forKey: .value)
        }
    }
}

let blocks: [Block] = [.text("hi"), .number(7)]
let data = try JSONEncoder().encode(blocks)
print(try JSONDecoder().decode([Block].self, from: data).count)

理解度チェック:異種JSON

多相的なデコードについての理解度を確認しましょう。

まとめ:異種JSONのデコード

多相的なペイロードを処理する方法を学びました。

  • バリエーションを、関連値を持つ列挙型としてモデル化します。
  • init(from:) で判別フィールドをデコードし、分岐して適切なケースを構築します。
  • [Enum].self で混在した配列をデコードし、結果をパターンマッチングします。
  • 未知の型用のフォールバックケースを追加し、encode(to:) を実装してデコードとエンコードを往復できるようにします。

よくある質問

「異種JSONのデコード」レッスンは無料ですか?

はい。「異種JSONのデコード」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「異種JSONのデコード」で何を学びますか?

ポリモーフィックで動的なJSON構造を処理します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「異種JSONのデコード」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 名前変更のためのCodingKeys
  2. キーと日付のデコード戦略
  3. 手動のencode(to:)とinit(from:)
  4. 異種JSONのデコード
← Swift Academyに戻る