如何在 Swift 和 Alamofire 中解码来自 Laravel 的请求

How to decodable request from Laravel in Swift and Alamofire

我对 laravel api 请求中的“产品”的解码有疑问。错误是“无法读取数据,因为它的格式不正确。

失眠的要求是正确的,我得到了 ean 号码的产品。

Laravel 数据库:

Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->bigInteger('ean');
        $table->string('name');
        $table->string('manufacturer');
        $table->string('productNumber');
        $table->longText('description')->nullable();
        $table->string('propertie')->nullable();
        $table->string('storagePlace');
        $table->integer('inStock')->nullable();
        $table->integer('unit');
        $table->longText('note')->nullable();
        $table->string('department')->default("zvk");
        $table->timestamps();

Swift 产品可解码:

class Product: Decodable {

var id: Int
var ean: Double
var name: String
var manufacturer: String
var productNumber: String
var description: String
var propertie: String
var storagePlace: String
var inStock: Int
var unit: Int
var note: String
var department: String
var created_at: Date
var updated_at: Date

enum CodingKeys: String, CodingKey {

    case id
    case ean
    case name
    case manufacturer
    case productNumber
    case description
    case propertie
    case storagePlace
    case inStock
    case unit
    case note
    case department
    case created_at
    case updated_at
}

public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.id = try container.decode(Int.self, forKey: .id)
    self.ean = try container.decode(Double.self, forKey: .ean)
    self.name = try container.decode(String.self, forKey: .name)
    self.manufacturer = try container.decode(String.self, forKey: .manufacturer)
    self.productNumber = try container.decode(String.self, forKey: .productNumber)
    self.description = try container.decode(String.self, forKey: .description)
    self.propertie = try container.decode(String.self, forKey: .propertie)
    self.storagePlace = try container.decode(String.self, forKey: .storagePlace)
    self.inStock = try container.decode(Int.self, forKey: .inStock)
    self.unit = try container.decode(Int.self, forKey: .unit)
    self.note = try container.decode(String.self, forKey: .note)
    self.department = try container.decode(String.self, forKey: .department)
    self.created_at = try container.decode(Date.self, forKey: .created_at)
    self.updated_at = try container.decode(Date.self, forKey: .updated_at)
}

}

Swift 短代码:

AF.request(productWithEanUrlRequest, method: .get, parameters: parameters, headers: headers).responseJSON { response in
        ProgressHUD.dismiss()
        
        //Gets HTTP status code
        let statusCode = (response.response?.statusCode)
        
        switch statusCode {
        case 200:
            //OK
            do {
                let product = try JSONDecoder().decode([Product].self, from: response.data!)
                debugPrint(product)
            } catch let error as NSError {
                AlertView.showAlertView(with: "Error", and: error.localizedDescription, in: self)
            }

请求预览:

{
"product": [
    {
        "id": 1,
        "ean": "1234567890",
        "name": "Name",
        "manufacturer": "Hersteller",
        "productNumber": "Artikel-Nr.",
        "description": "Beschreibung vom Artikel",
        "propertie": "Eigenschaften",
        "storagePlace": "Lagerplatz",
        "inStock": "12",
        "unit": "0",
        "note": "Irgendwelche Notiz",
        "department": "zvs",
        "created_at": "2022-01-12T10:20:51.000000Z",
        "updated_at": "2022-01-12T10:20:51.000000Z"
    }
]

我看不到我犯的错误!?

有没有可能你没有设置接受header?据我了解,您期​​待的是 json 响应,但它表示类似:响应格式无效。我建议您尝试在请求中使用 accept header 。本质上是这样的:

headers: {
    "Accept": "application/json"
}

当然要根据您使用的语言转换语法。

我的 http headers 看起来像这样:

let headers: HTTPHeaders = [
        .authorization(bearerToken: userDefaults.string(forKey: "access_token")!),
        .accept("application/json")
    ]

我在 laravel 中发送找到的产品是这样的:

$product = Product::where('ean', $request['ean'])->get();
return response()->json([
        'product' => $product
    ]);

我已尝试更改 swift 中 class 产品的类型:

class Product: Decodable, Identifiable {

var id: Int
var ean: Double
var name: String
var manufacturer: String
var productNumber: String
var description: String
var propertie: String
var storagePlace: String
var inStock: Int
var unit: Int
var note: String
var department: String
var created_at: Date
var updated_at: Date

enum CodingKeys: String, CodingKey {

    case id
    case ean
    case name
    case manufacturer
    case productNumber
    case description
    case propertie
    case storagePlace
    case inStock
    case unit
    case note
    case department
    case created_at
    case updated_at
}

public required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.id = try container.decode(Int.self, forKey: .id)
    self.ean = try container.decode(Double.self, forKey: .ean)
    self.name = try container.decode(String.self, forKey: .name)
    self.manufacturer = try container.decode(String.self, forKey: .manufacturer)
    self.productNumber = try container.decode(String.self, forKey: .productNumber)
    self.description = try container.decode(String.self, forKey: .description)
    self.propertie = try container.decode(String.self, forKey: .propertie)
    self.storagePlace = try container.decode(String.self, forKey: .storagePlace)
    self.inStock = try container.decode(Int.self, forKey: .inStock)
    self.unit = try container.decode(Int.self, forKey: .unit)
    self.note = try container.decode(String.self, forKey: .note)
    self.department = try container.decode(String.self, forKey: .department)
    self.created_at = try container.decode(Date.self, forKey: .created_at)
    self.updated_at = try container.decode(Date.self, forKey: .updated_at)
}

我在终端中从 error.debugDescription 打印调试信息:

Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: \"Expected to decode Array<Any> but found a dictionary instead.\", underlyingError: nil))

我理解错误是这样的:你解码了一个数组但是你有一个字典。 但是我从 laravel 发送一个 json 并解码一个 json.

不要用error.localizedError,用error,打印出来,会有更多有用的信息。你会在我一步一步做的答案中看到它。

您忽略了一个事实,即您的 JSON 是顶级词典,它是 {"product": [Product1, Product2]},如果它是 [Product1, Product2],您的解码就可以工作,但您需要解析首先是密钥 "product".

如果您的变量名称与 JSON 中的键名称匹配,则不需要 enum CodingKeys: String, CodingKey,如果您 self.variable = container.decode(VariableType.self, forKey: .variable)

,则不需要 init(from decoder: Decoder) ]

让我们重现您的问题:

let jsonStr = """
{
"product": [
{
    "id": 1,
    "ean": "1234567890",
    "name": "Name",
    "manufacturer": "Hersteller",
    "productNumber": "Artikel-Nr.",
    "description": "Beschreibung vom Artikel",
    "propertie": "Eigenschaften",
    "storagePlace": "Lagerplatz",
    "inStock": "12",
    "unit": "0",
    "note": "Irgendwelche Notiz",
    "department": "zvs",
    "created_at": "2022-01-12T10:20:51.000000Z",
    "updated_at": "2022-01-12T10:20:51.000000Z"
}
]
}
"""
class Product: Decodable {

    var id: Int
    var ean: Double
    var name: String
    var manufacturer: String
    var productNumber: String
    var description: String
    var propertie: String
    var storagePlace: String
    var inStock: Int
    var unit: Int
    var note: String
    var department: String
    var created_at: Date
    var updated_at: Date
}
do {
    let products = try JSONDecoder().decode([Product].self, from: Data(jsonStr.utf8))
    print(products)
} catch {
    print("Error while decoding: \(error)")
}

现在,打印 error 而不是 error.localizedDescription 可以提供更好的输入:

$>Error while decoding: typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))

