Home > Mobile >  iOS How can I parse JSON codable array with Alamofire
iOS How can I parse JSON codable array with Alamofire

Time:09-05

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
            }
        }
    }

CodePudding user response:

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

CodePudding user response:

The problem with your code is case-sensitive properties. If the API returns these keys Name, Type, Email, Size, Type then the Codable object should have the coding Keys to handle keys from API

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" 
    }
} 

In terms of unwrapping properties, it will depend on the contract between your code and API. If you're pretty sure API will always return size,type, name but email is nullable. Then you should use the method decodeIfPresent allows the value of the parsing key is nullable If you're certain about name, the value is always not null, then you can use decode normally

and your Order struct will become

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" 
    }
} 
  • Related