0

I have JSON as below.

{
  "type": "regular",
  "data": [
    {
      "title": "Title 1",
      "price_regular": 1.1,
      "price_express": 2.2,
    },
    {
      "title": "Title 2",
      "price_regular": 1.1,
      "price_express": 2.2,
    }
  ]
}

For this I have model as below.

struct MainModel : Codable {
    var type : String?
    var data : [DataModel]?
}

struct DataModel : Codable {
    var title : String?
    var price_regular : Float?
    var price_express : Float?
    
    var price : Float {
        get {
            if (type == "regular") { ----> Please check below query for this.
                return price_regular ?? 0.0
            }
            
            return price_express ?? 0.0
        }
    }
}

What I am doing in DataModel is create new variable as price. I want to check what data I have for type of main class and based on that I want to get value for Price. Is there way I can achieve this?

Actually I am playing in model. I know I can make this run-time, but I would like to do this in model so that logic is at 1 place only.

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276
  • You can't. You could give `DataModel` an extra property `type`, which will let you know the price. But that would need a custom parsing (of the JSON) by overriding `init(from decoder:)`. Why is this tagged with Android btw? – Larme Dec 13 '21 at 11:52
  • @Larme : Just because this style is used in swift/ android most. so thought to add both tags else just model will not bring more focus on this question. Also I don't to do any overriding as above code will work fine but most important what I want to know how to access variable of previous object. – Fahim Parkar Dec 13 '21 at 11:58
  • @Larme : What I have is type of delivery and based on that I will show prices. Hence I am adding new variable as `price` in model & type I am defining at 1 place only... – Fahim Parkar Dec 13 '21 at 12:00
  • 1
    Since you are writing in Swift, it's not really linked to Android, since language brings some proper logic to it, that's why in my opinion, the Android tag is misleading. Also, it's a OS, so is quite strange. – Larme Dec 13 '21 at 12:11

2 Answers2

1

I'd do it with a custom init(from decoder:), allowing to pass type from MainModel to DataModel.

struct MainModel : Codable {
    var type : String?
    var data : [DataModel]?

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decode(String.self, forKey: .type)
        var dataContainer = try container.nestedUnkeyedContainer(forKey: .data)
        var dataTemp: [DataModel] = []
        while !dataContainer.isAtEnd {
            let dataSubcontainer = try dataContainer.nestedContainer(keyedBy: DataModel.DataModelKeys.self)
            let title = try dataSubcontainer.decode(String.self, forKey: .title)
            let price_regular = try dataSubcontainer.decode(Float.self, forKey: .price_regular)
            let price_express = try dataSubcontainer.decode(Float.self, forKey: .price_express)
            dataTemp.append(DataModel(title: title, price_regular: price_regular, price_express: price_express, type: type))
        }
        data = dataTemp
    }
}

struct DataModel : Codable {
    var title : String?
    var price_regular : Float?
    var price_express : Float?
    var type: String?

    enum DataModelKeys: String, CodingKey {
        case title
        case price_regular
        case price_express
    }

    var price : Float {
        get {
            if (type == "regular") {
                return price_regular ?? 0.0
            }
            return price_express ?? 0.0
        }
    }
}

To test it:

let json = """
[{
"type": "regular",
"data": [
{
  "title": "Title 1",
  "price_regular": 1.1,
  "price_express": 1.2,
},
{
  "title": "Title 2",
  "price_regular": 2.1,
  "price_express": 2.2,
}
]
},
    {
"type": "irregular",
"data": [
{
  "title": "Title 3",
  "price_regular": 1.1,
  "price_express": 1.2,
},
{
  "title": "Title 4",
  "price_regular": 2.1,
  "price_express": 2.2,
}
]
}]
"""

do {
    let model = try JSONDecoder().decode([MainModel].self, from: Data(json.utf8))
    print(model)
    model.forEach { aMainModel in
        print("MainModel type: \(aMainModel.type)")
        aMainModel.data?.forEach {
            print("Title: \($0.title) - type: \($0.type) - price: \($0.price)")
        }
    }
} catch {
    print("Error: \(error)")
}

Which output:

$>MainModel type: Optional("regular")
$>Title: Optional("Title 1") - type: Optional("regular") - price: 1.1
$>Title: Optional("Title 2") - type: Optional("regular") - price: 2.1
$>MainModel type: Optional("irregular")
$>Title: Optional("Title 3") - type: Optional("irregular") - price: 1.2
$>Title: Optional("Title 4") - type: Optional("irregular") - price: 2.2
Larme
  • 24,190
  • 6
  • 51
  • 81
  • Thanks, the way you are doing helped me and I make it another way. Check answer shortly... – Fahim Parkar Dec 13 '21 at 13:26
  • Can you take a look at [this question of mine](https://stackoverflow.com/questions/70321754/scrolltorow-in-uitableview-is-not-taking-to-correct-position) – Fahim Parkar Dec 13 '21 at 13:30
0

With help of @Larme answer, I updated model and made it working.

struct MainModel : Codable {
    var type : String?
    var data : [DataModel]? {
        didSet { // on didSet assigned value of type to type
            for i in 0..<(self.data ?? [DataModel]()).count {
                self.data![i].type = self.type ?? ""
            }
        }
    }
    
    
}

struct DataModel : Codable {
    var title : String?
    var price_regular : Float?
    var price_express : Float?
    
    var type : String? // added new variable
    
    var price : Float? {
        get {
            if (type == "regular") {
                return price_regular ?? 0.0
            }
            return price_express ?? 0.0
        }
    }
}

And this helped me of doing what I was looking for...

Fahim Parkar
  • 30,974
  • 45
  • 160
  • 276
  • Are you sure you code works? I tried it and it didn't, because `didSet` shouldn't be called on `init` methods. You can test it with my sample, which have both possibles values for the types values. `type` of `DataModel` being nil each time, since not set. – Larme Dec 13 '21 at 13:34
  • See https://stackoverflow.com/questions/25230780/is-it-possible-to-allow-didset-to-be-called-during-initialization-in-swift for workarounds and a link to the documentation – Larme Dec 13 '21 at 13:37
  • @Larme : I got your point. Once I have data from API, `didSet` won't be called. I will check further when I implement API. Right now I am preparing data using for loop which is why this is working with me. – Fahim Parkar Dec 13 '21 at 13:49