swift 115 lines · 1 tab

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.


Related snips

Share this code

Here's the card — post it anywhere.

JSON encoding and decoding with Codable — share card
Link copied