如果我在 swift 中的结构中使用协议类型,我的结构不符合协议 'Decodable' / 'Encodable'
My structure does not conform to protocol 'Decodable' / 'Encodable' if I use protocol type in my structure in swift
我在这里尝试从 json 文件中读取数据,并动态转换它。但是,如果我在结构中使用原型,它会向我显示 does not conform to protocol 'Decodable' / 'Encodable'
错误。如果我在这里遗漏了什么,请告诉我。
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
}
这里我正在从 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
需要知道要将JSON解码成的具体事物类型。毕竟,一切都必须在运行时具有具体类型,您可以使用 type(of:)
获得。你不能告诉它只是“解码协议”。编码器有点不同 - 它实际上不需要知道具体类型,并且有一种方法可以解决它。
好像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)!")
}
}
对于 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)
}
}
并做:
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)
}
通过使用 AnyEncodable
,我们基本上将协议包装在具体类型中,但请放心 - 这实际上不会在 JSON 中创建一对额外的大括号。
我在这里尝试从 json 文件中读取数据,并动态转换它。但是,如果我在结构中使用原型,它会向我显示 does not conform to protocol 'Decodable' / 'Encodable'
错误。如果我在这里遗漏了什么,请告诉我。
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
}
这里我正在从 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
需要知道要将JSON解码成的具体事物类型。毕竟,一切都必须在运行时具有具体类型,您可以使用 type(of:)
获得。你不能告诉它只是“解码协议”。编码器有点不同 - 它实际上不需要知道具体类型,并且有一种方法可以解决它。
好像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)!")
}
}
对于 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)
}
}
并做:
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)
}
通过使用 AnyEncodable
,我们基本上将协议包装在具体类型中,但请放心 - 这实际上不会在 JSON 中创建一对额外的大括号。