JSON encoding and decoding with Codable

Sofia Martinez Jan 2026
1 tab
import Foundation

// MARK: - Basic Codable
struct User: Codable {
    let id: Int
    let username: String
    let email: String
    let createdAt: Date
}

// MARK: - Custom CodingKeys
struct Post: Codable {
    let id: Int
    let title: String
    let body: String
    let authorID: Int
    let createdAt: Date
    let updatedAt: Date

    enum CodingKeys: String, CodingKey {
        case id
        case title
        case body
        case authorID = "author_id"
        case createdAt = "created_at"
        case updatedAt = "updated_at"
    }
}

// MARK: - Nested Codable
struct Comment: Codable {
    let id: Int
    let content: String
    let author: User
    let post: Post
}

// MARK: - Custom Decoding
struct APIResponse<T: Codable>: Codable {
    let success: Bool
    let data: T?
    let error: String?
    let metadata: Metadata?

    struct Metadata: Codable {
        let page: Int
        let perPage: Int
        let total: Int

        enum CodingKeys: String, CodingKey {
            case page
            case perPage = "per_page"
            case total
        }
    }
}

// MARK: - Encoding/Decoding Manager
class JSONManager {
    static let shared = JSONManager()

    private let encoder: JSONEncoder
    private let decoder: JSONDecoder

    private init() {
        encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
        encoder.dateEncodingStrategy = .iso8601
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

        decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        decoder.dateDecodingStrategy = .iso8601
    }

    func encode<T: Encodable>(_ value: T) throws -> Data {
        return try encoder.encode(value)
    }

    func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
        return try decoder.decode(type, from: data)
    }

    func encodeToString<T: Encodable>(_ value: T) -> String? {
        guard let data = try? encode(value) else { return nil }
        return String(data: data, encoding: .utf8)
    }

    func decodeFromString<T: Decodable>(_ type: T.Type, from string: String) -> T? {
        guard let data = string.data(using: .utf8) else { return nil }
        return try? decode(type, from: data)
    }
}

// MARK: - Custom Date Decoding
extension JSONDecoder {
    static var customDateDecoder: JSONDecoder {
        let decoder = JSONDecoder()
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
        decoder.dateDecodingStrategy = .formatted(dateFormatter)
        return decoder
    }
}

// MARK: - Usage Example
extension User {
    static func fromJSON(_ json: String) -> User? {
        JSONManager.shared.decodeFromString(User.self, from: json)
    }

    func toJSON() -> String? {
        JSONManager.shared.encodeToString(self)
    }
}
1 file · swift Explain with highlit

Codable protocol combines Encodable and Decodable for seamless JSON conversion. Swift structs and classes conforming to Codable automatically synthesize encoding/decoding logic when all properties are Codable. JSONEncoder converts Swift types to JSON data, while JSONDecoder parses JSON into Swift types. I customize key mapping with CodingKeys enum to match API snake_case or different field names. For complex transformations, I implement custom init(from decoder:) and encode(to encoder:) methods. Date strategies handle various date formats. The keyDecodingStrategy and keyEncodingStrategy automate case conversion. Codable works with property lists, UserDefaults, and file storage too.