Декодирование неоднородного JSON
Обрабатывайте полиморфные и динамические структуры JSON.
«Декодирование неоднородного JSON» — бесплатный урок Swift Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.
Что такое неоднородный JSON
Некоторые программные интерфейсы возвращают массивы, в которых объекты имеют разную структуру и различаются по полю type. Чтобы декодировать их в один тип Swift, сначала нужно проверить этот признак.
import Foundation
let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
print("Each element carries a type discriminator")Моделирование с помощью перечисления
Естественная модель Swift — это перечисление со связанными значениями, по одному варианту для каждой структуры JSON. Декодер выберет вариант на основе признака.
import Foundation
enum Block {
case text(String)
case number(Int)
}
print("Enum models the variants")Определение ключа-признака
Добавьте перечисление CodingKeys, включив в него поле-признак (здесь type) и необходимые ключи данных, которые нужно прочитать.
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 одним вызовом обрабатывает целый массив элементов разной структуры.
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 с известными именами типов делает конструкцию выбора исчерпывающей и понятной.
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)!)Собираем всё вместе
Полная полиморфная модель декодирует смешанный массив, выбирает действие для каждого варианта и может снова закодировать данные в той же структуре — это надёжный шаблон для гибких программных интерфейсов.
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/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.
Чему я научусь в уроке «Декодирование неоднородного JSON»?
Обрабатывайте полиморфные и динамические структуры JSON. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Swift Academy?
Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Декодирование неоднородного JSON»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Swift Academy?
Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- CodingKeys для переименования
- Стратегии декодирования ключей и дат
- Ручные encode(to:) и init(from:)
- Декодирование неоднородного JSON