代码之家  ›  专栏  ›  技术社区  ›  Nicolas Miari

Swift Codable-如何以失败的方式初始化可选枚举属性

  •  0
  • Nicolas Miari  · 技术社区  · 6 年前

    我在试着接受 Codable

    其中一个属性是 枚举 类型,可选: nil 意味着 enum 已经被选中了。

    枚举 Int -基于和开始于 1 , 0 :

    class MyClass: Codable {
    
        enum Company: Int {
            case toyota = 1
            case ford
            case gm
        } 
        var company: Company?
    

    这是因为 在对应的JSON条目上保留为“not set”;i、 e.应映射到 设置初始化时 company 它的财产。

    init?(rawValue:) 提供这种开箱即用的功能: 内景 与任何case的原始值不匹配的参数将导致初始值设定项失败并返回nil。也, 内景 可以使基于(和字符串)的枚举符合 可编码的 只需在类型定义中声明它:

    enum Company: Int, Codable {
        case toyota = 1
        case ford
        case gm
    } 
    

    问题 是的,我的自定义类有20多个属性,所以我真的很想避免实现 init(from:) encode(to:) ,而是依赖于通过提供 CondingKeys 自定义枚举类型。

    这导致整个类实例的初始化失败,因为“合成”初始值设定项似乎无法推断枚举类型的不受支持的原始值应被视为 可选择的 Company? ).

    Decodable 可以抛出,但不能返回零:

    // This is what we have:
    init(from decoder: Decoder) throws
    
    // This is what I would want:
    init?(from decoder: Decoder)
    

    私有的 , 存储 内景 类的属性,该属性仅用作存储,并引入强类型 计算 财产 充当桥梁 在存储和我的应用程序的其余部分之间:

    class MyClass {
    
       // (enum definition skipped, see above)
    
       private var companyRawValue: Int = 0
    
       public var company: Company? {
           set {
               self.companyRawValue = newValue?.rawValue ?? 0
               // (sets to 0 if passed nil)
           }
           get {
               return Company(rawValue: companyRawValue)
               // (returns nil if raw value is 0)
           }
       }
    
       enum CodingKeys: String, CodingKey {
           case companyRawValue = "company"
       }
    
       // etc...
    

    1. 需要重复的属性,如我的工作区,以及
    2. 初始化(从:) 和/或 encode(with:) ,或者实现这些属性的简化版本,这些简化版本在很大程度上委托给默认行为(即不需要手动初始化/编码每个属性的整个样板)?

    附录: 还有第三个, 也不雅观 我第一次发布问题时没有想到的解决方案。它涉及到为了自动解码而使用人工基类。我将不使用它,但为了完整起见,请在此处进行描述:

    // Groups all straight-forward decodable properties
    //
    class BaseClass: Codable {
        /*
         (Properties go here)
         */
    
        enum CodingKeys: String, CodingKey {
            /*
             (Coding keys for above properties go here)
             */
        }
    
        // (init(from decoder: Decoder) and encode(to:) are 
        // automatically provided by Swift)
    }
    
    // Actually used by the app
    //
    class MyClass: BaseClass {
    
        enum CodingKeys: String, CodingKey {
            case company
        }
    
        var company: Company? = nil
    
        override init(from decoder: Decoder) throws {
            super.init(from: decoder)
    
            let values = try decoder.container(keyedBy: CodingKeys.self)
            if let company = try? values.decode(Company.self, forKey: .company) {
                self.company = company
            }
    
        }
    }
    

    2 回复  |  直到 6 年前
        1
  •  4
  •   Max Chuquimia    6 年前

    如果我理解正确的话,我想我和你有类似的问题。在我的例子中,我为每个有问题的枚举编写了一个包装器:

    struct NilOnFail<T>: Decodable where T: Decodable {
    
        let value: T?
    
        init(from decoder: Decoder) throws {
            self.value = try? T(from: decoder) // Fail silently
        }
    
        // TODO: implement Encodable
    }
    

    然后像这样使用:

    class MyClass: Codable {
    
        enum Company: Int {
            case toyota = 1
            case ford
            case gm
        } 
    
        var company: NilOnFail<Company>
    ...
    

    当然,需要注意的是,无论您在哪里需要访问 company 你需要使用 myClassInstance.company.value

        2
  •  2
  •   Nicolas Miari    6 年前

    在文档中搜索 Decoder Decodable 协议和具体 JSONDecoder 班级, 我相信没有办法 去实现我想要的。最接近的是实施 init(from decoder: Decoder) 并手动执行所有必要的检查和转换。


    其他想法

    在对这个问题进行了一些思考之后,我发现了当前设计中的一些问题:首先,映射 0 在对的JSON响应中 nil 好像不对。

    0 在API端有一个“unspecified”的特定含义,通过强制failable init?(rawValue:) 我本质上是把所有无效值合并在一起。如果由于某些内部错误或bug,服务器返回(例如) -7 ,我的代码将无法检测到它,并将静默地将其映射到 ,就好像它是指定的

    正因为如此,我认为 可能是:

    1. 对于 company 属性,并定义 enum

      enum Company: Int {
         case unspecified = 0
         case toyota
         case ford
         case gm
      }
      

      …与JSON非常匹配,或者,

    2. 保持可选性,但是让API返回一个 缺少关键“公司”的价值 )而不是回来 0 (我相信JSON确实有一个“null”值,但我不知道怎么做 处理)

    if let... 比较 .unspecified ).

        3
  •  1
  •   Aura    3 年前

    我知道我的答案迟了,但也许它会帮助别人。

    我也有字符串可选枚举,但是如果我从后端得到一个本地枚举中没有包含的新值,json将不会被解析——即使枚举是可选的。

    我就是这样修复的,不需要实现任何init方法。这样,如果需要,还可以提供默认值而不是nil。

    struct DetailView: Codable {
    
    var title: ExtraInfo?
    var message: ExtraInfo?
    var action: ExtraInfo?
    var imageUrl: String?
    
    // 1
    private var imagePositionRaw: String?
    private var alignmentRaw: String?
    
    // 2
    var imagePosition: ImagePosition {
        ImagePosition.init(optionalRawValue: imagePositionRaw) ?? .top
    }
    
    // 3
    var alignment: AlignmentType? {
        AlignmentType.init(optionalRawValue: alignmentRaw)
    }
    
    enum CodingKeys: String, CodingKey {
        case imagePositionRaw = "imagePosition",
             alignmentRaw = "alignment",
             imageUrl,
             title,
             message,
             action
    }
    

    }

    (1) 从后端获取原始值(string,int-您需要的任何值),然后从这些原始值初始化枚举(2,3)。

    extension RawRepresentable {
      init?(optionalRawValue: RawValue?) {
        guard let rawData = optionalRawValue else { return nil }
        self.init(rawValue: rawData)
      }
    }
    
        4
  •  0
  •   canius    4 年前

    你可以试试 SafeDecoder

    import SafeDecoder
    
    class MyClass: Codable {
    
      enum Company: Int {
        case toyota = 1
        case ford
        case gm
      }
      var company: Company?
    }
    

        5
  •  0
  •   khamitimur    3 年前

    谢谢你详细的提问和回答。你让我重新思考解码JSON的方法。有类似的问题,决定将JSON值解码为Int,而不是将逻辑添加到本应是DTO的内容中。之后,添加模型扩展以将值转换为enum从使用enum的角度看并没有什么区别,但看起来是一个更干净的解决方案。