@lorem ipsum's method should work I didn't try it myself with swiftUI, however it feels a bit convoluted to deal with 2 different types of object. Eventhough they share a common protocol, since it's the same object that will be decoded, it seems natural to keep track of one single type.
As stated by @Larme it can be done with a custom init(from decoder: Decoder)
method.
import UIKit
let jsonA = """
[
{
"id": 45,
"chapter__book__name": "Alonso",
"chapter__book__id": 70,
"chapter__chapter": 2,
"verse": "",
"verse_number": 5,
"chapter": 97
},
]
"""
let jsonB = """
[
{
"id": 962,
"book_name": "Title here",
"book_id": 70,
"chapter_number": 32,
"verse": "xxx",
"verse_number": 24,
"chapter": 127
},
]
"""
protocol VerseCodingKey: CodingKey {
static var id: Self { get }
static var book_name: Self { get }
static var book_id: Self { get }
static var verse: Self { get }
static var verse_number: Self { get }
static var chapter: Self { get }
static var chapter_number: Self { get }
}
struct Verse: Decodable {
var id: Int
var book_name: String
var book_id: Int
var verse: String
var verse_number: Int
var chapter: Int
var chapter_number: Int
enum CodingKeysA: String, VerseCodingKey {
case id
case book_name
case book_id
case verse
case verse_number
case chapter
case chapter_number
}
enum CodingKeysB: String, VerseCodingKey {
case id
case book_name = "chapter__book__name"
case book_id = "chapter__book__id"
case verse
case verse_number
case chapter = "chapter__chapter"
case chapter_number = "chapter"
}
init(from decoder: Decoder) throws {
do {
try self.init(from: decoder, verseCodingKey: CodingKeysA.self)
return
} catch { }
do {
try self.init(from: decoder, verseCodingKey: CodingKeysB.self)
return
} catch { }
throw CustomError.unmatchedCodingKeys
}
init<T: VerseCodingKey>(from decoder: Decoder, verseCodingKey: T.Type) throws {
do {
let values = try decoder.container(keyedBy: T.self)
id = try values.decode(Int.self, forKey: .id)
book_name = try values.decode(String.self, forKey: .book_name)
book_id = try values.decode(Int.self, forKey: .book_id)
verse = try values.decode(String.self, forKey: .verse)
verse_number = try values.decode(Int.self, forKey: .verse_number)
chapter = try values.decode(Int.self, forKey: .chapter)
chapter_number = try values.decode(Int.self, forKey: .chapter_number)
} catch {
throw CustomError.missingCodingKey
}
}
}
enum CustomError: Error {
case missingCodingKey
case unmatchedCodingKeys
}
let dataA = jsonA.data(using: .utf8)!
let dataB = jsonB.data(using: .utf8)!
let verseA = try? JSONDecoder().decode([Verse].self, from: dataA)
let verseB = try? JSONDecoder().decode([Verse].self, from: dataB)
This code works on playground
SideNotes:
The whole point is to juggle with two different CodingKey
s.
since this evolution it is now feasible to make an enum conform to protocols, which I didn't now of before diving into your issue. This makes the code more straightforward and reusable.
There may be a better way to handle the do catch
mechanism but it's acceptable at this point. as stated by @Cristik in comment, you should enhance the error handling mechanism because you don't want to let all the error going through. see his comment below
This is how far I could get with this little experiment, I reckon someone will be able to do better. It still seem more reliable to use a single concrete class instead of two plus a protocol, but again, I'm not pretending to be an expert.