Introduction

A common misconception with Swift Codable is that optional fields automatically handle type mismatches. They do not. An optional field handles a missing key or a null value, but if the JSON contains a value of the wrong type (e.g., a string where an integer is expected), decoding fails with a typeMismatch error. This frequently occurs when APIs evolve or return inconsistent data types.

Symptoms

  • typeMismatch(Swift.Int, ... expected to decode Int but found a string/data)
  • dataCorrupted error for a field declared as optional
  • Decoding succeeds for some records but fails for others
  • API sometimes returns "0" (string) instead of 0 (number)
  • Error: Expected to decode Array<String> but found a dictionary instead

Example error: `` typeMismatch(Swift.Int, Swift.DecodingError.Context( codingPath: [CodingKeys(stringValue: "age", ...)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil ) )

Common Causes

  • API returns string numbers: "age": "25" instead of "age": 25
  • API returns boolean as integer: 1/0 instead of true/false
  • Array returned as single object when there is only one element
  • Empty string "" where null is expected
  • Nested object returned as flat string

Step-by-Step Fix

  1. 1.Handle string-to-number conversion with custom decoder:
  2. 2.```swift
  3. 3.struct User: Codable {
  4. 4.let id: Int
  5. 5.let name: String
  6. 6.let age: Int?

enum CodingKeys: String, CodingKey { case id, name, age }

init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) name = try container.decode(String.self, forKey: .name)

// Try Int first, then String -> Int conversion if let intValue = try? container.decode(Int.self, forKey: .age) { age = intValue } else if let stringValue = try? container.decode(String.self, forKey: .age), let parsed = Int(stringValue) { age = parsed } else { age = nil } } } ```

  1. 1.Create a reusable flexible decoder type:
  2. 2.```swift
  3. 3.@propertyWrapper
  4. 4.struct FuzzyInt: Codable {
  5. 5.var wrappedValue: Int?

init(wrappedValue: Int?) { self.wrappedValue = wrappedValue }

init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let int = try? container.decode(Int.self) { wrappedValue = int } else if let string = try? container.decode(String.self), let int = Int(string) { wrappedValue = int } else if let double = try? container.decode(Double.self) { wrappedValue = Int(double) } else { wrappedValue = nil } }

func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(wrappedValue) } }

struct User: Codable { let id: Int @FuzzyInt var age: Int? } ```

  1. 1.Handle boolean-as-integer:
  2. 2.```swift
  3. 3.struct Feature: Codable {
  4. 4.let enabled: Bool

init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let bool = try? container.decode(Bool.self) { enabled = bool } else if let int = try? container.decode(Int.self) { enabled = int != 0 } else if let string = try? container.decode(String.self) { enabled = ["true", "1", "yes"].contains(string.lowercased()) } else { throw DecodingError.typeMismatch( Bool.self, DecodingError.Context(codingPath: container.codingPath, debugDescription: "Expected Bool, Int, or String") ) } } } ```

  1. 1.**Use decodeIfPresent for truly optional fields**:
  2. 2.```swift
  3. 3.// The key difference:
  4. 4.let age = try container.decodeIfPresent(Int.self, forKey: .age)
  5. 5.// This returns nil if the key is absent or null
  6. 6.// But throws typeMismatch if the value is the wrong type
  7. 7.`

Prevention

  • Use @propertyWrapper types for fuzzy decoding of known API inconsistencies
  • Add unit tests that decode sample JSON from each API endpoint
  • Use JSONDecoder().dataDecodingStrategy and dateDecodingStrategy consistently
  • Log decoding failures with the raw JSON for investigation
  • Consider using a library like FlexibleDecoding for complex APIs
  • Document API type inconsistencies and handle them at the model layer