Swift 4 可编码数组

18

我有一个API路由,返回一个JSON对象数组。例如:

[
    {"firstname": "Tom", "lastname": "Smith", "age": 31},
    {"firstname": "Bob", "lastname": "Smith", "age": 28}
]

我正在尝试想象如何使用Swift中的新可编码功能,将它们转换为类中的两个对象。所以如果我有一个可编码的person类,我希望将响应转换为两个person对象。

我还在使用Alamofire处理请求。

我该如何做呢?到目前为止,我所看到的所有与可编码内容相关的东西都只允许1个对象。而且我也没有看到任何与Alamofire或Web框架的集成。


2
你的问题是如何将你提供的JSON转换成Person数组(例如实体)?还是一个异构对象数组? - nathan
1
我知道如果我有{"firstname": "Tom", "lastname": "Smith", "age": 31}和一个人类,我可以使用可编码将JSON转换为Swift中的Person对象。但是,如果我有从Alamofire获取的JSON数组,我不确定该怎么做。 - Charlie Fish
我并不是很熟悉Alamofire(或这个库),但是有一个叫做https://github.com/Otbivnoe/CodableAlamofire的东西。 - Itai Ferber
5个回答

19

关于Alamofire 5的更新: responseJSONDecodable

struct Person: Codable {
    let firstName, lastName: String
    let age: Int

    enum CodingKeys : String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

Alamofire.request(request).responseJSONDecodable { (response: DataResponse<Person>) in
    print(response)
}

目前 Alamofire 4 不支持 Codable(参见 #2177),你可以使用这个扩展来实现: https://github.com/Otbivnoe/CodableAlamofire

let jsonData = """
[
    {"firstname": "Tom", "lastname": "Smith", "age": 31},
    {"firstname": "Bob", "lastname": "Smith", "age": 28}
]
""".data(using: .utf8)!

struct Person: Codable {
    let firstName, lastName: String
    let age: Int

    enum CodingKeys : String, CodingKey {
        case firstName = "firstname"
        case lastName = "lastname"
        case age
    }
}

let decoded = try! JSONDecoder().decode([Person].self, from: jsonData)

示例:http://swift.sandbox.bluemix.net/#/repl/59a4b4fad129044611590820

使用CodableAlamofire:

let decoder = JSONDecoder()
Alamofire.request(url).responseDecodableObject(keyPath: nil, decoder: decoder) { (response: DataResponse<[Person]>) in
    let persons = response.result.value
    print(persons)
}

keypath 对应于 JSON 结构中包含结果的路径。例如:

{
    "result": {
        "persons": [
            {"firstname": "Tom", "lastname": "Smith", "age": 31},
            {"firstname": "Bob", "lastname": "Smith", "age": 28}
        ]
    }
}

keypath => results.persons

[
    {"firstname": "Tom", "lastname": "Smith", "age": 31},
    {"firstname": "Bob", "lastname": "Smith", "age": 28}
]

keypath => nil(空的keypath会抛出异常)


非常详细的回答。非常感谢。现在没有时间完全阅读,但会在某个时候看一下。非常感谢! - Charlie Fish
回答已经通过Alamofire 5代码更新(不需要扩展)。 - nathan

0

为了将解码结果转换成数组,您可以使用类型别名来使结果更清晰:

typealias ServiceResponseObject = [ResponseObject]

但是,您需要将数组确认为可编码的:

extension Array: Decodable where Element: Decodable {}

这样就可以让所有东西正常工作了。


0

我成功地将数据响应序列化为可编码对象。

正如大家所熟悉的,例如将json对象[String: String]转换。该json对象需要通过使用json.data(using: .utf8)!转换为Data

使用Alamofire,很容易获取这些数据(或者至少对于我来说,这种数据已经兼容.utf8),我可以直接使用这个已经可用的函数。

func responseData(queue: DispatchQueue?, completionHandler: @escaping (DataResponse<Data>) -> Void) -> Self

然后只需将该数据用作完成处理程序解码器的输入即可

let objek = try JSONDecoder().decode(T.self, from: data)

你也可以通过一些调整,将其变成通用的序列化函数,具体操作见文档。

通用响应对象序列化

进行如下修改:

func responseCodable<T: Codable>(
    queue: DispatchQueue? = nil,
    completionHandler: @escaping (DataResponse<T>) -> Void)
    -> Self
{
    let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
        guard error == nil else { return .failure(BackendError.network(error: error!)) }

        guard let data = data else {
            return .failure(BackendError.objectSerialization(reason: "data is not valid"))
        }


        do{
            let objek = try JSONDecoder().decode(T.self, from: data!)
            return .success(objek)
        } catch let e {
            return .failure(BackendError.codableSerialization(error: e))
        }

    }

    return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}

样例结构体

struct Fids: Codable {

   var Status: Status?
   var Airport: Airport?
   var Record: [FidsRecord]
}

使用这个函数的方式

    Alamofire.request("http://whatever.com/zzz").responseCodable { (response: DataResponse<Fids>) in
        switch response.result{
        case .success(let value):
            print(value.Airport)
        // MARK: do whatever you want
        case .failure(let error):
            print(error)
            self.showToast(message: error.localizedDescription)
        }
    }

与被接受的答案相比,您的答案有何独特之处? - Charlie Fish
你只需要使用现有函数的数据响应,这样做非常简单。对于那些长期使用Alamofire的人来说,这是一个简单的替代方案,对吧? - abbawssdsad
是的。在做了更多的研究之后,有更好的方法来处理这个问题。我认为添加responseCodable只会增加复杂度。我也认为我的主要问题是关于Swift 4 Codable数组,而不是Alamofire。虽然我提到了Alamofire,但那并不是我的主要问题。 - Charlie Fish

0

使用 Codable 的 Swift 5

Alamofire 通用响应

PersonModel.swift (使用 SwiftyJsonAccelerator 创建)

import Foundation

class PersonModel: Codable {

  enum CodingKeys: String, CodingKey {
    case age
    case firstname
    case lastname   }

  var age: Int?   var firstname: String?   var lastname: String?

  init (age: Int?, firstname: String?, lastname: String?) {
    self.age = age
    self.firstname = firstname
    self.lastname = lastname   }

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    age = try container.decodeIfPresent(Int.self, forKey: .age)
    firstname = try container.decodeIfPresent(String.self, forKey: .firstname)
    lastname = try container.decodeIfPresent(String.self, forKey: .lastname)   }

}

通用获取响应

func genericGET<T: Decodable>(urlString: String, completion: @escaping (T?) -> ()) {

Alamofire.request(urlString)
    .responseJSON { response in
        // check for errors
        switch response.result {
        case .success(_):

            do {
                let obj = try JSONDecoder().decode(T.self, from: response.data!)
                completion(obj)
            } catch let jsonErr {
                print("Failed to decode json:", jsonErr)
            }

            break
        case .failure(_):


            completion(nil)

            break
        }
}

}

调用这个方法

genericGET(urlString: "YOUR_URL") { (persons: [PersonModel]?) in
    print(persons?[0].firstname)
}

0
从 "Сodable" 继承数组中的对象
struct CustomObject: Codable {
    let var1: String
    let var2: Int
}

结果如下:

现在这个数组将可以进行编码:

var objects: [CustomObject] = []

同样,AnotherCustomObject将可以进行编码。
struct AnotherCustomObject: Codable {
    var var1: String
    var var2: String
    var objects: [CustomObject] = []
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接