从嵌套容器解码 JSON 并动态检查其类型以在 swift 中进行类型转换
Decode JSON from the nested container and check its type dynamically for typecasting in swift
这是我要解码的 JSON。它在屏幕中有不同类型的对象数组,所以我想根据它的 objectid 投射每个对象。例如,如果它的对象 ID 是 bd_label 那么它应该是 LabelConfig 类型。
{
"objectid": "template",
"version": {
"major": "2",
"minor": "1"
},
"screens":[
{
"id":"1",
"objectid":"bd_label",
"height": "100",
"width" : "50",
"label": "it's a label"
},
{
"id":"2",
"objectid":"bd_input",
"height": "100",
"width" : "50",
"placeholder": "enter your input"
},
{
"id":"3",
"objectid":"bd_button",
"height":"100",
"width" : "50",
"btn_label":
[
"click",
" the",
" button"
]
}
]
}
我想解码它,为此我尝试了以下结构:
struct Version : Codable{
var major : String
var minor : String
}
protocol ComponentConfig: class, Codable{
var id : String { get set }
var objectid : String { get set }
}
class LabelConfig : ComponentConfig {
var id: String
var objectid : String
var height : String?
var width : String?
var label : String?
}
class ButtonConfig : ComponentConfig {
var id: String
var objectid : String
var height : String?
var width : String?
var btn_label : [String]
}
class InputConfig : ComponentConfig {
var id: String
var objectid : String
var height : String?
var width : String?
var placeholder : String?
}
在这里,我想根据该对象的 objectid 属性 来决定要动态解码的 UIConfig 类型,即 LabelConfig
或 ButtonConfig
或 InputConfig
。
struct ScreenData: Decodable {
var objectid : String
var version: Version
var screens : [ComponentConfig]
enum CodingKeys: CodingKey {
case objectid, version, screens
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
objectid = try container.decode(String.self, forKey: .objectid)
version = try container.decode(Version.self, forKey: .version)
}
}
使用继承很简单
import SwiftUI
class CustomJSONViewModel: ObservableObject {
var configObject: ConfigObject?{
do {
let decodedResponse = try JSONDecoder().decode(ConfigObject.self, from: jsonData!)
return decodedResponse
} catch {
print(error)
return nil
}
}
let jsonData = """
{
"objectid": "template",
"version": {
"major": "2",
"minor": "1"
},
"screens":[
{
"id":"1",
"objectid":"bd_label",
"height": "100",
"width" : "50",
"label": "it's a label"
},
{
"id":"2",
"objectid":"bd_input",
"height": "100",
"width" : "50",
"placeholder": "enter your input"
},
{
"id":"3",
"objectid":"bd_button",
"height":"100",
"width" : "50",
"btn_label":
[
"click",
" the",
" button"
]
}
]
}
""".data(using: .utf8)
}
struct CustomJSONView: View {
@StateObject var vm: CustomJSONViewModel = CustomJSONViewModel()
var body: some View {
List{
Text(vm.configObject?.objectid ?? "nil")
Text(vm.configObject?.version.major ?? "nil")
Text(vm.configObject?.version.minor ?? "nil")
if vm.configObject?.screens != nil{
ForEach(vm.configObject!.screens, id: \.id){ screen in
//To access the specific properties you have to detect the type
if screen is BDInput{
//Once you know what type it is you can force it into that type
Text((screen as! BDInput).placeholder ?? "nil placeholder").foregroundColor(.green)
}else if screen is BDLabel{
Text((screen as! BDLabel).label ?? "nil label").foregroundColor(.orange)
}else if screen is BDButton{
Text((screen as! BDButton).btnLabel?.first ?? "nil button").foregroundColor(.blue)
}else{
//You would default to Screen if the type in unknown
Text(screen.objectid)
}
}
}
}
}
}
// MARK: - ConfigObject
class ConfigObject: Codable {
let objectid: String
let version: Version
let screens: [Screen]
init(objectid: String, version: Version, screens: [Screen]) {
self.objectid = objectid
self.version = version
self.screens = screens
}
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.objectid = try coder.decode(String.self, forKey: .objectid)
self.version = try coder.decode(Version.self, forKey: .version)
//
let container = try decoder.container(keyedBy: CodingKeys.self)
var objectsArray = try container.nestedUnkeyedContainer(forKey: CodingKeys.screens)
var items = [Screen]()
var array = objectsArray
while !objectsArray.isAtEnd {
let object = try objectsArray.nestedContainer(keyedBy: Screen.CodingKeys.self)
let type = try object.decode(String.self, forKey: Screen.CodingKeys.objectid)
//Here is where the decision is made to decode as the specific type/objectid
switch type {
case "bd_label":
items.append(try array.decode(BDLabel.self))
case "bd_input":
items.append(try array.decode(BDInput.self))
case "bd_button":
items.append(try array.decode(BDButton.self))
default:
items.append(try array.decode(Screen.self))
}
}
self.screens = items
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case objectid, version, screens
}
}
// MARK: - Screen
///The different types will be a Screen
class Screen: Codable {
let id, objectid, height, width: String
enum CodingKeys: String, CodingKey {
case id, objectid, height, width
}
init(id: String, objectid: String, height: String, width: String) {
self.id = id
self.objectid = objectid
self.height = height
self.width = width
}
//This superclass would do all the work for the common properties
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.id = try coder.decode(String.self, forKey: .id)
self.objectid = try coder.decode(String.self, forKey: .objectid)
self.height = try coder.decode(String.self, forKey: .height)
self.width = try coder.decode(String.self, forKey: .width)
}catch{
print(error)
throw error
}
}
}
// MARK: - BDLabel
class BDLabel: Screen {
//Each subclass would handle their individual properties
let label: String?
init(id: String, objectid: String, height: String, width: String, label: String?) {
self.label = label
super.init(id: id, objectid: objectid, height: height, width: width)
}
//Each subclass would handle their individual properties
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.label = try coder.decode(String.self, forKey: .label)
//Sending the super class work to be done by Screen
try super.init(from: decoder)
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case label
}
}
// MARK: - BDInput
class BDInput: Screen {
//Each subclass would handle their individual properties
let placeholder: String?
init(id: String, objectid: String, height: String, width: String, placeholder: String?) {
self.placeholder = placeholder
//Sending the super class work to be done by Screen
super.init(id: id, objectid: objectid, height: height, width: width)
}
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.placeholder = try coder.decode(String.self, forKey: .placeholder)
try super.init(from: decoder)
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case placeholder
}
}
// MARK: - BDButton
class BDButton: Screen {
//Each subclass would handle their individual properties
let btnLabel: [String]?
init(id: String, objectid: String, height: String, width: String, btnLabel: [String]?) {
self.btnLabel = btnLabel
//Sending the super class work to be done by Screen
super.init(id: id, objectid: objectid, height: height, width: width)
}
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.btnLabel = try coder.decode([String].self, forKey: .btnLabel)
//Sending the super class work to be done by Screen
try super.init(from: decoder)
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case btnLabel = "btn_label"
}
}
// MARK: - Version
struct Version: Codable {
let major, minor: String
}
struct CustomJSONView_Previews: PreviewProvider {
static var previews: some View {
CustomJSONView()
}
}
这是我要解码的 JSON。它在屏幕中有不同类型的对象数组,所以我想根据它的 objectid 投射每个对象。例如,如果它的对象 ID 是 bd_label 那么它应该是 LabelConfig 类型。
{
"objectid": "template",
"version": {
"major": "2",
"minor": "1"
},
"screens":[
{
"id":"1",
"objectid":"bd_label",
"height": "100",
"width" : "50",
"label": "it's a label"
},
{
"id":"2",
"objectid":"bd_input",
"height": "100",
"width" : "50",
"placeholder": "enter your input"
},
{
"id":"3",
"objectid":"bd_button",
"height":"100",
"width" : "50",
"btn_label":
[
"click",
" the",
" button"
]
}
]
}
我想解码它,为此我尝试了以下结构:
struct Version : Codable{
var major : String
var minor : String
}
protocol ComponentConfig: class, Codable{
var id : String { get set }
var objectid : String { get set }
}
class LabelConfig : ComponentConfig {
var id: String
var objectid : String
var height : String?
var width : String?
var label : String?
}
class ButtonConfig : ComponentConfig {
var id: String
var objectid : String
var height : String?
var width : String?
var btn_label : [String]
}
class InputConfig : ComponentConfig {
var id: String
var objectid : String
var height : String?
var width : String?
var placeholder : String?
}
在这里,我想根据该对象的 objectid 属性 来决定要动态解码的 UIConfig 类型,即 LabelConfig
或 ButtonConfig
或 InputConfig
。
struct ScreenData: Decodable {
var objectid : String
var version: Version
var screens : [ComponentConfig]
enum CodingKeys: CodingKey {
case objectid, version, screens
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
objectid = try container.decode(String.self, forKey: .objectid)
version = try container.decode(Version.self, forKey: .version)
}
}
使用继承很简单
import SwiftUI
class CustomJSONViewModel: ObservableObject {
var configObject: ConfigObject?{
do {
let decodedResponse = try JSONDecoder().decode(ConfigObject.self, from: jsonData!)
return decodedResponse
} catch {
print(error)
return nil
}
}
let jsonData = """
{
"objectid": "template",
"version": {
"major": "2",
"minor": "1"
},
"screens":[
{
"id":"1",
"objectid":"bd_label",
"height": "100",
"width" : "50",
"label": "it's a label"
},
{
"id":"2",
"objectid":"bd_input",
"height": "100",
"width" : "50",
"placeholder": "enter your input"
},
{
"id":"3",
"objectid":"bd_button",
"height":"100",
"width" : "50",
"btn_label":
[
"click",
" the",
" button"
]
}
]
}
""".data(using: .utf8)
}
struct CustomJSONView: View {
@StateObject var vm: CustomJSONViewModel = CustomJSONViewModel()
var body: some View {
List{
Text(vm.configObject?.objectid ?? "nil")
Text(vm.configObject?.version.major ?? "nil")
Text(vm.configObject?.version.minor ?? "nil")
if vm.configObject?.screens != nil{
ForEach(vm.configObject!.screens, id: \.id){ screen in
//To access the specific properties you have to detect the type
if screen is BDInput{
//Once you know what type it is you can force it into that type
Text((screen as! BDInput).placeholder ?? "nil placeholder").foregroundColor(.green)
}else if screen is BDLabel{
Text((screen as! BDLabel).label ?? "nil label").foregroundColor(.orange)
}else if screen is BDButton{
Text((screen as! BDButton).btnLabel?.first ?? "nil button").foregroundColor(.blue)
}else{
//You would default to Screen if the type in unknown
Text(screen.objectid)
}
}
}
}
}
}
// MARK: - ConfigObject
class ConfigObject: Codable {
let objectid: String
let version: Version
let screens: [Screen]
init(objectid: String, version: Version, screens: [Screen]) {
self.objectid = objectid
self.version = version
self.screens = screens
}
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.objectid = try coder.decode(String.self, forKey: .objectid)
self.version = try coder.decode(Version.self, forKey: .version)
//
let container = try decoder.container(keyedBy: CodingKeys.self)
var objectsArray = try container.nestedUnkeyedContainer(forKey: CodingKeys.screens)
var items = [Screen]()
var array = objectsArray
while !objectsArray.isAtEnd {
let object = try objectsArray.nestedContainer(keyedBy: Screen.CodingKeys.self)
let type = try object.decode(String.self, forKey: Screen.CodingKeys.objectid)
//Here is where the decision is made to decode as the specific type/objectid
switch type {
case "bd_label":
items.append(try array.decode(BDLabel.self))
case "bd_input":
items.append(try array.decode(BDInput.self))
case "bd_button":
items.append(try array.decode(BDButton.self))
default:
items.append(try array.decode(Screen.self))
}
}
self.screens = items
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case objectid, version, screens
}
}
// MARK: - Screen
///The different types will be a Screen
class Screen: Codable {
let id, objectid, height, width: String
enum CodingKeys: String, CodingKey {
case id, objectid, height, width
}
init(id: String, objectid: String, height: String, width: String) {
self.id = id
self.objectid = objectid
self.height = height
self.width = width
}
//This superclass would do all the work for the common properties
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.id = try coder.decode(String.self, forKey: .id)
self.objectid = try coder.decode(String.self, forKey: .objectid)
self.height = try coder.decode(String.self, forKey: .height)
self.width = try coder.decode(String.self, forKey: .width)
}catch{
print(error)
throw error
}
}
}
// MARK: - BDLabel
class BDLabel: Screen {
//Each subclass would handle their individual properties
let label: String?
init(id: String, objectid: String, height: String, width: String, label: String?) {
self.label = label
super.init(id: id, objectid: objectid, height: height, width: width)
}
//Each subclass would handle their individual properties
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.label = try coder.decode(String.self, forKey: .label)
//Sending the super class work to be done by Screen
try super.init(from: decoder)
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case label
}
}
// MARK: - BDInput
class BDInput: Screen {
//Each subclass would handle their individual properties
let placeholder: String?
init(id: String, objectid: String, height: String, width: String, placeholder: String?) {
self.placeholder = placeholder
//Sending the super class work to be done by Screen
super.init(id: id, objectid: objectid, height: height, width: width)
}
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.placeholder = try coder.decode(String.self, forKey: .placeholder)
try super.init(from: decoder)
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case placeholder
}
}
// MARK: - BDButton
class BDButton: Screen {
//Each subclass would handle their individual properties
let btnLabel: [String]?
init(id: String, objectid: String, height: String, width: String, btnLabel: [String]?) {
self.btnLabel = btnLabel
//Sending the super class work to be done by Screen
super.init(id: id, objectid: objectid, height: height, width: width)
}
required public init(from decoder: Decoder) throws {
do{
let coder = try decoder.container(keyedBy: CodingKeys.self)
self.btnLabel = try coder.decode([String].self, forKey: .btnLabel)
//Sending the super class work to be done by Screen
try super.init(from: decoder)
}catch{
print(error)
throw error
}
}
enum CodingKeys: String, CodingKey {
case btnLabel = "btn_label"
}
}
// MARK: - Version
struct Version: Codable {
let major, minor: String
}
struct CustomJSONView_Previews: PreviewProvider {
static var previews: some View {
CustomJSONView()
}
}