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)
}
}
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.