Search code examples
jsonswiftuijsondecoder

Can't seem to decode JSON


I know this type of question seems to be answered a lot but I really can't seem to make this work. I'm trying to decode some JSON data into my data structs. I think the problem is there. I may have my data model wrong, but can't quite work it out. The data is not an array, there is an array within it. Its trying to decode a dictionary into array but when I try to initialise my results variable as something other than array it won't build. I'll submit my code and the JSON data in the hopes someone can shed light!

The error I'm getting is:

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

thank you so much


import SwiftUI

struct DataFormatted: Codable, Hashable {
    
    var deliveryPoints: [DeliveryPoints]
    var deliveryPointCount: Int
    var postalCounty: String
    var traditionalCounty: String
    var town: String
    var postCode: String
}

struct DeliveryPoints: Codable, Hashable {
    var organisationName: String
    var departmentName: String
    var line1: String
    var line2: String
    var udprn: String
    var dps: String
    
}

struct ContentView: View {

// I reckon the error is here:
    @State private var results = [DataFormatted]()

    var body: some View {
        VStack{
            List{
                ForEach(results, id: \.self) { result in
                    Text(result.postalCounty)
                }
                
            }
        }
        .task {
            await loadData()
        }
    }
    
    func loadData() async {
        
       
        guard let url = URL(string: "https://pcls1.craftyclicks.co.uk/json/rapidaddress?key=APIKEY&postcode=aa11aa&response=data_formatted") else {
            print("Invalid URL")
            return
        }
        
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "GET"
        
        do {
            let (data, _) = try await URLSession.shared.data(for: request)
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            
            let decodedResponse = try decoder.decode([DataFormatted].self, from: data)
            
            results = decodedResponse
            
        } catch let jsonError as NSError {
            print("JSON decode failed: \(jsonError)")
          }
    }
}

JSON Data:

{
    "delivery_points":[
        {
            "organisation_name":"THE BAKERY",
            "department_name":"",
            "line_1":"1 HIGH STREET",
            "line_2":"CRAFTY VALLEY",
            "udprn":"12345678",
            "dps":"1A"
        },
        {
            "organisation_name":"FILMS R US",
            "department_name":"",
            "line_1":"3 HIGH STREET",
            "line_2":"CRAFTY VALLEY",
            "udprn":"12345679",
            "dps":"1B"
        }
    ],
    "delivery_point_count":2,
    "postal_county":"POSTAL COUNTY",
    "traditional_county":"TRADITIONAL COUNTY",
    "town":"BIG CITY",
    "postcode":"AA1 1AA"
}

Solution

  • try something like this:

    struct DataFormatted: Codable {
        var deliveryPoints: [DeliveryPoint]
        var deliveryPointCount: Int
        var postalCounty: String
        var traditionalCounty: String
        var town: String
        var postcode: String // <-- postcode
    }
    
    struct DeliveryPoint: Codable {
        var organisationName: String
        var departmentName: String
        var line1: String?
        var line2: String?
        var line3: String?
        var udprn: String
        var dps: String
    }
      
    

    and use it like this:

       let apiResponse = try decoder.decode(DataFormatted.self, from: data)
    

    EDIT-1: here is the code I used for testing:

    // -- here, default values for convenience
    struct DataFormatted: Codable {
        var deliveryPoints: [DeliveryPoint] = []
        var deliveryPointCount: Int = 0
        var postalCounty: String = ""
        var traditionalCounty: String = ""
        var town: String = ""
        var postcode: String = ""  // <-- postcode
    }
    
    struct DeliveryPoint: Hashable, Codable {  // <-- here
        var organisationName: String
        var departmentName: String
        var line1: String?
        var line2: String?
        var line3: String?
        var udprn: String
        var dps: String
    }
    
    struct ContentView: View {
        @State private var results = DataFormatted()
        
        var body: some View {
            VStack{
                Text(results.postalCounty)
                Text(results.town)
                List {
                    ForEach(results.deliveryPoints, id: \.self) { point in
                        Text(point.organisationName)
                    }
                }
            }
            .task {
                await loadData()
            }
        }
        
        func loadData() async {
            let apikey = "your-key"  // <-- here
            guard let url = URL(string: "https://pcls1.craftyclicks.co.uk/json/rapidaddress?key=\(apikey)&postcode=aa11aa&response=data_formatted") else {
                print("Invalid URL")
                return
            }
            do {
                let (data, _) = try await URLSession.shared.data(from: url)
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                results = try decoder.decode(DataFormatted.self, from: data)  // <-- here
            } catch {
                print("JSON decode failed: \(error)")
            }
        }
    }