0Pricing
Swift Academy · บทเรียน

การถอดรหัส JSON ต่างชนิด

จัดการโครงสร้าง JSON แบบพหุรูปและเปลี่ยนแปลงได้

การถอดรหัส JSON ต่างชนิด เป็นบทเรียน Swift Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Swift Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Swift Academy มีบทเรียนทั้งหมด 4 บทเรียน

JSON ต่างรูปแบบคืออะไร

API บางประเภทส่งอาร์เรย์ที่ออบเจ็กต์มีโครงสร้างแตกต่างกัน โดยแยกความแตกต่างด้วยฟิลด์ type การถอดรหัสข้อมูลเหล่านี้ให้เป็นชนิดข้อมูล Swift เดียวจำเป็นต้องตรวจสอบตัวระบุชนิดนั้นก่อน

import Foundation

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

การสร้างแบบจำลองด้วยชนิดแจกแจง

แบบจำลอง Swift ที่เหมาะสมตามธรรมชาติคือ enum ที่มีค่าที่เกี่ยวข้อง โดยมีหนึ่ง case ต่อหนึ่งโครงสร้าง JSON decoder จะเลือก case ตามตัวระบุชนิด

import Foundation

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

print("Enum models the variants")

การกำหนดคีย์ตัวระบุชนิด

เพิ่ม enum CodingKeys ที่มีฟิลด์ตัวระบุชนิด ซึ่งในที่นี้คือ type รวมถึงคีย์ข้อมูลที่คุณต้องอ่าน

import Foundation

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

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

การอ่านตัวระบุชนิด

ใน init(from:) ให้ถอดรหัสข้อความ type ก่อน จากนั้นใช้ switch กับค่านั้นเพื่อถอดรหัสข้อมูลของชนิดที่ตรงกัน

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) }

การถอดรหัสอาร์เรย์แบบผสม

เมื่อ enum รองรับ Decodable แล้ว การถอดรหัส [Block].self จะจัดการอาร์เรย์ทั้งหมดที่มีโครงสร้างผสมกันได้ด้วยการเรียกเพียงครั้งเดียว

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)

การจับคู่รูปแบบของผลลัพธ์

หลังจากถอดรหัสแล้ว ให้ใช้ switch กับ enum เพื่อจัดการแต่ละรูปแบบอย่างปลอดภัยตามชนิดข้อมูล

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 ที่ซ้อนกันสำหรับแต่ละ case แทนการใช้ค่าเพียงค่าเดียว

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)
}

การจัดการชนิดข้อมูลที่ไม่รู้จักอย่างเหมาะสม

แทนที่จะส่งข้อผิดพลาดเมื่อตัวระบุชนิดไม่รู้จัก คุณสามารถจับคู่ตัวระบุนั้นกับ case สำรองได้ เพื่อให้ชนิดข้อมูลใหม่จากเซิร์ฟเวอร์ไม่ทำให้ไคลเอ็นต์หยุดทำงาน

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") }

การเลือกตัวระบุชนิดที่ดี

ตัวระบุชนิดควรเป็นฟิลด์ที่มีความคงที่และจำเป็นต้องมี enum 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)!)

การประกอบทุกอย่างเข้าด้วยกัน

แบบจำลองหลายรูปแบบที่สมบูรณ์จะถอดรหัสอาร์เรย์แบบผสม ใช้ switch กับแต่ละรูปแบบ และเข้ารหัสกลับเป็นโครงสร้างเดิมได้ ซึ่งเป็นรูปแบบที่แข็งแกร่งสำหรับ 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 ต่างรูปแบบ

คุณได้เรียนรู้การจัดการข้อมูลหลายรูปแบบแล้ว:

  • สร้างแบบจำลองแต่ละรูปแบบเป็น enum ที่มีค่าที่เกี่ยวข้อง
  • ใน init(from:) ให้ถอดรหัสฟิลด์ตัวระบุชนิด จากนั้นใช้ switch เพื่อสร้าง case ที่ถูกต้อง
  • ถอดรหัสอาร์เรย์แบบผสมด้วย [Enum].self และจับคู่รูปแบบของผลลัพธ์
  • เพิ่ม case สำรองสำหรับชนิดข้อมูลที่ไม่รู้จัก และใช้งาน encode(to:) เพื่อให้ข้อมูลเดินทางไปกลับได้

คำถามที่พบบ่อย

บทเรียน “การถอดรหัส JSON ต่างชนิด” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การถอดรหัส JSON ต่างชนิด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Swift Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Swift Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การถอดรหัส JSON ต่างชนิด”

จัดการโครงสร้าง JSON แบบพหุรูปและเปลี่ยนแปลงได้ คุณปฏิบัติ Swift Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Swift Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Swift Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “การถอดรหัส JSON ต่างชนิด” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Swift Academy นี้ได้ไหม

ได้ บทเรียน Swift Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. CodingKeys สำหรับการเปลี่ยนชื่อ
  2. กลยุทธ์การถอดรหัสคีย์และวันที่
  3. การเข้ารหัสด้วยตนเองด้วย encode(to:) และ init(from:)
  4. การถอดรหัส JSON ต่างชนิด
← กลับไปที่ Swift Academy