
Привіт, дорогі колеги та ті, хто працює з iOS. Якщо ви маєте базове уявлення про протокол Decodable, ви зможете розпарсити простий JSON за хвилину, і багато сайтів мають численні приклади того, як парсити JSON. Проблеми починаються, коли ви отримуєте JSON з кількома рівнями вкладеності, тоді вам доведеться трохи попотіти, щоб його розпарсити. Тут я хотів би поділитися своїм досвідом парсингу JSON з кількома рівнями вкладеності в CoreData. Я не описуватиму, як зробити запит для отримання даних з сервера, я просто використаю JSON, який зберігається на диску, і який ми будемо парсити та записувати в CoreData.
Досвід парсингу JSON
Отже, у нас є невеликий JSON, який має певні об'єкти, що відображаються під ключем result. Ці об'єкти, у свою чергу, мають масив друзів, які також повинні бути відображені.
{"studios":["Marvel", "Warner Brothers", "DC"], "results":[{"name":"Daffy Duck", "id":548001360, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K", "friends":[{"name":"Bugs Bunny", "id":825382838, "avatarPath":"https://avatarfiles.alphacoders.com/812/81220.webp"}, {"name":"Porky Pig", "id":819263082, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew"}]}, {"name":"Porky Pig", "id":819263082, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew", "friends":[{"name":"Bugs Bunny", "id":825382838, "avatarPath":"https://avatarfiles.alphacoders.com/812/81220.webp"}, {"name":"Daffy Duck", "id":548001360, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K"}]}, {"name":"Bugs Bunny", "id":825382838, "avatarPath":"https://avatarfiles.alphacoders.com/812/81220.webp", "friends":[{"name":"Porky Pig", "id":819263082, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew"}, {"name":"Daffy Duck", "id":548001360, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K"}]}]}
Отже, щоб все правильно розпарсити і без проблем, я знайшов таку структуру в мережі, яка здатна отримувати необхідні дані з контейнера за вказаним ключем.
struct DecodingHelper { /// Динамічний ключ private struct Key: CodingKey { let stringValue: String init?(stringValue: String){ self.stringValue = stringValue self.intValue =nil} let intValue: Int? init?(intValue: Int){returnnil}} /// Пустий модель, яка обробляє логіку витягування моделі з ключа private struct ContainerResponse<nestedmodel: decodable>: Decodable { let nested: NestedModel public init(from decoder: Decoder) throws { let key = Key(stringValue: decoder.userInfo[CodingUserInfoKey(rawValue:"key")!]! as! String)! let values = try decoder.container(keyedBy: Key.self) nested = try values.decode(NestedModel.self, forKey: key)}} static func decode<t: decodable>(modelType: T.Type, fromKey key: String, data: Data) throws -> T { let decoder = JSONDecoder() // ***Передаємо наш ключ через `userInfo` decoder.userInfo[CodingUserInfoKey(rawValue:"key")!]= key let model = try decoder.decode(ContainerResponse<t>.self, from: data).nested return model }} </t></t:></nestedmodel:>
Досить зручно працювати з такою структурою.
let craracterValues = try DecodingHelper.decode(modelType:[Character].self, fromKey:"results", data: jsonData)
У нас є статичний метод, який дозволяє мені зробити такий запит і встановити всі параметри: тип моделі, яку я хочу повернути, ключ, під яким знаходиться модель, і, очевидно, самі дані, які мені потрібні. Давайте подивимося на JSON, який у нас є. Кожен рівень вкладеності має бути представленим як окремий контейнер з даними, що відповідають певному ключу.
У контейнері можуть зберігатися будь-які дані, масиви з словниками, наприклад, а також єдине значення даних, як-от Int.
Наприклад, щоб витягнути друзів в такому об'єкті:
{"name":"Porky Pig", "id":819263082, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTEQcXKKj5YfDTIkNPcBBNyLrCv7M5V4LiwFh1n8VJy6H5RJrBKew", "friends":[{"name":"Bugs Bunny", "id":825382838, "avatarPath":"https://avatarfiles.alphacoders.com/812/81220.webp"}, {"name":"Daffy Duck", "id":548001360, "avatarPath":"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ7leBgakzSpfs50MeDnkd39Lu_nZpyHIE_0tm3FzovjaGhiF7K"}]}
Нам потрібно взяти
let friendsContainer = try? generalContainer.nestedUnkeyedContainer(forKey: .friends)
контейнер за ключем .friends з основного контейнера.
Насправді, наш новий контейнер буде масивом об'єктів, і для витягування об'єктів нам потрібно пройти через нього.
if var friendsContainer = friendsContainer { var friends:[Character]=[] while!friendsContainer.isAtEnd {if let friend = try friendsContainer.decodeIfPresent(Character.self){ friends.append(friend)}} self.friends =NSSet(array: friends)}
friendsContainer має наступну властивість: isAtEnd. Отже, ми насправді ітеруємо та створюємо об'єкти, поки ця властивість не поверне "true", що означатиме кінець нашого масиву.
Висновок
Протокол Decodable — це надзвичайно потужний інструмент, який нам безкоштовно надає Apple. Дякуємо йому! Єдине, що вам потрібно зробити — це вивчити його, інакше ви не зможете розбирати певні багаторівневі JSON об'єкти.
Шановні колеги, бажаю вам удачі і сподіваюся, що стаття була корисною для вас.