简体   繁体   English

Swift Codable init 手动一个属性

[英]Swift Codable init manually a property

I have struct that conforms to protocol Codable .我有符合协议structCodable Some properties are decoded from JSON fields so I use the CodingKeys enum for that.一些属性是从 JSON 字段解码的,所以我使用CodingKeys枚举。 But there are few properties that are not in my JSON and I need to calculate them from decoded JSON properties.但是我的 JSON 中没有几个属性,我需要从解码的 JSON 属性中计算它们。 For example, if you get a Zip code from JSON, I want to calculate City from it.例如,如果您从 JSON 获得Zip code ,我想从中计算City

I don't want City to be an optional String.我不希望City成为可选字符串。 So I try to calculate it right after my Zip code field is decoded from JSON.所以我尝试在我的Zip code字段从 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 triedthis solution to rewrite init(from decoder: Decoder) .我已经尝试过这个解决方案来重写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 .因为我有很多,我宁愿让默认的初始化解码器完成它的工作,然后添加我的代码来计算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.但是由于从 Zip 代码计算City是相当多的代码,我不希望它总是被计算。

I need something like:我需要类似的东西:

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

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 ).您不能将合成的 init 视为某种来自 super 的 inheritance (如您想象的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.但是由于从 Zip 代码计算 City 是相当多的代码,我不希望它总是被计算。

Use a lazy var property whose initializer calls a method that transforms zip to city.使用惰性 var 属性,其初始化程序调用将 zip 转换为城市的方法。 That way it is calculated, but just once.这样它就被计算出来了,但只有一次。 The zip will not change, so this is an acceptable compromise. zip 不会改变,因此这是一个可以接受的折衷方案。

Or even better, use a reducer to transform the decoded struct (with zip) into a completely different struct (with city).或者更好的是,使用 reducer 将解码的结构(带 zip)转换为完全不同的结构(带城市)。

I have found a solution but it needs to use class instead of struct .我找到了一个解决方案,但它需要使用class而不是struct

I store decoded properties in a parent class.我将解码的属性存储在父 class 中。 Then I extend this class and add calculated properties in it.然后我扩展这个 class 并在其中添加计算属性。

As it is not possible to extend struct , I had to use class .由于无法扩展struct ,我不得不使用class Maybe it is a first step to a better solution and it can help anyone find something better.也许这是迈向更好解决方案的第一步,它可以帮助任何人找到更好的东西。

class DecodedPlace: Codable {
    var name: String
    var zipcode: String
    
    enum CodingKeys: String, CodingKey {
        case name = "placeName"
        case zipcode = "NPA"
    }
}

class Place: DecodedPlace {
    var city: String

    required init(from decoder: Decoder) throws {
        city = "" // Default value just to call super.init.
        
        try super.init(from: decoder)
        
        city = CityHelper.city(from: zipcode)
    }
}

I have to check if using class instead of struct is not too bad.我必须检查使用class而不是struct是否还不错。 Any idea?任何想法?

Ok misread at first.好吧,一开始看错了。 Here's something I think could help: use willSet or didSet on zip to then compute the city perhaps?这是我认为可以帮助的东西:在 zip 上使用 willSet 或 didSet 来计算城市? 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
   }
}

You can use a computed property that relies on the zip to return a city.您可以使用依赖于 zip 的计算属性来返回城市。 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:因此,您必须弄清楚如何通过某种方法将 map zip 返回城市的逻辑,但这是我编写的一个简单示例:

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结果:25.0

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM