Ketika mengembangkan aplikasi seluler, dengan satu atau lain cara, kita dihadapkan dengan kebutuhan untuk mengurai data server ke dalam model aplikasi internal. Dalam sebagian besar kasus, data ini datang dalam format JSON. Dimulai dengan Swift 4, alat utama untuk mem-parsing JSON adalah menggunakan protokol Decodable
dan objek JSONDecoder
.
Pendekatan ini sangat menyederhanakan proses penguraian data dan mengurangi jumlah kode boilerplate. Dalam kebanyakan kasus, cukup mudah untuk membuat model dengan properti yang dinamai seperti bidang di objek JSON dan JSONDecoder
akan melakukan sisanya untuk Anda. Kode minimum, manfaat maksimum. Namun, pendekatan ini memiliki satu kelemahan, yaitu, kesetiaan parser yang sangat rendah. Saya akan menjelaskan. Jika ada perbedaan antara model data internal (objek yang dapat didekodekan) dan apa yang muncul di JSON,JSONDecoder
melempar kesalahan dan kami kehilangan seluruh objek. Mungkin, dalam beberapa situasi, model perilaku ini lebih disukai, terutama ketika datang, misalnya, untuk transaksi keuangan. Tetapi dalam banyak kasus akan berguna untuk membuat proses parsing lebih loyal. Dalam artikel ini saya ingin berbagi pengalaman dan berbicara tentang cara utama untuk meningkatkan loyalitas yang sama ini.
Memfilter objek yang tidak valid
Nah, item pertama tentu saja adalah memfilter objek yang tidak valid. Dalam banyak situasi, kami tidak ingin kehilangan seluruh objek jika salah satu objek bersarang tidak valid. Ini berlaku untuk objek tunggal dan array objek. Saya akan memberi contoh. Misalkan kita membuat aplikasi untuk menjual barang dan di salah satu layar kita mendapatkan daftar barang dalam bentuk ini.
{
"products": [
{...},
{...},
....
]
}
, . . , , - . , JSONDecoder
“ ” .
, :
{
"id": 1,
"title": "Awesome product",
"price": 12.2,
"image": {
"id": 1,
"url": "http://image.png",
"thumbnail_url": "http://thumbnail.png"
}
}
, , image
. , , image , image = nil
. , , JSONDecoder
.
, JSONDecoder
2 : decode
decodeIfPresent
. optional
, nil
, , null
. .
, , . , init(decoder)
try?
nil
. , boilerplate . .
struct FailableDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Value.self)
}
}
struct FailableDecodableArray<Value: Decodable>: Decodable {
var wrappedValue: [Value]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [Value] = []
while !container.isAtEnd {
if let element = try? container.decode(Value.self) {
elements.append(element)
}
}
wrappedValue = elements
}
}
.
struct ProductList: Decodable {
var products:FailableDecodableArray<Product>
}
struct Product: Decodable {
let id: Int
let title: String
let price: Double
let image: FailableDecodable<Image>?
}
struct Image: Decodable {
let id: Int
let url: String
let thumbnailUrl: String
}
. , .
let products = productsList.products.wrappedValue
let image = products.first?.image.wrappedValue
FailableDecodableArray
. , RandomAccessCollection
MutableCollection
, wrappedValue
. FailableDecodable
. , , computed property
, , . , .
@propertyWrapper
Swift 5.1 — @propertyWrapper
.
@propertyWrapper
struct FailableDecodable<Value: Decodable>: Decodable {
...
}
@propertyWrapper
struct FailableDecodableArray<Value: Decodable>: Decodable {
...
}
struct ProductList: Decodable {
@FailableDecodableArray
var products:[Product]
}
struct Product: Decodable {
let id: Int
let title: String
let price: Double
@FailableDecodable
let image:Image?
}
, wrappedValue
. , , , , :)
, , optional
.
@FailableDecodable
let image:Image?
let image: FailableDecodable<Image>
, optional
Image?
, wrappedValue
optional
, .
Swift
@FailableDecodable?
let image:Image?
, , JSON nil
. @propertyWrapper
, 100% JSON.
@dynamicMemberLookup
dynamicMemberLookup
.
@dynamicMemberLookup
struct FailableDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
subscript<Prop>(dynamicMember kp: KeyPath<Value, Prop>) -> Prop {
wrappedValue[keyPath: kp]
}
subscript<Prop>(dynamicMember kp: WritableKeyPath<Value, Prop>) -> Prop {
get {
wrappedValue[keyPath: kp]
}
set {
wrappedValue[keyPath: kp] = newValue
}
}
}
2 subscript
, readonly , read/write . .
struct ProductList: Decodable {
var products:FailableDecodableArray<Product>
}
struct Product: Decodable {
let id: Int
let title: String
let price: Double
let image: FailableDecodable<Image>?
}
@propertyWrapper
wrappedValue
, .
let imageURL = products.first?.image.url
, optional
. , , wrappedValue
, , .
products.first?.image.load()
products.first?.image.wrappedValue.load()
( ), .
— . - , ( JSON) Swift , “1” 1 — . JSONDecoder
, , . . , Product
.
{
"id": 1,
"title": "Awesome product",
"price": "12.2",
"image": {
"id": 1,
"url": "http://image.png",
"thumbnail_url": "http://thumbnail.png"
}
}
, , , — , , , , , . , .
struct Convertible<Value: Decodable & LosslessStringConvertible>: Decodable {
var wrappedValue: Value
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
guard let stringValue = try? container.decode(String.self),
let value = Value(stringValue) else {
wrappedValue = try container.decode(Value.self)
return
}
wrappedValue = value
}
}
struct Product: Decodable {
let id: Int
let title: String
let price: Convertible<Double>
let image: FailableDecodable<Image>?
}
, . FailableDecdable
@propertyWrapper
@dynamicMemberLookup
.
( ) . , , API - , , - . , , , , -, , , -, , . , , .
struct StringConvertible: Decodable {
var wrappedValue: String
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
guard let number = try? container.decode(Double.self) else {
wrappedValue = try container.decode(String.self)
return
}
wrappedValue = "\(number)"
}
}
Meringkas semua hal di atas, saya ingin mencatat bahwa kemunculan protokol Decodable
bersama dengan kelas JSONDecoder
secara signifikan menyederhanakan hidup kita dalam apa yang terkait dengan penguraian data server. Namun, perlu dicatat bahwa JSONDecoder
saat ini memiliki loyalitas yang sangat rendah dan untuk meningkatkannya (jika perlu) Anda perlu bekerja sedikit dan menulis beberapa bungkus. Saya berpikir bahwa di masa depan semua fitur ini akan terwujud dalam objek itu sendiri JSONDecoder
, karena relatif baru-baru ini dia bahkan tidak tahu bagaimana mengkonversi kunci dari snakecase ke camelcase dan string to date, dan sekarang semua ini tersedia "di luar kotak".