Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

JSON to Swift Class Generator

This technical guide provides an in-depth analysis of the json to swift class engine, best practices for implementation, and data security standards.

JSON to Swift Class: ObservableObject, @Observable, and Reference-Type Models

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.

Live Example: Shared Cart Model with @Observable (iOS 17+)

// 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")
    }
}

ObservableObject + @Published (iOS 13+ / older codebase)

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

Inheritance Hierarchies with Codable

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

NSObject Subclass for Objective-C Interop

// 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

Swift struct vs class for JSON Models

  • struct (value type) — the default: Copied on assignment. Thread-safe by default. Preferred for API response models, DTOs, and view state passed down through a view hierarchy via function arguments.
  • class (reference type) — use when shared: Multiple owners see the same instance. Required when a single model must be observed and mutated from multiple independent views (shopping cart, auth state, settings). Use @Observable (iOS 17+) or ObservableObject (iOS 13+).
  • class with inheritance: The only option when you need an inheritance hierarchy (e.g., polymorphic event types decoded from a discriminant field, or NSObject subclasses for Objective-C interop).

FAQ

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.

Developer FAQ

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.