Swift 4 menambahkan Codable
protokol baru . Ketika saya menggunakannya JSONDecoder
, tampaknya memerlukan semua properti non-opsional Codable
kelas saya untuk memiliki kunci di JSON atau itu membuat kesalahan.
Membuat setiap properti kelas saya opsional sepertinya merepotkan yang tidak perlu karena yang saya inginkan adalah menggunakan nilai di json atau nilai default. (Saya tidak ingin properti menjadi nihil.)
Apakah ada cara untuk melakukan ini?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
Jawaban:
Pendekatan yang saya sukai adalah menggunakan apa yang disebut DTO - objek transfer data. Ini adalah struct, yang sesuai dengan Codable dan mewakili objek yang diinginkan.
Kemudian Anda cukup memasukkan objek yang ingin Anda gunakan dalam aplikasi dengan DTO itu.
Pendekatan ini juga bagus karena Anda dapat mengganti nama dan mengubah objek akhir sesuka Anda. Jelas dan membutuhkan lebih sedikit kode daripada decoding manual. Selain itu, dengan pendekatan ini Anda dapat memisahkan lapisan jaringan dari aplikasi lain.
sumber
Anda dapat mengimplementasikan
init(from decoder: Decoder)
metode dalam tipe Anda daripada menggunakan implementasi default:Anda juga dapat membuat
name
properti konstan (jika Anda mau):atau
Re komentar Anda: Dengan ekstensi khusus
Anda bisa mengimplementasikan metode init sebagai
tapi itu tidak lebih pendek dari
sumber
CodingKeys
pencacahan yang dibuat secara otomatis (sehingga dapat menghapus definisi kustom) :)ObjectMapper
menangani ini dengan sangat baik.Decodable
dan juga menyediakan implementasi Anda sendiriinit(from:)
? Dalam hal ini kompilator menganggap Anda ingin menangani sendiri decoding secara manual dan oleh karena itu tidak mensintesisCodingKeys
enum untuk Anda. Seperti yang Anda katakan, menyesuaikan keCodable
malah berfungsi karena sekarang kompilator sedang melakukan sintesisencode(to:)
untuk Anda dan begitu juga sintesisCodingKeys
. Jika Anda juga menyediakan implementasi Anda sendiriencode(to:)
,CodingKeys
tidak akan lagi disintesis.Salah satu solusinya adalah menggunakan properti yang dihitung secara default ke nilai yang diinginkan jika kunci JSON tidak ditemukan. Ini menambahkan beberapa verbositas ekstra karena Anda harus mendeklarasikan properti lain, dan akan memerlukan penambahan
CodingKeys
enum (jika belum ada). Keuntungannya adalah Anda tidak perlu menulis kode decoding / encoding kustom.Sebagai contoh:
sumber
Anda bisa menerapkan.
sumber
Jika Anda tidak ingin menerapkan metode encoding dan decoding, ada solusi yang agak kotor di sekitar nilai default.
Anda dapat mendeklarasikan kolom baru Anda sebagai opsional yang tidak terbungkus secara implisit dan memeriksa apakah nilainya nihil setelah decoding dan menyetel nilai default.
Saya menguji ini hanya dengan PropertyListEncoder, tetapi saya pikir JSONDecoder bekerja dengan cara yang sama.
sumber
Saya menemukan pertanyaan ini mencari hal yang persis sama. Jawaban yang saya temukan tidak terlalu memuaskan meskipun saya takut solusi di sini akan menjadi satu-satunya pilihan.
Dalam kasus saya, membuat decoder khusus akan membutuhkan satu ton boilerplate yang akan sulit dipertahankan, jadi saya terus mencari jawaban lain.
Saya menemukan artikel ini yang menunjukkan cara menarik untuk mengatasi hal ini dalam kasus sederhana menggunakan file
@propertyWrapper
. Hal terpenting bagi saya, adalah dapat digunakan kembali dan memerlukan pemfaktoran ulang kode yang ada.Artikel ini mengasumsikan kasus di mana Anda ingin properti boolean yang hilang menjadi default ke false tanpa gagal, tetapi juga menampilkan varian lain yang berbeda. Anda dapat membacanya lebih detail tetapi saya akan menunjukkan apa yang saya lakukan untuk kasus penggunaan saya.
Dalam kasus saya, saya memiliki file
array
yang saya ingin diinisialisasi sebagai kosong jika kuncinya hilang.Jadi, saya menyatakan
@propertyWrapper
ekstensi berikut dan tambahannya:Keuntungan dari metode ini adalah Anda dapat dengan mudah mengatasi masalah dalam kode yang ada hanya dengan menambahkan
@propertyWrapper
ke properti. Dalam kasus saya:Semoga ini bisa membantu seseorang yang menghadapi masalah yang sama.
MEMPERBARUI:
Setelah memposting jawaban ini sambil terus menyelidiki masalah ini, saya menemukan artikel lain ini tetapi yang paling penting perpustakaan masing-masing yang berisi beberapa umum yang mudah digunakan
@propertyWrapper
untuk kasus-kasus seperti ini:https://github.com/marksands/BetterCodable
sumber
Jika Anda berpikir bahwa menulis versi Anda sendiri
init(from decoder: Decoder)
terlalu banyak, saya akan menyarankan Anda untuk menerapkan metode yang akan memeriksa input sebelum mengirimkannya ke decoder. Dengan begitu, Anda akan memiliki tempat untuk memeriksa ketidakhadiran bidang dan menetapkan nilai default Anda sendiri.Sebagai contoh:
Dan untuk melakukan init objek dari json, sebagai ganti:
Init akan terlihat seperti ini:
Dalam situasi khusus ini saya lebih suka berurusan dengan pilihan tetapi jika Anda memiliki pendapat berbeda, Anda dapat membuat metode customDecode (:) Anda dapat dibuang
sumber