SwiftUI Airtable API integration failing with decoding errors

Hey everyone! I’m pretty new to Swift development and running into some issues while working with Airtable’s API in my SwiftUI app.

I have a basic table with book information that includes name and cover fields. Here’s what the API returns:

{
   "records":[
      {
         "id":"recX1Y2Z3A4B5C6D7",
         "createdTime":"2022-11-15T14:22:18.000Z",
         "fields":{
            "name":"The Magic Kingdom",
            "cover":"coverMagic"
         }
      },
      {
         "id":"recA8B9C0D1E2F3G4",
         "createdTime":"2022-11-15T14:22:19.000Z",
         "fields":{
            "name":"Silent Waters",
            "cover":"coverSilent"
         }
      }
   ]
}

I created this response structure:

struct AirtableResponse: Decodable {
    let records: [BookRecord]
    
    struct BookRecord: Decodable {
        let id: String
        let createdTime: String
        let fields: BookFields
    }
    
    struct BookFields: Decodable {
        let name: String
        let cover: String
    }
}

And my main data model:

struct Book: Identifiable {
    let id = UUID()
    let name: String
    let cover: String
}

Here’s my network call:

class BooksViewModel: ObservableObject {
    
    @Published var books = [Book]()
    
    func loadBooks() async {
        
        guard let endpoint = URL(string: "https://api.airtable.com/v0/appXYZ123/Books") else {
            return
        }
        
        var apiRequest = URLRequest(url: endpoint)
        apiRequest.httpMethod = "GET"
        apiRequest.setValue(
            "Bearer mytoken",
            forHTTPHeaderField: "Authorization"
        )
        
        let networkTask = URLSession.shared.dataTask(with: apiRequest) { data, response, error in
            
            if let error = error {
                print(error)
            } else if
                let data = data,
                let httpResponse = response as? HTTPURLResponse,
                httpResponse.statusCode == 200 {
                
                do {
                    let apiResponse: AirtableResponse = try JSONDecoder().decode(AirtableResponse.self, from: data)
                    
                    DispatchQueue.main.async {
                        self.books = []
                    }
                    
                    for item in apiResponse.records {
                        DispatchQueue.main.async {
                            self.books.append(
                                Book(name: item.fields.name, cover: String(item.fields.cover))
                            )
                        }
                    }
                } catch {
                    print(error)
                }
            }
        }
        
        networkTask.resume()
    }
}

I keep getting this decoding error:

keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "records", intValue: nil), _JSONKey(stringValue: "Index 3", intValue: 3), CodingKeys(stringValue: "fields", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))

I’ve been stuck on this for hours and tried different approaches but can’t figure out what’s wrong. Any ideas what might be causing this?

Airtable gets wonky with empty fields. I bet one of your records has a null or missing name field. Add some debug prints before the decode to check the actual JSON response - it’s probably not matching your example. Also, those multiple dispatch calls are messy. Build the whole array first, then assign it once.

I hit this exact issue with Airtable last year. Yeah, it’s missing fields in some records, but here’s what caught my eye - your error mentions index 3, but you’re only showing 2 records in that JSON example. Double-check you’re looking at the real response, not just a sample.

Sure, make those fields optional like the other answer said, but I’d also throw in some logging first. Print out that raw JSON before you decode it so you can see which records are messed up. I always add a filter step after decoding to ditch any records missing the key fields.

One more thing - you’ve got async in your function but then you’re using completion handlers inside. Pick one and stick with it. Mixing them just makes debugging a pain.

That error’s pretty clear - record at index 3 is missing the “name” field. Happens all the time with Airtable since people leave fields empty or delete stuff. I hit this same issue when I started using Airtable’s API. Your struct expects every field to exist, but Airtable records can have missing fields. Make your BookFields properties optional:

struct BookFields: Decodable {
    let name: String?
    let cover: String?
}

Then handle the optionals when you create Book objects. Skip records missing required fields or set defaults. Also, ditch that completion handler for async/await - way cleaner and you won’t need those DispatchQueue calls.

Classic Airtable gotcha. I’ve debugged this exact thing probably 20 times across different projects. The issue isn’t just missing fields - Airtable can return completely empty field objects or fields with weird data types.

Here’s what I always do now. First, make everything optional and add validation:

struct BookFields: Decodable {
    let name: String?
    let cover: String?
}

But the real fix is a custom decoder that handles Airtable’s quirks:

struct BookFields: Decodable {
    let name: String
    let cover: String
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Unknown"
        self.cover = try container.decodeIfPresent(String.self, forKey: .cover) ?? "default"
    }
    
    private enum CodingKeys: String, CodingKey {
        case name, cover
    }
}

Also dump the raw response first. Add this right after you get the data:

print(String(data: data, encoding: .utf8) ?? "Invalid data")

I guarantee you’ll find records with missing fields or weird formatting. Airtable users love messing with data after you build your schema.

This video covers solid patterns for handling API inconsistencies:

One last thing - switch to async/await properly. That completion handler mixed with async is asking for trouble.