Десериализовать массив JSON на основе атрибута вложенного типа - PullRequest
1 голос
/ 06 октября 2019

Рассмотрим этот пример 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)")
}

Есть ли способ сделать код Swift выше Codable совместимым? Я знаю, что вы можете решить эту проблему вручную, сначала захватив каждую строку Row type, а затем создав правильный тип модели Row, а также изменив их со структур на классы и разрешив протоколу Row быть суперклассом. вместо. Но есть ли способ, который требует меньше ручного труда?

1 Ответ

1 голос
/ 06 октября 2019

Лучше всего использовать 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) )

Теперь вы можете добавить в декодер любой другой случай, который вам нужен, чтобы создать приложение для приложения .

...