I'm using JSONDecoder to decode from a JSON file which has nested dictionaries. It fails to decode from the json data to my customized model.
This is what I have tried in my code.
The JSONDecoder looks like this:
let netWorkManager = NetWorkManager(URL: url, httpMethodType: .GET)
netWorkManager.callAPI { (data, status, error) in
guard let data = data else {
onFail(NetWorkError.otherError)
return
}
switch status {
case 200:
do{
if let responseModel = try JSONDecoder().decode(ResonseModel?.self, from: data) {
onSuccess(responseModel)
}
}catch {
onFail(NetWorkError.otherError)
}
default:
onFail(NetWorkError.otherError)
}
}
The model looks like this:
struct ResonseModel: Codable {
let type : String
let format: String
let data: [String: Champion]
struct Champion: Codable {
let version: String
let id: String
let key: Int
let name: String
let title: String
let blurb: String
}
}
The JSON structure looks like this:
{
"type": "champion",
"format": "standAloneComplex",
"version": "9.3.1",
"data": {
"Aatrox": {
"version": "9.3.1",
"id": "Aatrox",
"key": "266",
"name": "Aatrox",
"title": "the Darkin Blade",
"blurb": "Once honored defenders of Shurima against the Void, Aatrox and his brethren would eventually become an even greater threat to Runeterra, and were defeated only by cunning mortal sorcery. But after centuries of imprisonment, Aatrox was the first to find...",
"info": {
"attack": 8,
"defense": 4,
"magic": 3,
"difficulty": 4
},
"tags": [
"Fighter",
"Tank"
],
"partype": "Blood Well",
},
"Ahri": {
"version": "9.3.1",
"id": "Ahri",
"key": "103",
"name": "Ahri",
"title": "the Nine-Tailed Fox",
"blurb": "Innately connected to the latent power of Runeterra, Ahri is a vastaya who can reshape magic into orbs of raw energy. She revels in toying with her prey by manipulating their emotions before devouring their life essence. Despite her predatory nature...",
"info": {
"attack": 3,
"defense": 4,
"magic": 8,
"difficulty": 5
},
"tags": [
"Mage",
"Assassin"
],
"partype": "Mana",
},
...
this is the link for the JSON if you want to look into it: http://ddragon.leagueoflegends.com/cdn/9.3.1/data/en_US/champion.json
I want to decode the "data" property as a dictionary whose key is the name of the champion and value is the champion. But the jsonDecoder seems doesn't recognize my model structure. It ends up catch the error.
The JSON-parameter "key" is not an Integer.
Change it to String and it will work:
struct ResonseModel: Codable {
let type : String
let format: String
let data: [String: Champion]
struct Champion: Codable {
let version: String
let id: String
let key: String
let name: String
let title: String
let blurb: String
}
}
You can switch to manually decoding Champion in order to clean up the data.
struct ResonseModel: Decodable {
let type : String
let format: String
let data: [String: Champion]
struct Champion: Decodable {
let version: String
let id: String
let key: Int
let name: String
let title: String
let blurb: String
enum CodingKeys: String, CodingKey {
case version, id, key, name, title, blurb
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.version = try container.decode(String.self, forKey: .version)
self.id = try container.decode(String.self, forKey: .id)
guard let key = Int(try container.decode(String.self, forKey: .key)) else {
throw DecodingError.valueNotFound(Int.self,
.init(codingPath: decoder.codingPath,
debugDescription: "Bad value for id"))
}
self.key = key
self.name = try container.decode(String.self, forKey: .name)
self.title = try container.decode(String.self, forKey: .title)
self.blurb = try container.decode(String.self, forKey: .blurb)
}
}
}
This is basically the code that the compiler writes for you; it just converts the string into an int because that's what you really wanted.
Related
Let's suppose this json which represents multilingual words:
[{
"id": "en_cat",
"name": "cat",
"def": "A cat is a domestic animal of the feline family.",
"trans": {
"fr": "fr_chat",
"ru": "ru_ко́шка"
}
}, {
"id": "fr_chat",
"name": "chat",
"def": "Le chat est un animal domestique de la famille des félins.",
"trans": {
"en": "en_cat",
"ru": "ru_ко́шка"
}
}, {
"id": "ru_ко́шка",
"name": "ко́шка",
"def": "..."
"trans": {
"en": "en_cat",
"fr": "fr_chat"
}
}]
This json has items related to each others in the "trans" (translation) nested container.
My class is straight forward
class Word: Decodable {
var id: String
var name: String
var definition: String
var enTranslation: Word?
var frTranslation: Word?
var ruTranslation: Word?
enum JsonCodingKey: String, CodingKey {
case id
case name
case def
case trans
}
enum JsonTransCodingKey: String, CodingKey {
case en
case fr
case ru
}
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: JsonCodingKey.self)
let id = try container.decode(String.self, forKey: .id)
let name = try container.decode(String.self, forKey: .name)
let definition = try container.decode(String.self, forKey: .def)
self.init(id: id, name: name, definition: definition)
// Tricky part here...
let transContainer = try container.nestedContainer(keyedBy: JsonTransCodingKey.self, forKey: .trans)
if let en = transContainer.decode(String.self, forKey: .en) {
self.enTranslation = realm.find(wordId: en) // Singleton that looks into memory for the word
}
// And repeat the same if logic for the other languages...
}
}
What is the fastest (CPU) way to JSON Decode it?
My way of handling it "feels" wrong:
I decode the words using
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data)
But the words don't have any translation linked because they are not "known" during the real-time parsing.
In my example, when we parse the first word "cat", we still don't know the French nor Russian words yet.
So I have to decode again, once I have all the words in memory.
let jsonDecoder = JSONDecoder()
let words = jsonDecoder.decode([Word].self, from: data) // Words don't have their translations
self.saveInMemory(words) // In my case, it is saved to Realm.
let words = jsonDecoder.decode([Word].self, from: data)
/* Words are now linked to each others
Because during decoding, the func Word.init(from decoder) will
look into `Realm` and find the translations. */
This double decoding feels like an overkill. Isn't there anyway to search into the json data directly?
Decode first, generate your structure later. You are trying to combine the two which does not make sense.
Your first decoding does the actual decoding, your second decoding does only the linking.
Instead of that, decode to temporary structures, build a dictionary of identifiers and use that to link them to final objects.
To be honest, there is no need to do the actual linking. It could still be completely dynamic, using a dictionary.
One possible approach:
let data = """
[{
"id": "en_cat",
"name": "cat",
"def": "A cat is a domestic animal of the feline family.",
"trans": {
"fr": "fr_chat",
"ru": "ru_ко́шка"
}
}, {
"id": "fr_chat",
"name": "chat",
"def": "Le chat est un animal domestique de la famille des félins.",
"trans": {
"en": "en_cat",
"ru": "ru_ко́шка"
}
}, {
"id": "ru_ко́шка",
"name": "ко́шка",
"def": "...",
"trans": {
"en": "en_cat",
"fr": "fr_chat"
}
}]
""".data(using: .utf8)!
enum Language: String {
case english = "en"
case french = "fr"
case russian = "ru"
}
class Word: Decodable {
let id: String
let name: String
let definition: String
let translationsIds: [String: String]
weak var parentDictionary: Dictionary!
private enum CodingKeys: String, CodingKey {
case id
case name
case definition = "def"
case translationsIds = "trans"
}
func translation(for language: Language) -> Word? {
return translationsIds[language.rawValue].flatMap { parentDictionary.words[$0] }
}
}
class Dictionary: Decodable {
let words: [String: Word]
required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let words = try container.decode([Word].self)
self.words = [String: Word](uniqueKeysWithValues: words.map { (key: $0.id, value: $0) })
for word in words {
word.parentDictionary = self
}
}
}
let decoder = JSONDecoder()
let dictionary = try decoder.decode(Dictionary.self, from: data)
print(dictionary.words["fr_chat"]?.translation(for: .english)?.name)
During my useless attempts to decode this simple JSON file, I've always got this strange response which I do reckon because of some mistakes I do in my code. I want to rid of these errors in brackets and keep my response clean:
vk_with_server_response.InitialPageViewController.(unknown context at $105a03c18).(unknown context at $105a03dd0)
The full response looks like this:
Response(count: 32363, items: [vk_with_server_response.InitialPageViewController.(unknown context at $105a03c18).(unknown context at $105a03dd0).Inner(first_name: "Sonya", last_name: "Kargina"), vk_with_server_response.InitialPageViewController.(unknown context at $105a03c18).(unknown context at $105a03dd0).Inner(first_name: "Slava", last_name: "Kholod")])
Also, if anyone knows how to do the same by using the "class". I'd be glad to have a look because with classes I have no response at all.
struct Response: Decodable{
let count:Int
let items:[Inner]
enum CodingKeys: String,CodingKey{
case items
case count
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.count = try container.decode(Int.self, forKey: .count)
var innerItems = try container.nestedUnkeyedContainer(forKey: .items)
var arrayInner = [Inner]()
while !innerItems.isAtEnd{
let nestedItems = try innerItems.decode(Inner.self)
arrayInner += [nestedItems]
}
items = arrayInner
}
}
struct Inner: Decodable {
var first_name:String
var last_name:String
enum ListCodingKeys:String,CodingKey{
case first_name
case last_name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ListCodingKeys.self)
self.first_name = try container.decode(String.self, forKey: .first_name)
self.last_name = try container.decode(String.self, forKey: .last_name)
}
}
struct FirstLayerOfResponse: Decodable{
let response: Response
}
//-----------------------
var jsonTest = """
{
"response": {
"count": 32363,
"items": [{
"first_name": "Sonya",
"last_name": "Kargina"
}, {
"first_name": "Slava",
"last_name": "Kholod"
}]
}
}
""".data(using: .utf8)! // our data in native format
do {
let tryJS = try JSONDecoder().decode(FirstLayerOfResponse.self, from: jsonTest)
print(tryJS.response)
} catch {
print(error.localizedDescription)
}
I have a very long Json array that is full of items that look like this:
[
{
"id": "sm10-1",
"name": "Pheromosa & Buzzwole-GX",
"imageUrl": "https://images.pokemontcg.io/sm10/1.png",
"subtype": "TAG TEAM",
"supertype": "Pokémon",
"hp": "260",
"retreatCost": [
"Colorless",
"Colorless"
],
"convertedRetreatCost": 2,
"number": "1",
"artist": "Mitsuhiro Arita",
"rarity": "Rare Holo GX",
"series": "Sun & Moon",
"set": "Unbroken Bonds",
"setCode": "sm10",
"text": [
"When your TAG TEAM is knocked out, your opponent takes 3 Prize Cards."
],
"types": [
"Grass"
],
"attacks": [
{
"name": "Jet Punch",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "30",
"text": "This attack does 30 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)"
},
{
"name": "Elegant Sole",
"cost": [
"Grass",
"Grass",
"Colorless"
],
"convertedEnergyCost": 3,
"damage": "190",
"text": "During your next turn, this Pokémon's Elegant Sole attack's base damage is 60."
},
{
"name": "Beast Game-GX",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "50",
"text": "If your opponent's Pokémon is Knocked Out by damage from this attack, take 1 more Prize card. If this Pokémon has at least 7 extra Energy attached to it (in addition to this attack's cost), take 3 more Prize cards instead. (You can't use more than 1 GX attack in a game.)"
}
],
"weaknesses": [
{
"type": "Fire",
"value": "×2"
}
],
"imageUrlHiRes": "https://images.pokemontcg.io/sm10/1_hires.png",
"nationalPokedexNumber": 794
}
]
That is just one item of hundreds in the array. What I want to do is grab specific values from each item (i.e. name, imageUrl, supertype, hp, rarity, set) and send them to a struct which will then be added to an array of such structs.
What I currently have prints just prints out all of the json data and I can not figure out how to get individual data and create an array of structs for each individual card.
Here is the code I have currently:
//[TEST] READING JSON FILE LOCALLY
struct card: Decodable {
let name: String
let imageUrl: String
let supertype: String
let artist: String
let rarity: String
let set: String
let types: Array<String>
}
func loadJsonInfo() {
do{
let data = try Data.init(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json")!))
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
} catch {
print(error)
}
}
Also, the json file is locally stored in my appData. Thanks in advance for your help!
Give a try to https://quicktype.io/
You put json there. And get all necessary data structures to decode json
To decode a JSON with Decodable type, you need to use JSONDecoder's decode(_:from:) method.
Update your loadJsonInfo() method to,
func loadJsonInfo() {
if let file = Bundle.main.url(forResource: "Unbroken Bonds", withExtension: "json") {
do {
let data = try Data(contentsOf: file)
let arr = try JSONDecoder().decode([Card].self, from: data)
print(arr)
} catch {
print(error)
}
}
}
Note: Use first letter capital while creating a type, i.e. use Card instead of card.
Parsing Code when you have DATA from server. I also removed force unwrapped ! so it won't crash in absence of file
func loadJsonInfo() {
if let path = Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
let result = try JSONDecoder().decode(ResultElement.self, from: data)
} catch let error {
print(error)
}
}
}
Your Model
import Foundation
// MARK: - ResultElement
struct ResultElement: Codable {
let id, name: String?
let imageURL: String?
let subtype, supertype, hp: String?
let retreatCost: [String]?
let convertedRetreatCost: Int?
let number, artist, rarity, series: String?
let resultSet, setCode: String?
let text, types: [String]?
let attacks: [Attack]?
let weaknesses: [Weakness]?
let imageURLHiRes: String?
let nationalPokedexNumber: Int?
enum CodingKeys: String, CodingKey {
case id, name
case imageURL = "imageUrl"
case subtype, supertype, hp, retreatCost, convertedRetreatCost, number, artist, rarity, series
case resultSet = "set"
case setCode, text, types, attacks, weaknesses
case imageURLHiRes = "imageUrlHiRes"
case nationalPokedexNumber
}
}
// MARK: - Attack
struct Attack: Codable {
let name: String?
let cost: [String]?
let convertedEnergyCost: Int?
let damage, text: String?
}
// MARK: - Weakness
struct Weakness: Codable {
let type, value: String?
}
typealias Result = [ResultElement]
i have 3 objects (nationalities, services and packages) and i got them from a JSON response as 3 arrays of these objects.. and i want to parse them from the JSON response ...
Classes are:
struct Root2 : Decodable {
let nationalities : [Nationalities]
let services : [Services]
let packages : [Packages]
}
struct Packages : Decodable {
let id: Int
let name: String
let idService: Int
let nationality: String
let totalPrice: Int
let visitsPerWeek: Int
let excludedDays: String
let excludedShifts: String
let excludedDates: String
let extraVisits: Int
let dateEnabled: String
let dateDisabled: String
let allowedFrom: String
let allowedTo: String
let visitType: String
let createdAt: String?
let updatedAt: String?
}
struct Nationalities : Decodable{
let id: Int
let name: String
let createdAt: String?
let updatedAt: String?
}
struct Services : Decodable{
let id: Int
let name, description: String
let createdAt: String?
let updatedAt: String?
}
and i have for each one another class like:
class Service : NSObject, NSCoding {
var id: Int
var name, desc: String
var createdAt: String?
var updatedAt: String?
init(id: Int, name: String, desc: String) {
self.id = id
self.name = name
self.desc = desc
}
required convenience init(coder aDecoder: NSCoder) {
let id = aDecoder.decodeInteger(forKey: "id")
let name = aDecoder.decodeObject(forKey: "name") as! String
let desc = aDecoder.decodeObject(forKey: "desc") as! String
self.init(id: id, name: name, desc: desc)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(name, forKey: "name")
aCoder.encode(desc, forKey: "desc")
}
}
and i'm using it like this:
func GetServicesPackagesNationalities(){
let link: String = "my link"
guard let url = URL(string: link) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard error == nil else {
print("error calling GET on /public/api/services")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder .decode(Root2.self, from: responseData)
var nationalities = [Nationality]()
for nationality in result.nationalities{
let newnationality = Nationality(id: nationality.id, name: nationality.name)
nationalities.append(newnationality)
print(newnationality.name)
}
var services = [Service]()
for service in result.services {
let newservice = Service(id: service.id, name: service.name, desc: service.description)
services.append(newservice)
print(newservice.name)
}
var packages = [Package]()
for package in result.packages{
let newpackage = Package(id: package.id, name: package.name, idService: package.idService, nationality: package.nationality, totalPrice: package.totalPrice, visitsPerWeek: package.visitsPerWeek, excludedDays: package.excludedDays, excludedShifts: package.excludedShifts, excludedDates: package.excludedDates, extraVisits: package.extraVisits ,dateEnabled: package.dateEnabled , dateDisabled: package.dateDisabled, allowedFrom: package.allowedFrom, allowedTo: package.allowedTo ,visitType: package.visitType)
packages.append(newpackage)
print(newpackage.name)
}
}catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
and i will always get this:
error trying to convert data to JSON
and my json is:
{
"nationalities": [
{
"id": 1,
"name": "Saudi Arabia",
"created_at": "2018-04-24 05:50:41",
"updated_at": "2018-04-24 06:35:29",
"deleted_at": null
},
{
"id": 2,
"name": "Bahrain",
"created_at": "2018-04-24 05:52:52",
"updated_at": "2018-04-24 05:52:52",
"deleted_at": null
},
],
"services": [
{
"id": 1,
"name": "Nad",
"description": "Nad ",
"created_at": null,
"updated_at": null,
"deleted_at": null
},
{
"id": 2,
"name": "Reay",
"description": "Re ",
"created_at": null,
"updated_at": null,
"deleted_at": null
},
],
"packages": [
{
"id": 1,
"name": "Gold Package",
"id_service": 1,
"nationality": "4",
"total_price": 1000,
"visits_per_week": 2,
"excluded_weekdays": "null",
"excluded_shifts": "null",
"excluded_dates": "1111-11-11",
"extra_visits": 1,
"date_enabled_from": "2018-04-01",
"date_enabled_to": "2018-04-30",
"date_allowed_from": "2018-04-05",
"date_allowed_to": "2018-04-30",
"visit_type": "Multi",
"created_at": "2018-04-26 11:18:45",
"updated_at": "2018-04-26 11:18:45",
"deleted_at": null
}
]
}
i don't know what i'm doing wrong .. since i tried the same with two objects and worked fine ..
can someone please tell me what im doing wrong?
using them in userdefaults:
storing:
let userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: services)
userDefaults.set(encodedData, forKey: "services")
userDefaults.synchronize()
retrieving:
let decoded = userDefaults.object(forKey: "services") as! Data
let decodedService = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! [Service]
for service in decodedService {
print(service.name)
}
Please never ever print a meaningless literal string in a catch clause, print always the actual error, it tells you exactly what's wrong
} catch {
print(error)
}
In Package there are a lot of issues, here are the error messages (and the suggestions to fix them):
"No value associated with key CodingKeys(stringValue: \"excludedDays\", converted to excluded_days."
There is no key excluded_days in the JSON. Delete the property or declare it as optional (String?)
"No value associated with key CodingKeys(stringValue: \"dateEnabled\" converted to date_enabled."
The actual snake_case converted property is supposed to be dateEnabledFrom
"No value associated with key CodingKeys(stringValue: \"dateDisabled\" converted to date_disabled."
There is no key date_disabled in the JSON. Delete the property or declare it as optional (String?)
"No value associated with key CodingKeys(stringValue: \"allowedFrom\", intValue: nil) (\"allowedFrom\"), converted to allowed_from."
The actual snake_case converted property is supposed to be dateAllowedFrom
"No value associated with key CodingKeys(stringValue: \"allowedTo\", intValue: nil) (\"allowedTo\"), converted to allowed_to."
The actual snake_case converted property is supposed to be dateAllowedTo
Note:
Why do you use another extra classes for Nationality, Service, Package? They seem to be redundant. If you really need reference semantics decode the JSON into classes.
See the difference between Nationality and Nationalities
You should use Nationalities to parse your JSON response.
You should do like this, as you have declare Nationalities object in Root2
var nationalities = [Nationalities]()
for nationality in result.nationalities{
let newnationality = Nationalities(id: nationality.id, name: nationality.name)
nationalities.append(newnationality)
print(newnationality.name)
}
same for Services and Packages, Please find it.
I have the following response from a JSON query. How do I represent a dictionary as codable? I have shortened the JSON response to save space.
{
"result":[
{
"delivery_address": "",
"made_sla": "true",
"watch_list": "",
"upon_reject": "cancel",
"location": {
"link": "https://foo.com/api/now/table/cmn_location/753ac76f4fd9320066bfb63ca310c79b",
"value": "753ac76f4fd9320066bfb63ca310c79b"
}
}
]
}
struct ResultList : Codable {
let result: [Result]
}
struct Result : Codable {
let delivery_address: String
let made_sla: String
let watch_list: String
let upon_reject: String
let location: Location
}
struct Location: Codable {
let link: String?
let value: String?
}
let decoder = JSONDecoder()
do {
let todo = try decoder.decode(ResultList.self, from: responseData)
print("todo \(todo)")
} catch {
print("error trying to convert data to JSON")
print(error)
}
I'm getting the following error:
"Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))
Based on all the comments, I believe the JSON you're actually decoding looks more like this:
{
"result": [{
"delivery_address": "",
"made_sla": "true",
"watch_list": "",
"upon_reject": "cancel",
"location": {
"link": "https://foo.com/api/now/table/cmn_location/753ac76f4fd9320066bfb63ca310c79b",
"value": "753ac76f4fd9320066bfb63ca310c79b"
}
},
{
"delivery_address": "",
"made_sla": "true",
"watch_list": "",
"upon_reject": "cancel",
"location": ""
}
]
}
So there are some records that have a location, and some that encode location as an empty string. Basically this is really messed up JSON on a whole lot of levels and whatever code generates it should be fixed. The bad news I'm sure that won't happen. The good news is we can fix it locally at least.
We're going to have to decode this by hand, so we might as well clean up all the rest of the mess while we're in here. The first thing is that the names don't match Swift naming conventions. We can fix that using CodingKeys. We can also provide real types (Bool and a Rejection enum) for the things that are currently mis-typed strings.
enum Rejection: String, Codable {
case cancel
}
struct Result : Codable {
let deliveryAddress: String
let madeSLA: Bool
let watchList: String
let uponReject: Rejection
let location: Location?
private enum CodingKeys: String, CodingKey {
case deliveryAddress = "delivery_address"
case madeSLA = "made_sla"
case watchList = "watch_list"
case uponReject = "upon_reject"
case location
}
}
Now we just need to be able to decode it. Notice that I made Location optional. Clearly it sometimes doesn't exist, so you either need a default value or it needs to be optional. I chose the latter. Decoding all of this is very straight forward:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
deliveryAddress = try container.decode(String.self, forKey: .deliveryAddress)
madeSLA = try container.decode(String.self, forKey: .madeSLA) == "true"
watchList = try container.decode(String.self, forKey: .watchList)
uponReject = try container.decode(Rejection.self, forKey: .uponReject)
location = try? container.decode(Location.self, forKey: .location)
}
The last line is your actual question. It just says if we can't decode it as a Location, set it to nil. We could be stricter here and try first to decode it as a Location, and then as a String, and then check if the String is empty, but it feels reasonable to use nil here for any decoding failure.
Assuming your JSON is (please note the missing closing brace)
{
"result": [
{
"delivery_address": "",
"made_sla": "true",
"watch_list": "",
"upon_reject": "cancel",
"location": {
"link": "https://blah/blah/foo",
"value": "fsfdfr32r34rwfwffas"
}
}
]
}
You can decode these structs
struct Root : Decodable {
let result : [Result]
struct Result : Decodable {
let location: Location
}
}
struct Location: Decodable {
let link: String
let value: String
}
with
JSONDecoder().decode(Root.self, from: data)