PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Sunday, February 20, 2022

[FIXED] How to decodable request from Laravel in Swift and Alamofire

 February 20, 2022     alamofire, api, json, laravel, swift     No comments   

Issue

I have a problem about decodable the "product" from laravel api request. Error is "The data couldn´t be read because it isn´t in the correct format.

request with insomnia is right and i get the product where the ean number has.

Laravel for database:

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 Product decodable:

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 short code:

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

Preview from request:

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

i can´t see the error i made!?


Solution

Don't use error.localizedError, use error, so print it, it will give more useful informations. You'll see it along the answers, which I made step by step.

You are missing the fact that your JSON is a top level a Dictionary, it's {"product": [Product1, Product2]}, if its was [Product1, Product2], your decoding would work, but you need to parsed first the key "product".

There is no need for enum CodingKeys: String, CodingKey if your variables names match the key name from JSON, and no need for init(from decoder: Decoder) if you self.variable = container.decode(VariableType.self, forKey: .variable)

So let's replicate your issue:

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

Now, printing error instead of error.localizedDescription gives better input:

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

As said, you are missing the top level. If you don't see it, let's do the reverse:

Let's start by making Product Codable (ie, Encodable too): class Product: Decodable { => class Product: Codable {, and now class Product: Codable { => struct Product: Codable {, because it will get a automatic init(id:ean:etc...).

Then, you wrote [Product].self, so you expect your JSON to be of that type, an array of Product. Let's see what it should look likes then:

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

Output:

[
  {
    "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"
  }
]

So that's what the [Products] -> JSON looks like. Does it match your JSON? No, you see it's no the same, there is a "product" level.

So let's add:

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

and change the decoding to:

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

Now, the output is:

$>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))

Ok, ean is in fact a String, not an Int, so change either the Product model, or fix it in your server side. You'll get the same kind of error for inStock & unit. Same solution.

Now, you'll get:

$>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))

By default, the date decoding expects a unixtimestamp, so it awaits for a Double. Here, you have a String representing the date, so you need a DateFormatter.

Let's fix that:

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))

Not, it could have just been decoder.dateDecodingStrategy = .iso8601 if your date strings looks like "2022-01-17T10:44:50Z", but there is need for the fractional seconds to decode...

Now, it should works.



Answered By - Larme
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing