2

I want to decode json responses of a websocket "notification" where the type of notification is within the json response.

JSON example:

{
    "jsonrpc": "2.0",
    "method": "Application.OnVolumeChanged",
    "params": {
        "data": {
            "muted": false,
            "volume": 88.6131134033203125
        },
        "sender": "xbmc"
    }
}

This is what I currently have:

func notificationMessage(text: String) {
    do {
        if let jsonData = text.data(using: .utf8),
            let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
            let method = json["method"] as? String,
            let methodName = method.split(separator: ".").last?.description {

            let decoder = JSONDecoder()
            let object = try decoder.decode(OnVolumeChanged.self, from: jsonData)

            print(object)
        }
    } catch {
        print("Error deserializing JSON: \(error)")
    }
}

Now I somehow want to use methodName instead of OnVolumeChanged.self. But I don't feel like making a huge switch case on the methodName since I can get like hundreds of diferent methods I have tried NSClassFromString(methodName) but this is giving me AnyClass? which is not a concrete type.

Is there a way to get a class type from string?

  • Have you tried `NSClassFromString(method)` instead of stripping the namespace part of the `method` string? – egor.zhdan Aug 13 '17 at 14:32
  • ``decode()`` is expecting a concrete type ``T.Type`` and ``AnyClass?`` is basically anything. – Bert van Nes Aug 13 '17 at 15:26
  • This goes against Swift's static typing system so you will have to swim against a pretty strong current. But before you do that, ask yourself: is there any difference that you want to capture for `OnVolumeChanged` and `AnotherMethodName`? If so, please edit your question to add an example. – Code Different Aug 14 '17 at 15:16
  • I had the same issue for matching JSON to class types, and I ended up using enumerations with raw values matching the JSON string values. – Sid Mani Aug 15 '17 at 05:26
  • Yes @CodeDifferent, the data object within the json is diferent for every method. But i'll stick to an enum containing all possible methods and then determine with a switch case which object should be used for decoding the json. Thanks for information! – Bert van Nes Aug 16 '17 at 17:23

1 Answers1

1

I have faced the same problem and here is my solution. You can add methods to Mapper dictionary any time you want.

//1

let Mapper: [String: Any] = ["OnVolumeChanged"  : OnVolumeChanged.self]

//2

func notificationMessage(text: String) {
                    do {
                        if let jsonData = text.data(using: .utf8),
                            let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
                            let method = json["method"] as? String,
                            let methodName = method.split(separator: ".").last?.description {

                            let className = Mapper[methodName] as! Codable.Type
                            let object = try className.init(jsonData: jsonData)


                            print(object)
                        }
                    } catch {
                        print("Error deserializing JSON: \(error)")
                    }
                }

//3

extension Decodable {
                    init(jsonData: Data) throws {
                        self = try JSONDecoder().decode(Self.self, from: jsonData)
                    }
                }
SamehDos
  • 1,183
  • 1
  • 10
  • 23
  • there is nothing "dynamic" in this solution, there should be a solution where we don't need to manually enter all the classes we auto generated for json responses, even supplying the "decoder" utility with the expected json response and do `Bundle.main.classNamed("MyClassName")` is better, (like here: https://stackoverflow.com/a/55170964/530884) – Shaybc Aug 25 '21 at 20:09