CtrlK
BlogDocsLog inGet started
Tessl Logo

dpearson2699/swift-ios-skills

Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

codable-advanced-patterns.mdskills/swift-codable/references/

Advanced Codable Patterns

Use these patterns after the model's core keys, nested containers, and decoding strategies are established.

Contents

  • Heterogeneous Arrays
  • Lossy Arrays
  • Single-Value Wrappers
  • Missing-Key Defaults
  • Encoder Configuration
  • Persistence Boundaries

Heterogeneous Arrays

Decode a discriminator first, then decode only fields owned by that case:

enum ContentBlock: Decodable {
    case text(String)
    case image(URL)

    enum CodingKeys: String, CodingKey { case type, content, url }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        switch try values.decode(String.self, forKey: .type) {
        case "text": self = .text(try values.decode(String.self, forKey: .content))
        case "image": self = .image(try values.decode(URL.self, forKey: .url))
        default:
            throw DecodingError.dataCorruptedError(
                forKey: .type, in: values,
                debugDescription: "Unknown content type"
            )
        }
    }
}

Lossy Arrays

Default array decoding fails when any element is invalid. Use lossy decoding only when the product contract permits partial data, and report skipped items.

struct LossyArray<Element: Decodable>: Decodable {
    let elements: [Element]

    init(from decoder: Decoder) throws {
        var values = try decoder.unkeyedContainer()
        var decoded: [Element] = []
        while !values.isAtEnd {
            if let value = try? values.decode(Element.self) {
                decoded.append(value)
            } else {
                _ = try? values.decode(DiscardedValue.self)
            }
        }
        elements = decoded
    }
}

private struct DiscardedValue: Decodable {}

Single-Value Wrappers

struct UserID: Codable, Hashable {
    let rawValue: String

    init(from decoder: Decoder) throws {
        rawValue = try decoder.singleValueContainer().decode(String.self)
    }

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

Missing-Key Defaults

A stored property default does not make synthesized decoding tolerate a missing nonoptional key. Decode manually when missing or null has an explicit fallback:

let values = try decoder.container(keyedBy: CodingKeys.self)
theme = try values.decodeIfPresent(String.self, forKey: .theme) ?? "system"

Preserve the distinction between a missing key, explicit null, and malformed data when the API contract assigns them different meanings.

Encoder Configuration

Configure matching date, data, float, and key strategies once per transport or file format. Use PropertyListEncoder/PropertyListDecoder for property lists; do not send plist configuration through JSON helpers.

Persistence Boundaries

  • SwiftData: persist supported Codable value types as typed model properties; route schema and unsupported-storage decisions to swiftdata.
  • UserDefaults: store primitive preferences directly. For a small Codable preference, a RawRepresentable wrapper with JSON-string storage can support @AppStorage; route larger or durable data to a real persistence layer.

skills

.mcp.json

README.md

tile.json