Swift - 将类型动态传递给 JSONDecoder

Posted

技术标签:

【中文标题】Swift - 将类型动态传递给 JSONDecoder【英文标题】:Swift - passing types dynamically to JSONDecoder 【发布时间】:2018-07-27 11:38:52 【问题描述】:

我正在尝试使用通用嵌套对象从 json 对象中解码,为此我想在解码时动态传递类的类型。

例如,我的类是扩展 ObjectModel(和 :Codable)的 EContactModel 和 ENotificationModel。 ENotificationModel 可以包含嵌套的 ObjectModel(可以是联系人、通知或其他对象模型)。

我有一本这样的字典:

static let OBJECT_STRING_CLASS_MAP = [
        "EContactModel" : EContactModel.self,
        "ENotificationModel" : ENotificationModel.self
...
    ]

我在 ENotificationModel 中的解码初始化方法如下所示:

required init(from decoder: Decoder) throws
    
        try super.init(from: decoder)
        let values = try decoder.container(keyedBy: CodingKeys.self)


    ...
    //decode some fields here
    self.message = try values.decodeIfPresent(String.self, forKey: .message)
    ...

    //decode field "masterObject" of generic type ObjectModel
    let cls = ObjectModelTypes.OBJECT_STRING_CLASS_MAP[classNameString]!
    let t = type(of: cls)
    print(cls) //this prints "EContactModel"
    self.masterObject = try values.decodeIfPresent(cls, forKey: .masterObject)
    print(t) //prints ObjectModel.Type
    print(type(of: self.masterObject!)) //prints ObjectModel


我也尝试过传递 type(of: anObjectInstanceFromADictionary) 但仍然无法正常工作,但如果我传递 type(of: EContactModel()) 则可以。我无法理解这一点,因为两个对象都是相同的(即 EContactModel 的实例)

有解决办法吗?

【问题讨论】:

【参考方案1】:

您可以使用可选变量声明您的对象模型,并让 JSONDecoder 为您解决问题。

class ApiModelImage: Decodable 
    let file: String
    let thumbnail_file: String
    ...


class ApiModelVideo: Decodable 
    let thumbnail: URL
    let duration: String?
    let youtube_id: String
    let youtube_url: URL
    ...


class ApiModelMessage: Decodable 
    let title: String
    let body: String
    let image: ApiModelImage?
    let video: ApiModelVideo?
    ...

那么你所要做的就是……

if let message = try? JSONDecoder().decode(ApiModelMessage.self, from: data) 
    if let image = message.image 
        print("yay, my message contains an image!")
    
    if let video = message.video 
        print("yay, my message contains a video!")
    

或者,您可以使用泛型并在调用 API 代码时指定类型:

func get<T: Decodable>(from endpoint: String, onError: @escaping(_: Error?) -> Void, onSuccess: @escaping (_: T) -> Void) 
    getData(from: endpoint, onError: onError)  (data) in
        do 
            let response = try JSONDecoder().decode(T.self, from: data)
            onSuccess(response)
         catch 
            onError(error)
        
    

使用这种方式,您只需确保定义了预期的响应类型:

    let successCb =  (_ response: GetUnreadCountsResponse) in
        ...
    
    ApiRequest().get(from: endpoint, onError:  (_) in 
        ...
    , onSuccess: successCb)

由于您将 successCb 定义为需要 GetUnreadCountsResponse 模型,因此 API get 方法泛型在运行时将是 GetUnreadCountsResponse 类型。

祝你好运!

【讨论】:

好的,但这不是我的情况。在我的模型中,我有很多扩展 ObjectModel 的实体。所以我的字段类型是通用的。我不能为每种对象类型设置一个字段。在您的示例中,我需要的是一个字段,比如说 Decodable 类型的“媒体”(或扩展 Decodable 的类型),然后动态解码该字段而无需 switch/if 感谢您的回答。问题是当尝试在 init(from: decoder) 方法中应用相同的逻辑时,返回的类型是超类的类型,而不是实际的类。

以上是关于Swift - 将类型动态传递给 JSONDecoder的主要内容,如果未能解决你的问题,请参考以下文章

将类型传递给通用 Swift 扩展,或者理想情况下推断它

Swift:如何将可编码类型作为函数输入传递给编码或解码 json

Swift 3:如何动态加载图像并将变量传递给第二个视图控制器?

Swift - 值类型和引用类型的区别

Swift 3使用选择器将按钮/手势识别器动作传递给其他功能

在 Swift 中传递值类型作为引用