根据嵌套类型属性反序列化 JSON 数组

Deserialize JSON array based on nested type attribute

考虑这个例子JSON:

{
    "sections": [{
        "title": "Sign up",
        "rows": [
            {
                "type": "image",
                "imageURL": "https://example.com/image.jpg"
            },
            {
                "type": "textField",
                "value": "",
                "placeholder": "Username"
            },
            {
                "type": "textField",
                "placeholder": "password"
            },
            {
                "type": "textField",
                "placeholder": "confirmPassword"
            },
            {
                "type": "button",
                "placeholder": "Register!"
            }
        ]
    }]
}

假设我想将上面的 JSON 解析为以下模型 (我知道它不会编译,因为 Row 协议不对应 Decodable):

enum RowType: String, Codable {
    case textField
    case image
    case button
}

protocol Row: Codable {
    var type: RowType { get }
}

struct TextFieldRow: Row {
    let type: RowType
    let placeholder: String
    let value: String

    enum CodingKey: String {
        case type
        case placeholder
        case value
    }
}

struct ImageRow: Row {
    let type: RowType
    let imageURL: URL

    enum CodingKey: String {
        case type
        case imageURL
    }
}

struct ButtonRow: Row {
    let type: RowType
    let title: String

    enum CodingKey: String {
        case type
        case title
    }
}

struct Section: Codable {
    let rows: [Row]
    let title: String

    enum CodingKey: String {
        case rows
        case title
    }
}

struct Response: Codable {
    let sections: [Section]

    enum CodingKey: String {
        case sections
    }
}


// Parsing the response using the Foundation JSONDecoder
let data: Data // From network
let decoder = JSONDecoder()
do {
    let response = try decoder.decode(Response.self, from: data)
} catch {
    print("error: \(error)")
}

有没有办法让 Codable 上面的 Swift 代码兼容? 我知道您可以通过首先获取每个 Rowtype 字符串然后创建正确类型的 Row 模型并将它们从结构更改为 类 来手动解决此问题并让 Row 协议成为超类。但是有没有一种方法可以减少体力劳动呢?

使用具有关联值的 enum 是最佳选择:

考虑这个枚举:

enum Row: Decodable {
    case textField(TextFieldRow)
    case image(ImageRow)
    // and other cases

    case unknown

    enum CodingKeys: String, CodingKey {
        case type
    }

    public init(from decoder: Decoder) throws {
        do {
            let selfContainer = try decoder.singleValueContainer()
            let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
            let type = try typeContainer.decode(String.self, forKey: .type)

            switch type {
            case "textField": self = .textField( try selfContainer.decode(TextFieldRow.self) )
            case "Image": self = .image( try selfContainer.decode(ImageRow.self) )
            // and other cases
            default: self = .unknown
            }
        }
    }
}

有了这些变化:

struct TextFieldRow: Decodable {
    let placeholder: String?
    let value: String?
}

struct ImageRow: Decodable {
    let imageURL: URL
}

// and so on

现在这将像魅力一样解码:

// Minmal testing JSON
let json = """
[
            {
                "type": "image",
                "imageURL": "https://example.com/image.jpg"
            },
            {
                "type": "textField",
                "value": "",
                "placeholder": "Username"
            },
            {
                "type": "textField",
                "placeholder": "password"
            }
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
print( try! decoder.decode([Row].self, from: json) )

您现在可以将您需要的任何其他案例添加到解码器以构建您的 application builder app。