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