1

this is my JSON, which I want to decode. It have array of objects in screen, which is of different type, So I want to cast each object depending on it's objectid. e.g if it's object id is bd_label then it should be of type 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"
            ]
            
        }
    ]
}

I want to decode it, For that I have tried Following Strucure:

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?
}

Here, and I want to decide what type of UIConfig i.e. LabelConfig or ButtonConfig or InputConfig to decode dynamically depending on objectid property of that object.

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)
      }
} 
ashwini_j
  • 35
  • 4
  • 3
    A lot of similar questions to this if you do a [search](https://stackoverflow.com/search?q=%5Bswift%5D+decode+json+array+of+different+types) – Joakim Danielson Apr 21 '21 at 12:33
  • @JoakimDanielson Yes, There are. But I havn't got any suitable solution by these. Specially for decoding `[Screen]` where every object is of different type. If you can guid on this part, will appreciate the help. – ashwini_j Apr 21 '21 at 13:46
  • I'd go with a `enum` with associated value. There should be there a question handling that. Basic logic can be seen on the linked questions. – Larme Apr 21 '21 at 15:20

1 Answers1

0

It is simple to do using inheritance

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)
            //https://stackoverflow.com/questions/64182273/how-to-decode-an-array-of-inherited-classes-in-swift
            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()
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48