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