Home > Enterprise >  Swift Codable init manually a property
Swift Codable init manually a property

Time:12-30

I have struct that conforms to protocol Codable. Some properties are decoded from JSON fields so I use the CodingKeys enum for that. But there are few properties that are not in my JSON and I need to calculate them from decoded JSON properties. For example, if you get a Zip code from JSON, I want to calculate City from it.

I don't want City to be an optional String. So I try to calculate it right after my Zip code field is decoded from JSON.

struct Place: Codable {
   var name: String
   var zipcode: String
   // ... Lot of other properties decoded from JSON

   var city: String // This property has to be calulated after `zip code` is decoded

   enum CodingKeys: String, CodingKey {
      case name = "placeName"
      case zipcode = "NPA"
      // other properties from JSON
   }
}

I've tried this solution to rewrite init(from decoder: Decoder). But that means I need to manually write each property I need to decode. As I have a lot, I would prefer to let default init decoder does it job, and then add my code to calculate City.

Is there a way to do something like : call default init with decoder, then add some code ?

I was also thinking about computed property. But as calculating City from Zip code is quite lot of code, I don't want that it is always computed.

I need something like :

init(from decoder: Decoder) throws {
   // <- Call default init from decoder
   city = CityHelper(from: zipcode) // quite heavy code in there
}

CodePudding user response:

I would prefer to let default init decoder does it job, and then add my code to calculate City

Unfortunately you can't. It is currently all or nothing; you cannot treat the synthesized init as some sort of inheritance from super (as in your imagined Call default init).

I was also thinking about computed property. But as calculating City from Zip code is quite lot of code, I don't want that it is always computed.

Use a lazy var property whose initializer calls a method that transforms zip to city. That way it is calculated, but just once. The zip will not change, so this is an acceptable compromise.

Or even better, use a reducer to transform the decoded struct (with zip) into a completely different struct (with city).

CodePudding user response:

Ok misread at first. Here's something I think could help: use willSet or didSet on zip to then compute the city perhaps? Not sure if that gets you around not having a default value but this code will only run if the zipcode changes

struct Place: Codable {
   var name: String
    var zipcode: String{
        didSet{
            //do something after zip is set like calculate city?
        }
        willSet{
            //do something before zip is set.
        }
    }
   // ... Lot of other properties decoded from JSON

   var city: String // This property has to be calulated after `zip code` is decoded

   enum CodingKeys: String, CodingKey {
      case name = "placeName"
      case zipcode = "NPA"
      // other properties from JSON
   }
}

CodePudding user response:

You can use a computed property that relies on the zip to return a city. So you'd have to figure out the logic of how to map zip back to city with some method but here's a simple example I cooked up:

struct Rectangle : Codable{
        var width : Float
        var height : Float
        
        var area : Float{
            return width * height
        }
    }

Usage:

let myRect = Rectangle(width: 5.0, height: 5.0)
 print(myRect.area)

Result: 25.0

  • Related