简体   繁体   English

如果我在 swift 的结构中使用协议类型,我的结构不符合协议“可解码”/“可编码”

[英]My structure does not conform to protocol 'Decodable' / 'Encodable' if I use protocol type in my structure in swift

Here I am trying to read data from json file, and casting it dynamically.在这里,我试图从 json 文件中读取数据,并动态转换它。 But if I use prototype in struct it's showing me does not conform to protocol 'Decodable' / 'Encodable' error.但是,如果我在结构中使用原型,则表明我does not conform to protocol 'Decodable' / 'Encodable'错误。 Please let me know if I am missing something here.如果我在这里遗漏了什么,请告诉我。

struct ScreenData: Codable {
    var id: String
    var objectid : String
    var config : UIConfig
}

protocol UIConfig: class, Codable{
    var bgColor : String? { get set }
}

class LabelConfig : UIConfig {
    var bgColor: String?
    var label : String? = ""
}

class ButtonConfig : UIConfig {
    var bgColor: String?
    var btn_label : String = ""
    //var btn_text_color : UIColor = .black
}

Here I am reading data from json file and adding component in stack view depending on data在这里,我正在从 json 文件中读取数据,并根据数据在堆栈视图中添加组件

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
//        create stack view to add components
        let stackView = UIStackView()
        stackView.axis = NSLayoutConstraint.Axis.vertical
        stackView.distribution = .fill
        stackView.alignment = .fill
        stackView.spacing = 10
        stackView.backgroundColor = .gray
        
        var screenData = [ScreenData]()
//        read components from json
        screenData =  loadScreen()
        //print("viewDidLoad screenData : \(screenData)")
        for data in screenData {
            let subView = loadScreenView(data: data, objectId: data.objectid)
            //add components in stack view
            stackView.addArrangedSubview(subView)
        }
        
        self.view.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10).isActive = true
        stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 10).isActive = true
        
        stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
        
        stackView.heightAnchor.constraint(equalToConstant: 200).isActive = true
    }
    
// function to laod data from json
    func loadScreen() -> [ScreenData] {
        var jsonData = [ScreenData]()
        if let fileLocation = Bundle.main.url(forResource: "screen_data", withExtension: "json"){
            do{
                let data = try Data(contentsOf: fileLocation)
                let jsonDecoder = JSONDecoder()
                let dataFromJson =  try jsonDecoder.decode([ScreenData].self, from: data)
                jsonData = dataFromJson
            }catch{
                print(error)
            }
        }
        //print("loadScreen screenData :: \(jsonData)")
        return jsonData
    }


Here I check the object type, and depending on that cast the config

    func loadScreenView(data : ScreenData,objectId : String) -> UIView {
        var view = UIView()
        if(objectId == "bd_label"){
            print("bd_label")
            let labelView = UILabel()
            //labelView.sizeToFit()
            let config = data.config as! LabelConfig
            labelView.text = config.label
            labelView.widthAnchor.constraint(equalToConstant: 300).isActive = true
            labelView.heightAnchor.constraint(equalToConstant: 35).isActive = true
            view = labelView
        }
        if(objectId.elementsEqual("bd_button")){
            print("bd_button")
            let buttonView = UIButton()
            let config = data.config as! ButtonConfig
            
            buttonView.setTitle(config.btn_label, for:.normal)
            buttonView.backgroundColor = .blue
            buttonView.widthAnchor.constraint(equalToConstant: 200).isActive = true
            buttonView.heightAnchor.constraint(equalToConstant: 35).isActive = true
            view = buttonView
        }
        if(objectId == "bd_input"){
            print("bd_input")
            let inputView = UITextView()
            let config = data.config as! InputConfig
            
            inputView.text = config.placeholder
            inputView.backgroundColor = .white
            inputView.widthAnchor.constraint(equalToConstant: 300).isActive = true
            inputView.heightAnchor.constraint(equalToConstant: 35).isActive = true
            view = inputView
        }
        
        return view
    }
    

}

JSONDecoder needs to know the concrete type of thing that you want to decode the JSON into. JSONDecoder需要知道要将 JSON 解码为的具体类型。 After all, everything must have a concrete type at runtime, that you can get with type(of:) .毕竟,一切都必须在运行时具有具体类型,您可以使用type(of:)获得。 You can't tell it to just "decode a protocol".你不能告诉它只是“解码协议”。 The encoder is a bit different though - it doesn't actually need to know the concrete type, and there is a way to get around it.编码器有点不同 - 它实际上不需要知道具体类型,并且有一种方法可以绕过它。

It seems like the type of UIConfig depends on objectid , so we can check objectid and decide what type of UIConfig to decode:看起来UIConfig的类型取决于objectid ,所以我们可以检查objectid并决定要解码的UIConfig类型:

enum CodingKeys: CodingKey {
    case id, objectid, config
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    id = try container.decode(String.self, forKey: .id)
    objectid = try container.decode(String.self, forKey: .objectid)
    if objectid == "bd_label" {
        config = try container.decode(LabelConfig.self, forKey: .config)
    } else if objectid == "bd_button" {
        config = try container.decode(ButtonConfig.self, forKey: .config)
    } 
    // other cases...
    else {
        throw DecodingError.dataCorruptedError(forKey: .config, in: container, debugDescription: "no suitable config type found for objectid \(objectid)!")
    } 
}

For the Encodable part, you can make a "type eraser"-like thingy:对于Encodable部分,您可以制作类似“类型橡皮擦”的东西:

struct AnyEncodable: Encodable {
    let encodeFunction: (Encoder) throws -> Void
    
    init(_ encodable: Encodable) {
        encodeFunction = encodable.encode(to:)
    }
    
    func encode(to encoder: Encoder) throws {
        try encodeFunction(encoder)
    }
}

and do:并做:

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(id, forKey: .id)
    try container.encode(objectid, forKey: .objectid)
    try container.encode(AnyEncodable(config), forKey: .config)
}

By using AnyEncodable , we are basically wrapping the protocol in a concrete type, but don't worry - this won't actually create an extra pair of curly brackets in the JSON.通过使用AnyEncodable ,我们基本上将协议包装在一个具体的类型中,但不用担心 - 这实际上不会在 JSON 中创建一对额外的花括号。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 类型“”不符合协议“可编码” - Type ' ' does not conform to protocol 'Encodable' Swift 4.2:类型“ T”不符合协议“可解码” - Swift 4.2 : Type 'T' does not conform to protocol 'Decodable' 类型“ AreaData”不符合协议“可编码” - Type 'AreaData' does not conform to protocol 'Encodable' SwiftUI-类型“服务”不符合协议“可解码” - SwiftUI - Type 'Service' does not conform to protocol 'Decodable' Argo:类型不符合“可解码”协议 - Argo: Type does not conform to protocol 'Decodable' 如何使用Decodable协议将此JSON转换为Swift结构? - How do I convert this JSON into a Swift structure using the Decodable protocol? swift 将字典转换为 jsonString 错误:协议类型 'Any' 不能符合 'Encodable' 因为只有具体类型才能符合协议 - swift Convert dictionary to jsonString error : Protocol type 'Any' cannot conform to 'Encodable' because only concrete types can conform to protocols 如何在 Swift 4 的可解码协议中使用自定义键? - How do I use custom keys with Swift 4's Decodable protocol? 类型“字符串”不符合协议“ NSCopying”-数组swift json错误 - Type 'String' does not conform to protocol 'NSCopying' - Array swift json Error Swift,ObjectMapper:类型“用户”不符合协议“可映射” - Swift, ObjectMapper: Type 'User' does not conform to protocol 'Mappable'
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM