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 class engine, best practices for implementation, and data security standards.
Swift structs are the right default for JSON models — but classes are the right tool when you need shared mutable state observed by multiple views. Two patterns dominate: ObservableObject with @Published for iOS 13–16, and the new @Observable macro for iOS 17+. Both use a class (reference type) so SwiftUI views can observe a single shared instance rather than getting a copy. This page covers class-based models for JSON: when to use them, how to make them Codable, and how to wire them into the SwiftUI observation system.
// Input JSON (loaded once, shared across tabs/views)
{
"cart_id": "cart_44f2",
"user_id": "usr_9921",
"item_count": 3,
"subtotal": 149.97,
"discount_code": null,
"last_updated": "2024-01-15T10:30:00Z"
}
// Generated as a reference-type model using @Observable
import Foundation
import Observation
@Observable
class Cart: Codable {
var cartId: String
var userId: String
var itemCount: Int
var subtotal: Double
var discountCode: String?
var lastUpdated: Date
enum CodingKeys: String, CodingKey {
case cartId = "cart_id"
case userId = "user_id"
case itemCount = "item_count"
case subtotal
case discountCode = "discount_code"
case lastUpdated = "last_updated"
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
cartId = try c.decode(String.self, forKey: .cartId)
userId = try c.decode(String.self, forKey: .userId)
itemCount = try c.decode(Int.self, forKey: .itemCount)
subtotal = try c.decode(Double.self, forKey: .subtotal)
discountCode = try c.decodeIfPresent(String.self, forKey: .discountCode)
lastUpdated = try c.decode(Date.self, forKey: .lastUpdated)
}
}
// One shared instance injected into the SwiftUI environment
@main
struct ShopApp: App {
@State private var cart = Cart() // lives for the lifetime of the app
var body: some Scene {
WindowGroup {
ContentView()
.environment(cart)
}
}
}
// Any view reads from and writes to the same Cart instance
struct CartBadgeView: View {
@Environment(Cart.self) private var cart
var body: some View {
Label("\(cart.itemCount)", systemImage: "cart")
}
}
import Foundation
import Combine
class CartStore: ObservableObject, Codable {
@Published var cartId: String
@Published var userId: String
@Published var itemCount: Int
@Published var subtotal: Double
@Published var discountCode: String?
// @Published is not Codable — must write CodingKeys and init manually
enum CodingKeys: String, CodingKey {
case cartId = "cart_id", userId = "user_id"
case itemCount = "item_count", subtotal, discountCode = "discount_code"
}
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
cartId = try c.decode(String.self, forKey: .cartId)
userId = try c.decode(String.self, forKey: .userId)
itemCount = try c.decode(Int.self, forKey: .itemCount)
subtotal = try c.decode(Double.self, forKey: .subtotal)
discountCode = try c.decodeIfPresent(String.self, forKey: .discountCode)
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(cartId, forKey: .cartId)
try c.encode(userId, forKey: .userId)
try c.encode(itemCount, forKey: .itemCount)
try c.encode(subtotal, forKey: .subtotal)
try c.encodeIfPresent(discountCode, forKey: .discountCode)
}
}
// Inject via .environmentObject()
struct CartView: View {
@EnvironmentObject var cart: CartStore
var body: some View {
Text("Items: \(cart.itemCount), Subtotal: $\(cart.subtotal, specifier: "%.2f")")
}
}
// Base class for all events — subclasses add type-specific fields
class APIEvent: Codable {
let eventId: String
let timestamp: Date
let source: String
enum CodingKeys: String, CodingKey {
case eventId = "event_id", timestamp, source
}
}
// Subclass adds its own fields — must call super
class PurchaseEvent: APIEvent {
let amount: Double
let currency: String
enum CodingKeys: String, CodingKey { case amount, currency }
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
amount = try c.decode(Double.self, forKey: .amount)
currency = try c.decode(String.self, forKey: .currency)
try super.init(from: decoder) // decodes base fields
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(amount, forKey: .amount)
try c.encode(currency, forKey: .currency)
}
}
// Factory function: decode the right subclass based on a discriminant field
func decodeEvent(from data: Data) throws -> APIEvent {
struct Discriminant: Decodable { let type: String }
let disc = try JSONDecoder().decode(Discriminant.self, from: data)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
switch disc.type {
case "purchase": return try decoder.decode(PurchaseEvent.self, from: data)
default: return try decoder.decode(APIEvent.self, from: data)
}
}
// When mixing Swift JSON models with Objective-C code or UIKit KVO
import Foundation
@objc class UserProfile: NSObject, Codable {
@objc dynamic var userId: String
@objc dynamic var username: String
@objc dynamic var score: Double
enum CodingKeys: String, CodingKey {
case userId = "user_id", username, score
}
init(userId: String, username: String, score: Double) {
self.userId = userId
self.username = username
self.score = score
}
required init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
userId = try c.decode(String.self, forKey: .userId)
username = try c.decode(String.self, forKey: .username)
score = try c.decode(Double.self, forKey: .score)
}
}
// @objc dynamic enables KVO observation from Objective-C
// Also works with NSFetchedResultsController when bridging Core Data
@Observable (iOS 17+) or ObservableObject (iOS 13+).Q: @Observable vs ObservableObject — which should I use?
A: @Observable (iOS 17+) is simpler: no @Published on every property, inject with .environment(), observe with @Environment. ObservableObject (iOS 13+) is the older pattern: mark each published property with @Published, inject with .environmentObject(), observe with @EnvironmentObject. Use @Observable for new iOS 17+ projects; keep ObservableObject when supporting earlier OS versions.
Q: Why does @Observable class need manual Codable conformance?
A: The @Observable macro adds property-tracking wrappers to stored properties, which prevents the synthesized init(from:) from seeing the original storage. You must write init(from:) explicitly (as shown above) so the decoder accesses the underlying stored properties, not the observation tracking layer.
Q: Can I make a Codable class thread-safe for concurrent access?
A: Use actor for isolated async state, or mark the class @MainActor so all mutations happen on the main thread (the natural choice for UI-driving models). For background decoding, decode into a struct first, then pass the struct to the main-actor class to apply the update.
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.