如何在 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
模型,或在您的服务器端修复它。
对于 inStock
和 unit
,您会得到相同类型的错误。相同的解决方案。
现在,您将获得:
$>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
,但需要小数秒来解码...
现在,应该可以了。
我对 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
模型,或在您的服务器端修复它。
对于 inStock
和 unit
,您会得到相同类型的错误。相同的解决方案。
现在,您将获得:
$>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
,但需要小数秒来解码...
现在,应该可以了。