简体   繁体   中英

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": "johndoe@gmail.com", "type": "Lattee", "size": "medium" }, { "name": "Doe", "email": "doe@gmail.com", "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
            }
        }
    }

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

Since the API returns this payload:

[ { "name": "John Doe", "email": "johndoe@gmail.com", "type": "Lattee", "size": "medium" }, { "name": "Doe", "email": "doe@gmail.com", "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"
        let orginalSize  = try values.decodeIfPresent(String.self, forKey: .size)
        size = CoffeeSize(rawValue: orginalSize ?? CoffeeSize.small.rawValue) ?? .small
        let orginalType  = try values.decodeIfPresent(String.self, forKey: .type)
        type = CoffeeType(rawValue: orginalType ?? CoffeeType.latte.rawValue) ?? .latte
    }
}

enum CoffeeType: String, Codable {
    case cappuccino
    case latte
    case espressino
    case cortado
    
}

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
    
}

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"
        let orginalSize  = try values.decodeIfPresent(String.self, forKey: .size)
        size = CoffeeSize(rawValue: orginalSize ?? CoffeeSize.small.rawValue) ?? .small
        let orginalType  = try values.decodeIfPresent(String.self, forKey: .type)
        type = CoffeeType(rawValue: orginalType ?? CoffeeType.latte.rawValue) ?? .latte
    }
}

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)" + "\n" + "\($0.type.rawValue)" + "\n" + "\($0.size.rawValue)")
        }
    } catch {
        print(error.localizedDescription)
    }
}

parseJsonFile()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM