Search code examples
iosalamofire

iOS How can I parse JSON codable array with Alamofire


I'm trying data parsing from server with Alamofire.

(I've been trying for 2 days and it's failing.) How can I get Json array Codable type with Alamofire?...

API :

[ { "name": "John Doe", "email": "[email protected]", "type": "Lattee", "size": "medium" }, { "name": "Doe", "email": "[email protected]", "type": "Lattee", "size": "small" } ]

now this is my code

in Model.swift

struct OrderList : Codable{
    var list : [Order]
}

enum coffeeType: String, Codable{
    case cappuccino
    case lattee
    case espressino
    case cortado
    
}
enum coffeeSize: String, Codable{
    case small
    case medium
    case large
    
    enum CodingKeys: String, CodingKey {
        case small = "s"
        case medium = "m"
        case large = "l"
    }
}
struct Order: Codable {
    let email: String!
    let name : String!
    let size : coffeeSize!
    let type : coffeeType!
    
    enum CodingKeys: String, CodingKey{
        case name = "Name"
        case email = "Email"
        case type  = "Type"
        case size  = "Size"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        email = try values.decodeIfPresent(String.self, forKey: .email) ?? ""
        name  = try values.decodeIfPresent(String.self, forKey: .name)  ?? "Guest"
        size  = try values.decodeIfPresent(coffeeSize.self, forKey: .size) ?? .small
        type  = try values.decodeIfPresent(coffeeType.self, forKey: .type) ?? .lattee
    }
}

struct Resource<T: Codable> {
    let url       : URL
    var httpMethod: HTTPMethod = .get
}

I have defined it in various formats such as responseData, responseJSON, and responseCodable, but I keep getting nil or something is missing. I know how to parse with responseJSON. but I want trying to parse by applying Codable... it's too difficult.

---- data parsing ---


func load<T>(resource: Resource<T>, completion: @escaping (Result<T, NetworkError>) -> Void) {
        let call = AF.request(myurl,method: resource.httpMethod, parameters: nil).responseJSON{ response in
            switch response.result {
            case .success(let data):
                if let JSON = response.value {
                    do{
                       let dataJson = try JSONSerialization.data(withJSONObject: JSON, options: [])
                        let getInstanceData = try JSONDecoder().decode(T.self, from: dataJson)
                        print(getInstanceData)
                        completion(.success(getInstanceData))
                        
                    }catch{
                        print(error)
                    }
                }
            case .failure(_):
                
                break
            }
        }
    }


Solution

  • Since the API returns this payload:

    [ { "name": "John Doe", "email": "[email protected]", "type": "Lattee", "size": "medium" }, { "name": "Doe", "email": "[email protected]", "type": "Lattee", "size": "small" } ]
    

    these are keys that your code needs to handle: name, email, type, size

    Therefore the Order struct should be:

    struct Order: Codable {
        let email: String
        let name : String
        let size : CoffeeSize
        let type : CoffeeType
        
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            email = try values.decodeIfPresent(String.self, forKey: .email) ?? ""
            name  = try values.decodeIfPresent(String.self, forKey: .name)  ?? "Guest"
            size  = try values.decode(CoffeeSize.self, forKey: .size)
            type  = try values.decode(CoffeeType.self, forKey: .type)
        }
    }
    
    enum CoffeeType: String, Codable {
        case cappuccino
        case latte
        case espressino
        case cortado
       
        init(from decoder: Decoder) throws {
            let label = try decoder.singleValueContainer().decode(String.self)
            let lowercaseLabel = label.lowercased()
            self = CoffeeType(rawValue: lowercaseLabel) ?? .latte
        }
    }
    
    enum CoffeeSize: String, Codable {
        case small
        case medium
        case large
    }
    

    since there is no list key in the json structure therefore you don't need struct OrderList. In order to get a list of orders you can simply call

    load(resource<[Order]>) { result in
       // handle the response but now you will get the list of orders
    }
    
    

    Alternatively, I created a playground to load a json file (data.json) locally and parse it into objects you can have a look at the solution below

    func readJsonFile(filename: String) -> String {
        guard let fileUrl = Bundle.main.url(forResource: filename, withExtension: "json") else { fatalError() }
        guard let jsonData = try? String(contentsOf: fileUrl) else {
            return ""
        }
        return jsonData
    }
    
    enum CoffeeType: String, Codable {
        case cappuccino
        case latte
        case espressino
        case cortado
       
        init(from decoder: Decoder) throws {
            let label = try decoder.singleValueContainer().decode(String.self)
            let lowercaseLabel = label.lowercased()
            self = CoffeeType(rawValue: lowercaseLabel) ?? .latte
        }
    }
    
    enum CoffeeSize: String, Codable {
        case small
        case medium
        case large
    }
    
    struct Order: Codable {
        let email: String
        let name : String
        let size : CoffeeSize
        let type : CoffeeType
        
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            email = try values.decodeIfPresent(String.self, forKey: .email) ?? ""
            name  = try values.decodeIfPresent(String.self, forKey: .name)  ?? "Guest"
            size  = try values.decode(CoffeeSize.self, forKey: .size)
            type  = try values.decode(CoffeeType.self, forKey: .type)
        }
    }
    
    func parseJsonFile() {
        let jsonStr = readJsonFile(filename: "data")
        let jsonData: Data = Data(jsonStr.utf8)
        let decoder = JSONDecoder()
        do {
            let orders = try decoder.decode([Order].self, from: jsonData)
            orders.forEach {
                print("\($0.name)" + " - " + "\($0.type.rawValue)" + " - " + "\($0.size.rawValue)")
            }
        } catch {
            print(error.localizedDescription)
        }
    }
    
    parseJsonFile()
    

    Result:

    John Doe - latte - medium
    Doe - latte - small