decode json from http get request

I'm still a beginner in Swift and in programming. And I don't have any experience with JSON. Now I tried to make a App on my own. I tried my Code first in Playgrounds but errors occured and I don't know why 😀

So first here is my Code:


import UIKit
import Foundation

struct MonsterInfo : Decodable {
    var id : Int
    var name: String
    var type: String
    var elements: [String]
    var ailments: [String]?
    var locations: [String]
    var resistances: [String]
    var weaknesses: [String]
   
}
func getMonsterInfoAPI(id: Int) {
   
   
    let url = URL(string: "https://mhw-db.com/monsters/\(id)")!
   
    let session = URLSession.shared
   
    let task = session.dataTask(with: url, completionHandler: {data, response, error in
      

        do {
            let json = try? JSONSerialization.jsonObject(with: data!, options: [])
//            print(json)
        } catch {
            print("JSON error: \(error.localizedDescription)")
        }
        let decoder = JSONDecoder()
        let temp = try! decoder.decode(MonsterInfo.self, from: data!)
        print(temp.name)
        print(temp.locations)
    })
   
    task.resume()
}

getMonsterInfoAPI(id: 4)



The problem is: If i use print(temp.name) it works but on the other propertys I declared in the Struct it always says "

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "ailments", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil)): file MyPlayground.playground, line 34"


If I now change the types of the MonsterInfo ailments to dictionary like this...

struct MonsterInfo : Decodable {
var id : Int
    var name: String
    var type: String
    var elements: [String]
    var ailments: [String:Int]
    var locations: [String]
    var resistances: [String]
    var weaknesses: [String]
}

the follwing error occurs:

Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Swift.Int>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "ailments", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Int> but found an array instead.", underlyingError: nil)): file MyPlayground.playground, line 32


Why does this error occurs? I'm confused first it says "...but dictionary found" and then "...but found array"

How to achieve my aim to get from all "propertys" in my struct the right value even if it is null

The property ailments is an Array of Dictionaries, but the values of the dictionary are not always the same type:


id = Int

name = String

description = String

recovery = Dictionary


So var ailments should be of type Any, but 'Any' does not conform to Decodable, so you must implement your custom parsing.


Here you will find how to do it:


h ttps://medium.com/grand-parade/parsing-fields-in-codable-structs-that-can-be-of-any-json-type-e0283d5edb


Also, in your code:


- If you import UIKit, you do not need to import Foundation


- You could use let for the properties instead of var


- Also the JSON not always return the same keys, for instance for:


h ttps://mhw-db.com/monsters/5


The key recovery is not in the JSON o you need to use decodeIfPresent instead of decode, and use an optional var property.

Read the docs:

docs.mhw-db.com


The value for `ailments` is an Array<Ailment> and Ailment is not a String.

Location, MonsterResistance, MonsterWeakness...none of them are String.


So, your `MonsterInfo` should be something like this:

struct MonsterInfo : Decodable {
    var id : Int
    var name: String
    var type: String
    var elements: [String] //ElementType is String
    var ailments: [Ailment]?
    var locations: [Location]
//    var resistances: [MonsterResistance]
//    var weaknesses: [MonsterWeakness]
}


And declare another struct `Ailment` like this:

struct Ailment: Decodable {
    var id: Int
    var name: String
    var description: String
    var recovery: Recovery
//    var protection: Protection
}

And then `Recovery`:

struct Recovery: Decodable {
    var actions: [String] //RecoveryAction is String
    var items: [Item]
}

So, on... Location, Item, ... Define all the structs you want to retrieve.


You can use `JSONSerialization` if you would not like to define so many structs.

import UIKit

func getMonsterInfoAPI(id: Int) {
    let url = URL(string: "https://mhw-db.com/monsters/\(id)")!
    let session = URLSession.shared
    let task = session.dataTask(with: url, completionHandler: {data, response, error in
        if let error = error {
            print(error)
            return
        }
        guard let data = data else {
            print("data is nil")
            return
        }
        do {
            guard let monsterInfo = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
                print("monsterInfo is not a JSON object")
                return
            }
            if let name = monsterInfo["name"] as? String {
                print("name=", name)
            }
            if let locations = monsterInfo["locations"] as? [[String: Any]] {
                print("locations=", locations)
            }
        } catch {
            print(error)
        }
    })
    
    task.resume()
}
getMonsterInfoAPI(id: 4)
decode json from http get request
 
 
Q