Created 2025-04-20T09:00:31Z
Updated 2025-04-21T06:43:50Z
Type Information
Status In Progress

I’m working on dynamic decoding of JSON in Swift, where I need to decode each message into the appropriate type based on its fully qualified name (e.g., “MyApp.Events.UserCreated”), and all types conform to a common protocol like Event.

Can I actually do this using Swift’s Decodable with some reflection via the NSClassFromString + type casting approach.

Assumptions:

• Event classes/structs conform to a common protocol like Event: Decodable. • The list is like:

[
  (json: "{...}", typeName: "MyApp.Events.UserCreated"),
  (json: "{...}", typeName: "MyApp.Events.UserDeleted")
]

• I want to decode each JSON string into its proper type dynamically, and collect them into an array of [Event].

Potential Solution:

import Foundation

protocol Event: Decodable {}

func decodeEvents(from data: [(json: String, typeName: String)]) -> [Event] {
    var events: [Event] = []
    let decoder = JSONDecoder()

    for item in data {
        guard
            let cls = NSClassFromString(item.typeName) as? Decodable.Type,
            let jsonData = item.json.data(using: .utf8)
        else {
            continue
        }

        do {
            let event = try decoder.decode(cls, from: jsonData)
            if let typedEvent = event as? Event {
                events.append(typedEvent)
            }
        } catch {
            print("Decoding error for \(item.typeName): \(error)")
        }
    }

    return events
}

Important Notes:

  1. Class types must be declared with @objc and inherit from NSObject, or be visible to the runtime for NSClassFromString to find them.
  2. Types should be referenced using the module name prefix: e.g., “MyApp.UserCreatedEvent”—check the app’s module name carefully.
  3. For Swift structs (not classes), NSClassFromString won’t work directly. This will need a manual registry approach if using structs.

If using structs Instead of Classes:

Create a registry of type names to types manually:

var eventTypeRegistry: [String: Decodable.Type] = [
    "UserCreated": UserCreated.self,
    "UserDeleted": UserDeleted.self
]

func decodeEvents(from data: [(json: String, typeName: String)]) -> [Event] {
    var events: [Event] = []
    let decoder = JSONDecoder()

    for item in data {
        guard
            let type = eventTypeRegistry[item.typeName],
            let jsonData = item.json.data(using: .utf8)
        else {
            continue
        }

        do {
            let event = try decoder.decode(type, from: jsonData)
            if let typedEvent = event as? Event {
                events.append(typedEvent)
            }
        } catch {
            print("Failed to decode \(item.typeName): \(error)")
        }
    }

    return events
}