Decoding JSON coming from MySQL via PHP to a complex Struct in SwiftUI

I have this JSON-File coming from a MySQL-Database via PHP:

[
   {
      "id":1,
      "partner":{
         "id":1,
         "name":"Migros Bank",
         "image":"migrosbank"
      },
      "name":"Testkonto 1",
      "type":"bank",
      "iban":"CH12 1234 1234 1234 1234 1",
      "datapoints":[
         {
            "id":1,
            "depot_id":1,
            "date":"2021-12-28",
            "amount":5811.490234375
         },
         {
            "id":2,
            "depot_id":1,
            "date":"2021-12-29",
            "amount":7736.89013671875
         }
      ]
   },
   {
      "id":2,
      "partner":{
         "id":1,
         "name":"Migros Bank",
         "image":"migrosbank"
      },
      "name":"Testkonto 2",
      "type":"bank",
      "iban":"CH12 1234 1234 1234 1234 2",
      "datapoints":[
         {
            "id":3,
            "depot_id":2,
            "date":"2021-12-28",
            "amount":500
         },
         {
            "id":4,
            "depot_id":2,
            "date":"2021-12-29",
            "amount":1500
         }
      ]
   }
]

In SwiftUI I try to decode it to a custom struct called Depot which consists of one instance of the custom struct Partner and an array of Instances of the custom struct Depotstand:

import Foundation
import SwiftUI

struct Partner: Hashable, Codable, Identifiable {
    var id: Int
    var name: String
    var image: String
    
    var imageName: Image {
        Image(image)
    }
}

struct Depotstand: Hashable, Codable, Identifiable {
    var id: Int
    var depot_id: Int
    var date: Date
    var amount: Double
}

struct Depot: Hashable, Codable, Identifiable {
    var id: Int
    var partner: Partner
    var name: String
    var type: String
    var iban: String
    var datapoints: [Depotstand]
}

I've added a data-model, to get the JSON-data from my webserver - this part works fine - and then I try to decode the data to the custom struct Depot, which fails (it works, if I simplify the JSON/struct to only the simple Depot struct without depending on Partner and Depotstand instances):

import Foundation

final class ModelDataDepot: ObservableObject {
    @Published var depots = [Depot]()
    
    init(){
        let url = URL(string: "https://api.webcoders.ch/index.php")!
        
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                if let data = data {
                    let decodedData = try JSONDecoder().decode([Depot].self, from: data)
                    DispatchQueue.main.async {
                        self.depots = decodedData
                    }
                } else {
                    print("No Data!")
                }
            } catch {
                print("JSON-Error: \(error)")
            }
        }.resume()
    }
}

Here is the error I get which contains mainly data-type errors, but I have no idea to get around this:

JSON-Error: typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "datapoints", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "date", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))

I've already tried some different data-casting, even using Numberformatters etc. but I can't get it to work...

Thanks for helping me out!

P.s: I am an absolute beginner to SwiftUI and mobile development in general... My coding background is mainly PHP, PowerShell and such, so please be patient with me. :-)

Answered by OOPer in 700041022

The most important part of the error message is this:

CodingKeys(stringValue: "date", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead."

In your JSON text, the value for "date" is string, but in your struct, you are trying to pass the value to Date not String.

What you may need is not a Numberformatter, but setting dateDecodingStrategy:

final class ModelDataDepot: ObservableObject {
    @Published var depots = [Depot]()
    
    private let dateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd"
        df.locale = Locale(identifier: "en_US_POSIX")
        return df
    }()
    
    init() {
        let url = URL(string: "https://api.webcoders.ch/index.php")!
        
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let error = error {
                print(error)
                return
            }
            guard let data = data else {
                print("No Data!")
                return
            }
            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
                let decodedData = try decoder.decode([Depot].self, from: data)
                DispatchQueue.main.async {
                    self.depots = decodedData
                }
            } catch {
                print("JSON-Error: \(error)")
            }
        }.resume()
    }
}
Accepted Answer

The most important part of the error message is this:

CodingKeys(stringValue: "date", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead."

In your JSON text, the value for "date" is string, but in your struct, you are trying to pass the value to Date not String.

What you may need is not a Numberformatter, but setting dateDecodingStrategy:

final class ModelDataDepot: ObservableObject {
    @Published var depots = [Depot]()
    
    private let dateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateFormat = "yyyy-MM-dd"
        df.locale = Locale(identifier: "en_US_POSIX")
        return df
    }()
    
    init() {
        let url = URL(string: "https://api.webcoders.ch/index.php")!
        
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if let error = error {
                print(error)
                return
            }
            guard let data = data else {
                print("No Data!")
                return
            }
            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
                let decodedData = try decoder.decode([Depot].self, from: data)
                DispatchQueue.main.async {
                    self.depots = decodedData
                }
            } catch {
                print("JSON-Error: \(error)")
            }
        }.resume()
    }
}
Decoding JSON coming from MySQL via PHP to a complex Struct in SwiftUI
 
 
Q