Free & open source — no account required
Free & open source — no account required
This technical guide provides an in-depth analysis of the json to swift struct engine, best practices for implementation, and data security standards.
Swift's Codable protocol handles JSON mapping at compile time — #[derive(Deserialize)] in Rust, @JsonSerializable in Dart, but built directly into the Swift standard library with no dependencies. Converting your JSON to Swift structs with Codable gives you automatic JSON ↔ Swift conversion, type-safe enums for discriminated values, custom decoding strategies for dates and keys, and full integration with Swift's async/await networking. For SwiftUI apps, these structs drive the entire view state from decoded API responses.
// Input JSON
{
"order_id": "ORD-772",
"status": "shipped",
"customer_name": "Sarah Jenkins",
"is_gift": false,
"items": [
{ "sku": "WGT-001", "quantity": 2, "unit_price": 49.99 }
],
"created_at": "2024-01-15T08:30:00Z"
}
// Generated Swift Structs
import Foundation
struct OrderItem: Codable {
let sku: String
let quantity: Int
let unitPrice: Double
enum CodingKeys: String, CodingKey {
case sku
case quantity
case unitPrice = "unit_price"
}
}
enum OrderStatus: String, Codable {
case pending = "pending"
case shipped = "shipped"
case delivered = "delivered"
case cancelled = "cancelled"
}
struct Order: Codable {
let orderId: String
let status: OrderStatus
let customerName: String
let isGift: Bool
let items: [OrderItem]
let createdAt: Date
enum CodingKeys: String, CodingKey {
case orderId = "order_id"
case status
case customerName = "customer_name"
case isGift = "is_gift"
case items
case createdAt = "created_at"
}
}
// Decoder with ISO 8601 date strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let order = try decoder.decode(Order.self, from: jsonData)
The CodingKeys enum maps snake_case JSON keys to camelCase Swift properties. For models where all keys follow snake_case → camelCase conversion, use decoder.keyDecodingStrategy = .convertFromSnakeCase instead — this applies the transformation globally without a per-property enum.
struct UserProfile: Codable {
let userId: String
let displayName: String
let avatarUrl: String?
let createdAt: Date
let isVerified: Bool
}
// With keyDecodingStrategy, no CodingKeys needed for snake_case JSON
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601
// JSON: {"user_id": "...", "display_name": "...", "avatar_url": null, ...}
let profile = try decoder.decode(UserProfile.self, from: jsonData)
convertFromSnakeCase handles consecutive uppercase letters correctly: user_url → userUrl, html_content → htmlContent. Use it when consuming REST APIs that consistently use snake_case keys.
struct Product: Decodable {
let id: String
let name: String
let priceCents: Int // JSON: "price_cents": 4999
let price: Double // computed: 49.99
enum CodingKeys: String, CodingKey {
case id, name
case priceCents = "price_cents"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
priceCents = try container.decode(Int.self, forKey: .priceCents)
price = Double(priceCents) / 100.0 // derive from stored value
}
}
// Flexible date decoding: try multiple formats
struct FlexibleEvent: Decodable {
let id: String
let timestamp: Date
enum CodingKeys: String, CodingKey { case id, timestamp }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
// Try ISO 8601 first, fall back to Unix timestamp
if let isoString = try? container.decode(String.self, forKey: .timestamp) {
guard let date = ISO8601DateFormatter().date(from: isoString) else {
throw DecodingError.dataCorruptedError(forKey: .timestamp, in: container, debugDescription: "Invalid ISO8601 date")
}
timestamp = date
} else {
let epochSeconds = try container.decode(Double.self, forKey: .timestamp)
timestamp = Date(timeIntervalSince1970: epochSeconds)
}
}
}
import Foundation
struct APIClient {
private let decoder: JSONDecoder = {
let d = JSONDecoder()
d.keyDecodingStrategy = .convertFromSnakeCase
d.dateDecodingStrategy = .iso8601
return d
}()
func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return try decoder.decode(T.self, from: data)
}
}
// Usage in a SwiftUI ViewModel
@MainActor
class UserViewModel: ObservableObject {
@Published var user: UserProfile?
@Published var error: String?
private let client = APIClient()
func loadUser(id: String) async {
do {
let url = URL(string: "https://api.example.com/users/\(id)")!
user = try await client.fetch(UserProfile.self, from: url)
} catch {
self.error = error.localizedDescription
}
}
}
// SwiftUI View
struct UserView: View {
@StateObject private var viewModel = UserViewModel()
let userId: String
var body: some View {
Group {
if let user = viewModel.user {
Text(user.displayName)
} else if let error = viewModel.error {
Text("Error: \(error)").foregroundColor(.red)
} else {
ProgressView()
}
}
.task { await viewModel.loadUser(id: userId) }
}
}
// JSON polymorphism: {"type": "click", "x": 100, "y": 200}
enum DomEvent: Decodable {
case click(x: Int, y: Int)
case keypress(key: String, ctrl: Bool)
case submit(formId: String)
enum CodingKeys: String, CodingKey {
case type, x, y, key, ctrl
case formId = "form_id"
}
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 "click":
let x = try container.decode(Int.self, forKey: .x)
let y = try container.decode(Int.self, forKey: .y)
self = .click(x: x, y: y)
case "keypress":
let key = try container.decode(String.self, forKey: .key)
let ctrl = try container.decode(Bool.self, forKey: .ctrl)
self = .keypress(key: key, ctrl: ctrl)
case "submit":
let formId = try container.decode(String.self, forKey: .formId)
self = .submit(formId: formId)
default:
throw DecodingError.dataCorruptedError(
forKey: .type, in: container,
debugDescription: "Unknown event type: \(type)"
)
}
}
}
// Exhaustive handling in Swift
func handleEvent(_ event: DomEvent) {
switch event {
case .click(let x, let y): print("Click at \(x), \(y)")
case .keypress(let key, _): print("Key: \(key)")
case .submit(let formId): print("Form: \(formId)")
}
}
let: Immutable struct instances are thread-safe by default. When you need to update a value, create a new instance: var updated = original; updated = updated with the change isn't idiomatic — use a new initializer or var struct at the call site.enum Status: String, Codable throws on unknown values. For APIs that may add new statuses, add an unknown(String) case with custom decoding that captures the raw value instead of failing.JSONDecoder instance: Creating a JSONDecoder on every request allocates a new instance. Create one statically or inject it as a dependency — the decoder is stateless and thread-safe after configuration.Q: What is Codable and why not just use [String: Any]?
A: Codable is a type alias for Encodable & Decodable. Unlike [String: Any] dictionaries, Codable structs give you compile-time field access, Swift's type system enforcing optionality, and IDE autocompletion. Dictionary access requires as? casts everywhere and silently passes nil when a key is mistyped.
Q: How do I handle arrays at the top level?
A: Decode directly to an array type: try decoder.decode([Order].self, from: jsonData). The decoder handles top-level arrays identically to arrays inside an object.
Q: Should I use Codable or a third-party library like Alamofire?
A: Use built-in Codable with URLSession async/await for new projects. Alamofire adds convenience but is a dependency; Swift's native async networking has largely eliminated the need for it in modern codebases. If you're already using Alamofire, its responseDecodable method integrates directly with any Codable type.
Q: How do I test JSON decoding?
A: Load test JSON from a file in your test bundle: Bundle(for: type(of: self)).url(forResource: "test-order", withExtension: "json"). Decode it with the same decoder configuration as production and assert on the decoded properties. This tests the actual mapping without network calls.
Is the processing local-only?
Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.
Can I use this for enterprise projects?
Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.