Saat menggunakan protokol Swift4 dan Codable, saya mendapat masalah berikut - sepertinya tidak ada cara untuk mengizinkan JSONDecoder
melewatkan elemen dalam array. Misalnya, saya memiliki JSON berikut:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
Dan struct Codable :
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Saat mendekode json ini
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Hasil products
kosong. Yang diharapkan, karena fakta bahwa objek kedua di JSON tidak memiliki "points"
kunci, sedangkan struct points
opsional GroceryProduct
.
Pertanyaannya adalah bagaimana saya mengizinkan JSONDecoder
untuk "melewati" objek yang tidak valid?
points
bisa dinyatakan opsional?Jawaban:
Salah satu opsinya adalah menggunakan jenis pembungkus yang mencoba memecahkan kode nilai yang diberikan; menyimpan
nil
jika tidak berhasil:Kami kemudian dapat memecahkan kode array ini, dengan
GroceryProduct
pengisian Anda diBase
placeholder:Kami kemudian menggunakan
.compactMap { $0.base }
untuk menyaringnil
elemen (yang membuat kesalahan saat decoding).Ini akan membuat array perantara
[FailableDecodable<GroceryProduct>]
, yang seharusnya tidak menjadi masalah; namun jika Anda ingin menghindarinya, Anda selalu dapat membuat jenis pembungkus lain yang mendekode dan membuka bungkus setiap elemen dari wadah yang tidak dikunci:Anda kemudian akan memecahkan kode sebagai:
sumber
var container = try decoder.unkeyedContainer()
init(from:) throws
, jadi Swift akan secara otomatis menyebarkan kesalahan kembali ke pemanggil (dalam hal ini decoder, yang akan menyebarkannya kembali keJSONDecoder.decode(_:from:)
panggilan).Saya akan membuat tipe baru
Throwable
, yang dapat membungkus semua tipe yang sesuai denganDecodable
:Untuk mendekode larik
GroceryProduct
(atau lainnyaCollection
):di mana
value
properti yang dihitung diperkenalkan dalam ekstensi padaThrowable
:Saya akan memilih untuk menggunakan
enum
tipe pembungkus (lebih dari aStruct
) karena mungkin berguna untuk melacak kesalahan yang dilemparkan serta indeksnya.Cepat 5
Untuk Swift 5 Pertimbangkan untuk menggunakan mis
Result
enum
Untuk membuka bungkus nilai yang didekodekan, gunakan
get()
metode padaresult
properti:sumber
init
Masalahnya adalah saat melakukan iterasi pada sebuah container, container.currentIndex tidak bertambah sehingga Anda dapat mencoba mendekode lagi dengan tipe yang berbeda.
Karena currentIndex hanya dapat dibaca, solusinya adalah meningkatkannya sendiri dengan berhasil mendekode dummy. Saya mengambil solusi @Hamish, dan menulis pembungkus dengan init khusus.
Masalah ini adalah bug Swift saat ini: https://bugs.swift.org/browse/SR-5953
Solusi yang diposting di sini adalah solusi di salah satu komentar. Saya suka opsi ini karena saya mem-parsing banyak model dengan cara yang sama pada klien jaringan, dan saya ingin solusinya menjadi lokal untuk salah satu objek. Artinya, saya masih ingin yang lainnya dibuang.
Saya menjelaskan lebih baik di github saya https://github.com/phynet/Lossy-array-decode-swift4
sumber
if/else
saya gunakando/catch
di dalamwhile
loop sehingga saya dapat mencatat kesalahanAda dua pilihan:
Deklarasikan semua anggota struct sebagai opsional yang kuncinya bisa hilang
Tulis penginisialisasi kustom untuk menetapkan nilai default dalam
nil
kasus tersebut.sumber
try?
dengandecode
itu lebih baik digunakantry
dengandecodeIfPresent
di opsi kedua. Kita perlu menyetel nilai default hanya jika tidak ada kunci, tidak jika terjadi kegagalan decoding, seperti saat kunci ada, tetapi jenisnya salah.deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000
jadi jika gagal itu hanya akan memasukkan 0000 tetapi masih gagal.decodeIfPresent
ini salahAPI
karena kuncinya memang ada. Gunakando - catch
blok lain . DekodeString
, jika terjadi kesalahan, dekodeInt
Solusi yang dimungkinkan oleh Swift 5.1, menggunakan pembungkus properti:
Dan kemudian penggunaan:
Catatan: Properti wrapper hanya akan berfungsi jika responsnya bisa dibungkus dalam struct (yaitu: bukan array tingkat atas). Dalam hal ini, Anda masih bisa membungkusnya secara manual (dengan typealias agar lebih mudah dibaca):
sumber
Saya telah menempatkan solusi @ sophy-swicz, dengan beberapa modifikasi, menjadi ekstensi yang mudah digunakan
Sebut saja seperti ini
Contoh di atas:
sumber
Sayangnya Swift 4 API tidak memiliki penginisialisasi yang gagal untuk
init(from: Decoder)
.Hanya satu solusi yang saya lihat adalah mengimplementasikan decoding kustom, memberikan nilai default untuk kolom opsional dan kemungkinan filter dengan data yang diperlukan:
sumber
Saya mengalami masalah serupa baru-baru ini, tetapi sedikit berbeda.
Dalam hal ini, jika salah satu elemen di
friendnamesArray
adalah nihil, seluruh objek nihil saat decoding.Dan cara yang tepat untuk menangani kasus tepi ini adalah dengan mendeklarasikan array string
[String]
sebagai array string opsional[String?]
seperti di bawah ini,sumber
Saya meningkatkan @ Hamish untuk kasus ini, bahwa Anda menginginkan perilaku ini untuk semua array:
sumber
@ Jawaban Hamish bagus. Namun, Anda dapat mengurangi
FailableCodableArray
menjadi:sumber
Sebagai gantinya, Anda juga bisa melakukan seperti ini:
dan kemudian saat mendapatkannya:
sumber
Saya datang dengan ini
KeyedDecodingContainer.safelyDecodeArray
yang menyediakan antarmuka sederhana:Perulangan yang berpotensi tak terbatas
while !container.isAtEnd
menjadi perhatian, dan ditangani dengan menggunakanEmptyDecodable
.sumber
Upaya yang jauh lebih sederhana: Mengapa Anda tidak mendeklarasikan poin sebagai opsional atau membuat array berisi elemen opsional
sumber