如前所述,您缺少顶层。如果你没有看到它,让我们做相反的事情:

让我们从 Product Codable(也就是 Encodable)开始: class Product: Decodable { => class Product: Codable {,现在 class Product: Codable { => struct Product: Codable {,因为它会得到一个自动 init(id:ean:etc...).

然后,你写了 [Product].self,所以你希望你的 JSON 是那种类型,一个 Product 的数组。 让我们看看它应该是什么样子:

do {
    let products: [Product] = [Product(id: 3, ean: 3.4, name: "Product Name", manufacturer: "Manufacturer Name", productNumber: "Product Number", description: "Product description", propertie: "Product Properties", storagePlace: "Product Storage Place", inStock: 4, unit: 5, note: "Product note", department: "Product Department", created_at: Date(), updated_at: Date())]
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted //Because it's more human readable
    let encodedProductsJSONAsData = try encoder.encode(products)
    let encodedProductsJSONAsString = String(data: encodedProductsJSONAsData, encoding: .utf8)!
    print(encodedProductsJSONAsString)
} catch {
    print("Error while encoding: \(error)")
}

输出:

[
  {
    "id" : 3,
    "unit" : 5,
    "description" : "Product description",
    "productNumber" : "Product Number",
    "note" : "Product note",
    "created_at" : 664108170.25946903,
    "propertie" : "Product Properties",
    "ean" : 3.3999999999999999,
    "storagePlace" : "Product Storage Place",
    "manufacturer" : "Manufacturer Name",
    "updated_at" : 664108170.25946999,
    "inStock" : 4,
    "department" : "Product Department",
    "name" : "Product Name"
  }
]

这就是 [Products] -> JSON 的样子。它与您的 JSON 匹配吗? 不,你看不一样,有一个"product"级。

所以让我们添加:

struct ProductsAPIResponse: Codable {
    let product: [Product]
}

并将解码更改为:

do {
    let productsAPIResponse = try JSONDecoder().decode(ProductsAPIResponse.self, from: Data(jsonStr.utf8))
    print(productsAPIResponse)
    let products = productsAPIResponse.product
    print(products)
} catch {
    print("Error while decoding: \(error)")
}

现在,输出是:

$>Error while decoding: typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "product", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "ean", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))

好的,ean 实际上是 String,而不是 Int,因此请更改 Product 模型,或在您的服务器端修复它。 对于 inStockunit,您会得到相同类型的错误。相同的解决方案。

现在,您将获得:

$>Error while decoding: typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "product", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "created_at", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))

默认情况下,日期解码需要一个 unixtimestamp,因此它等待 Double。在这里,您有一个代表日期的 String,因此您需要一个 DateFormatter.

让我们解决这个问题:

let decoder = JSONDecoder()
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions.insert(.withFractionalSeconds)
decoder.dateDecodingStrategy = .custom({ decoder in
    let container = try decoder.singleValueContainer()
    let dateStr = try container.decode(String.self)
    return dateFormatter.date(from: dateStr) ?? Date()
})
let productsAPIResponse = try decoder.decode(ProductsAPIResponse.self, from: Data(jsonStr.utf8))

不,如果您的日期字符串看起来像 "2022-01-17T10:44:50Z",它可能只是 decoder.dateDecodingStrategy = .iso8601,但需要小数秒来解码...

现在,应该可以了。