This question already has answers here:
Closed 2 years ago.
Im trying to get my head around the new Codable feature Apple has added but Im not able to solve this problem.
I have a json output which is like this
{
"Afpak": {
"id": 1,
"race": "hybrid",
"flavors": [
"Earthy",
"Chemical",
"Pine"
],
"effects": {
"positive": [
"Relaxed",
"Hungry",
"Happy",
"Sleepy"
],
"negative": [
"Dizzy"
],
"medical": [
"Depression",
"Insomnia",
"Pain",
"Stress",
"Lack of Appetite"
]
}
}
}
and I have a structure that is like
struct Strain: Codable {
var name: String
var id: Int
var race: String
var flavors: [String]
var effects: [String: [String]]
}
so obviously it will fail because there is no name key inside my json. The name should be "Afpak", I have looked around but most tutorials didnt give any example on how to solve this problem, they just did [String:Strain]
which is not what I need. Is there anyway set the key of my dictionary to my name variable?
You can use a small trick to get around the unknown key issue: create a helper struct for your Strain
struct, make that helper struct Codable
, decode the response as [String:Helper]
, then create a custom initializer that takes 2 input arguments, the name of the strain and a Helper
instance.
This way you can store the name
as a property of Strain
and you won't need an overcomplicated decoding to circumvent the issue of the unknown top level Dictionary
key.
struct Strain {
var name: String
var id: Int
var race: String
var flavors: [String]
var effects: [String: [String]]
init(from helper: StrainHelper, with name:String){
self.name = name
self.id = helper.id
self.race = helper.race
self.flavors = helper.flavors
self.effects = helper.effects
}
}
struct StrainHelper: Codable {
var id: Int
var race: String
var flavors: [String]
var effects: [String: [String]]
}
do {
let strainHelper = try JSONDecoder().decode([String:StrainHelper].self, from: data)
guard let strainName = strainHelper.keys.first else {throw NSError(domain: "No key in dictionary",code: 404)}
let strain = Strain(from: strainHelper[strainName]!, with: strainName)
} catch {
print(error)
}
Just make your name property optional, create a custom initializer to your structure that takes a dictionary and use its first key to initialise your name property:
enum ParseError: Error {
case noKeyFound
}
struct Strain: Codable {
let name: String!
let id: Int
let race: String
let flavors: [String]
let effects: [String: [String]]
init(dictionary: [String: Strain]) throws {
guard
let key = dictionary.keys.first,
let strain = dictionary[key] else { throw ParseError.noKeyFound }
name = key
id = strain.id
race = strain.race
flavors = strain.flavors
effects = strain.effects
}
}
do {
let strain = try Strain(dictionary: JSONDecoder().decode([String:Strain].self, from: data))
print(strain) // Strain(name: Afpak, id: 1, race: "hybrid", flavors: ["Earthy", "Chemical", "Pine"], effects: ["positive": ["Relaxed", "Hungry", "Happy", "Sleepy"], "negative": ["Dizzy"], "medical": ["Depression", "Insomnia", "Pain", "Stress", "Lack of Appetite"]])
} catch {
print(error)
}