Decode JSON events
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:
- Class types must be declared with @objc and inherit from NSObject, or be visible to the runtime for NSClassFromString to find them.
- Types should be referenced using the module name prefix: e.g., “MyApp.UserCreatedEvent”—check the app’s module name carefully.
- 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
}