Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Medium
Suggest reviewing before use
Encode and decode Swift types using Codable (Encodable & Decodable) with
JSONEncoder, JSONDecoder, and related APIs. Targets Swift 6.3 / iOS 26+.
When all stored properties are themselves Codable, the compiler synthesizes
conformance automatically:
struct User: Codable {
let id: Int
let name: String
let email: String
let isVerified: Bool
}
let user = try JSONDecoder().decode(User.self, from: jsonData)
let encoded = try JSONEncoder().encode(user)Prefer Decodable for read-only API responses and Encodable for write-only.
Use Codable only when both directions are required.
Rename JSON keys without writing a custom decoder by declaring a CodingKeys
enum:
struct Product: Codable {
let id: Int
let displayName: String
let imageURL: URL
let priceInCents: Int
enum CodingKeys: String, CodingKey {
case id
case displayName = "display_name"
case imageURL = "image_url"
case priceInCents = "price_in_cents"
}
}Every stored property must appear in the enum. Omitting a property from
CodingKeys excludes it from encoding/decoding -- provide a default value or
compute it separately.
Override init(from:) and encode(to:) for transformations the synthesized
conformance cannot handle:
struct Event: Codable {
let name: String
let timestamp: Date
let tags: [String]
enum CodingKeys: String, CodingKey {
case name, timestamp, tags
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
// Decode Unix timestamp as Double, convert to Date
let epoch = try container.decode(Double.self, forKey: .timestamp)
timestamp = Date(timeIntervalSince1970: epoch)
// Default to empty array when key is missing
tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(timestamp.timeIntervalSince1970, forKey: .timestamp)
try container.encode(tags, forKey: .tags)
}
}Use nestedContainer(keyedBy:forKey:) to navigate and flatten nested JSON:
// JSON: { "id": 1, "location": { "lat": 37.7749, "lng": -122.4194 } }
struct Place: Decodable {
let id: Int
let latitude: Double
let longitude: Double
enum CodingKeys: String, CodingKey { case id, location }
enum LocationKeys: String, CodingKey { case lat, lng }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
let location = try container.nestedContainer(
keyedBy: LocationKeys.self, forKey: .location)
latitude = try location.decode(Double.self, forKey: .lat)
longitude = try location.decode(Double.self, forKey: .lng)
}
}Chain multiple nestedContainer calls to flatten deeply nested structures.
Also use nestedUnkeyedContainer(forKey:) for nested arrays.
Decode arrays of mixed types using a discriminator field:
// JSON: [{"type":"text","content":"Hello"},{"type":"image","url":"pic.jpg"}]
enum ContentBlock: Decodable {
case text(String)
case image(URL)
enum CodingKeys: String, CodingKey { case type, content, url }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "text":
let content = try container.decode(String.self, forKey: .content)
self = .text(content)
case "image":
let url = try container.decode(URL.self, forKey: .url)
self = .image(url)
default:
throw DecodingError.dataCorruptedError(
forKey: .type, in: container,
debugDescription: "Unknown type: \(type)")
}
}
}
let blocks = try JSONDecoder().decode([ContentBlock].self, from: jsonData)Configure JSONDecoder.dateDecodingStrategy to match your API:
let decoder = JSONDecoder()
// ISO 8601 (e.g., "2024-03-15T10:30:00Z")
decoder.dateDecodingStrategy = .iso8601
// Unix timestamp in seconds (e.g., 1710499800)
decoder.dateDecodingStrategy = .secondsSince1970
// Custom DateFormatter
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(formatter)
// Custom closure for multiple formats
decoder.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
if let date = ISO8601DateFormatter().date(from: string) { return date }
throw DecodingError.dataCorruptedError(
in: container, debugDescription: "Cannot decode date: \(string)")
}Set the matching strategy on JSONEncoder:
encoder.dateEncodingStrategy = .iso8601
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64 // Base64-encoded Data fields
decoder.keyDecodingStrategy = .convertFromSnakeCase // simple keys only; not URL/ID spelling
// {"user_name": "Alice"} maps to `var userName: String` -- no CodingKeys needed
let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .base64
encoder.keyEncodingStrategy = .convertToSnakeCaseUse key strategies only for mechanical snake_case-to-camelCase mappings.
convertFromSnakeCase maps by spelling, not Swift acronym/initialism policy:
image_url, base_uri, and user_id match imageUrl, baseUri, and
userId only. If the Swift model uses imageURL, baseURI, or userID,
declare explicit CodingKeys; the strategy will not synthesize those names.
By default, one invalid element fails the entire array. Use a wrapper to skip invalid elements:
struct LossyArray<Element: Decodable>: Decodable {
let elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [Element] = []
while !container.isAtEnd {
if let element = try? container.decode(Element.self) {
elements.append(element)
} else {
_ = try? container.decode(AnyCodableValue.self) // advance past bad element
}
}
self.elements = elements
}
}
private struct AnyCodableValue: Decodable {}Wrap primitives for type safety using singleValueContainer():
struct UserID: Codable, Hashable {
let rawValue: String
init(_ rawValue: String) { self.rawValue = rawValue }
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
rawValue = try container.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
// JSON: "usr_abc123" decodes directly to UserIDStored property defaults such as var theme = "system" do not make synthesized
Decodable tolerate a missing nonoptional key; synthesis still fails unless the
property is optional or decoded manually. Use decodeIfPresent with
nil-coalescing when a missing or null key should fall back:
struct Settings: Decodable {
let theme: String
let fontSize: Int
let notificationsEnabled: Bool
enum CodingKeys: String, CodingKey {
case theme, fontSize = "font_size"
case notificationsEnabled = "notifications_enabled"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
theme = try container.decodeIfPresent(String.self, forKey: .theme) ?? "system"
fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 16
notificationsEnabled = try container.decodeIfPresent(
Bool.self, forKey: .notificationsEnabled) ?? true
}
}let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
let decoder = JSONDecoder()
// Non-conforming floats (NaN, Infinity are not valid JSON)
encoder.nonConformingFloatEncodingStrategy = .convertToString(
positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
decoder.nonConformingFloatDecodingStrategy = .convertFromString(
positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")let plistEncoder = PropertyListEncoder()
plistEncoder.outputFormat = .xml // or .binary
let data = try plistEncoder.encode(settings)
let decoded = try PropertyListDecoder().decode(Settings.self, from: data)func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200...299).contains(http.statusCode) else {
throw APIError.invalidResponse
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // simple keys only; keep CodingKeys for URL/URI/ID
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(User.self, from: data)
}
// Generic API envelope. Configure a decoder inside this helper because
// fetchUser's decoder is out of scope.
struct APIResponse<T: Decodable>: Decodable {
let data: T
let meta: Meta?
struct Meta: Decodable { let page: Int; let totalPages: Int }
}
func decodeUsersEnvelope(from data: Data) throws -> [User] {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // simple keys only; keep CodingKeys for URL/URI/ID
decoder.dateDecodingStrategy = .iso8601
return try decoder.decode(APIResponse<[User]>.self, from: data).data
}SwiftData persists compatible noncomputed stored properties declared on @Model
types. Use Codable structs, enums, and other value types directly when that
value is part of the durable model schema:
struct Address: Codable {
var street: String
var city: String
var zipCode: String
}
@Model class Contact {
var name: String
var address: Address? // Codable value-type property stored by SwiftData
init(name: String, address: Address? = nil) {
self.name = name; self.address = address
}
}Do not recommend @Attribute(.transformable), encoded Data, or encoded
String as a fallback in this Codable skill. Keep schema data as typed
SwiftData properties and defer unsupported persistence designs to the SwiftData skill.
@AppStorage is only for small UserDefaults-backed preferences. Store Bool,
numeric, String, or a RawRepresentable type with a primitive raw value. For
a small Codable preference payload, prefer RawRepresentable with JSON
String raw storage so @AppStorage binds the typed preference directly:
struct UserPreferences: Codable {
var showOnboarding: Bool = true
var accentColor: String = "blue"
}
extension UserPreferences: RawRepresentable {
init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let decoded = try? JSONDecoder().decode(Self.self, from: data)
else { return nil }
self = decoded
}
var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let string = String(data: data, encoding: .utf8)
else { return "{}" }
return string
}
}
struct SettingsView: View {
@AppStorage("userPrefs") private var prefs = UserPreferences()
var body: some View {
Toggle("Show Onboarding", isOn: $prefs.showOnboarding)
}
}1. Not handling missing defaulted fields:
// DON'T -- crashes if key is absent
let value = try container.decode(String.self, forKey: .bio)
// DO -- falls back when the key is absent or null
let value = try container.decodeIfPresent(String.self, forKey: .bio) ?? ""2. Failing entire array when one element is invalid:
// DON'T -- one bad element kills the whole decode
let items = try container.decode([Item].self, forKey: .items)
// DO -- use LossyArray or decode elements individually
let items = try container.decode(LossyArray<Item>.self, forKey: .items).elements3. Date strategy mismatch:
// DON'T -- default strategy expects Double, but API sends ISO string
let decoder = JSONDecoder() // dateDecodingStrategy defaults to .deferredToDate
// DO -- set strategy to match your API format
decoder.dateDecodingStrategy = .iso86014. Force-unwrapping decoded optionals:
// DON'T
let user = try? decoder.decode(User.self, from: data)
print(user!.name)
// DO
guard let user = try? decoder.decode(User.self, from: data) else { return }5. Using Codable when only Decodable is needed:
// DON'T -- unnecessarily constrains the type to also be Encodable
struct APIResponse: Codable { let id: Int; let message: String }
// DO -- use Decodable for read-only API responses
struct APIResponse: Decodable { let id: Int; let message: String }6. Manual CodingKeys for simple snake_case APIs:
// DON'T -- verbose boilerplate for every model
enum CodingKeys: String, CodingKey {
case userName = "user_name"
case avatarUrl = "avatar_url"
}
// DO -- configure once on the decoder for simple cases
decoder.keyDecodingStrategy = .convertFromSnakeCase
// Keep CodingKeys for `imageURL`, `baseURI`, `userID`, and similar names.Decodable only when encoding is not neededdecodeIfPresent used with defaults for optional or missing keyskeyDecodingStrategy = .convertFromSnakeCase used for simple snake_case APIs, with CodingKeys retained for acronym spellingsdateDecodingStrategy matches the API date formatinit(from:) validates and transforms data instead of post-decode fixupsJSONEncoder.outputFormatting includes .sortedKeys for deterministic test outputsingleValueContainer for clean JSONAPIResponse<T> wrapper used for consistent API envelope handling@AppStorage/UserDefaults only for small primitive or RawRepresentable preferences.tessl-plugin
skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
references
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references