【问题标题】:How to make the RealmSwift RealmOptional compatible with Swift Codable?如何使 RealmSwift RealmOptional 与 Swift Codable 兼容?
【发布时间】:2018-07-12 09:31:03
【问题描述】:

我面临一个问题,我无法使 RealmOptional 与带有 json 解码器的 swift 新 Codable 功能兼容。

考虑以下 Realm 对象。

class School: Object, Codable {

    @objc dynamic var id: Int64 = 0

    @objc dynamic var name: String?
    var numberOfStudents = RealmOptional<Int64>()
    var classes = List<Class>()

    enum CodingKeys: String, CodingKey {
       case id
       case name
       case numberOfStudents
       case classes
    }
}

class Class: Object, Codable {
    var name: String?
    var numberOfStudents = RealmOptional<Int64>()
}

在这里我们可以将类声明为 Codable,因为我在 this gist 的帮助下为 RealmOptinal 编写了一个扩展。但问题是解码器解码json的时候。

考虑这个json

let jsonData = """
[
    "id": 1234,
    "name": "Shreesha",
    "numberOfStudents": nil,
    "classes": {
       "name": "Class V",
       "numberOfStudents": 12
    }
]
""".data(using: .utf8)!

在这个 json 中,所有的数据都被传递,并且这与代码完美地解码。

let decoder = JSONDecoder()

let decoded = try! decoder.decode(School.self, from: jsonData)

但是,如果我从应该是 RealmOptional 对象的 json 数据中删除 numberOfStudents 键,它将引发错误并且不会解码,因为 RealmOptional 不是快速可选的,因此解码器认为应该有一个键在 json 数据中。在JSONDecoder 中,如果 json 中不存在密钥并且该属性被声明为可选,它不会尝试解码。它只是跳到其他键。

直到现在我还没有重写初始化程序,因为我们拥有RealmOptional Realm Lists 等的所有支持扩展。但是现在我必须重写init(from decoder: Decoder) 来手动解码它,Realm 模型有更多50 个属性(你知道我的意思)。

如果我们覆盖初始化器,我觉得使用 JSONDecoder 没有意义,因为与使用 JSONDecoder 相比,手动工作更多。

required convenience init(from decoder: Decoder) throws {
    self.init()
    let container = try decoder.container(keyedBy: CodingKeys.self)

    id = try container.decodeIfPresent(Int64.self, forKey: .id) ?? 0
    name = try container.decodeIfPresent(String?.self, forKey: .name) ?? ""
    numberOfStudents = try container.decodeIfPresent(RealmOptional<Int64>.self, forKey: .numberOfStudents) ?? RealmOptional<Int64>()

    let classesArray = try container.decode([Class].self, forKey: .classes)
    classes.append(objectsIn: classesArray)
}

那么有人可以向我建议使 RealmOptional 与JSONDecoder 兼容的替代解决方案,这样我们就不必覆盖初始化程序。

【问题讨论】:

    标签: swift realm codable jsondecoder


    【解决方案1】:

    您可以采取以下措施来解决该问题。创建一个支持解码并具有 RealmOptional 作为其属性的新类。

    class OptionalInt64: Object, Decodable {
        private var numeric = RealmOptional<Int64>()
    
        required public convenience init(from decoder: Decoder) throws {
            self.init()
    
            let singleValueContainer = try decoder.singleValueContainer()
            if singleValueContainer.decodeNil() == false {
                let value = try singleValueContainer.decode(Int64.self)
                numeric = RealmOptional(value)
            }
        }
    
        var value: Int64? {
            return numeric.value
        }
    
        var zeroOrValue: Int64 {
            return numeric.value ?? 0
        }
    }
    

    然后,不要在你的学校课程中使用 RealmOptional,而是使用这个新的 OptionalInt64 类,

    class School: Object, Codable {
    
        @objc dynamic var id: Int64 = 0
    
        @objc dynamic var name: String?
        @objc dynamic  var numberOfStudents: OptionalInt64?
        var classes = List<Class>()
    
        enum CodingKeys: String, CodingKey {
           case id
           case name
           case numberOfStudents
           case classes
        }
    }
    

    请注意,现在您使用的是 RealmNumeric 而不是 RealmOptional?这是可选的类型。由于它是可选的,因此自动解码使用 decodeIfPresent 方法来解码可选值。如果它不存在于 json 中,则该值将简单地变为 nil。

    【讨论】:

      【解决方案2】:

      我已将 Sandeep 的解决方案修改为更通用:

      class RealmOptionalCodable<Value: Codable>: Object, Codable where Value: RealmSwift.RealmOptionalType {
      
          private var numeric = RealmOptional<Value>()
      
          var value: Value? {
              get {
                  numeric.value
              }
              set {
                  numeric.value = newValue
              }
          }
      
      
          required public convenience init(from decoder: Decoder) throws {
              self.init()
      
              let singleValueContainer = try decoder.singleValueContainer()
              if singleValueContainer.decodeNil() == false {
                  let value = try singleValueContainer.decode(Value.self)
                  numeric = RealmOptional(value)
              }
          }
      
      }
      

      使用

      @objc dynamic  var numberOfStudents: RealmOptionalCodable<Int>?
      

      【讨论】:

      • 我们如何赋值? wr.integer = RealmOptionalCodable(value: 21) 崩溃
      • 你必须这样分配:numberOfStudents.value = 21
      【解决方案3】:
      1. 在您的领域模型类上方添加@objcMembers。

      2. 如下使用变量

      public dynamic var someValue = RealmOptional&lt;Int&gt;()
      1. 在将值分配给领域可选时,您可以使用 someValue.value = 10

      默认情况下 someValue 将为 nil。

      【讨论】:

        【解决方案4】:

        我找到了this 解决方案,它就像一个魅力。我正在使用来自 srv7 的 comment 的更新代码。

        【讨论】:

        • 我的代码库中已经有了这段代码。我只是想使用 RealmOptional 而不覆盖初始化程序。因为 RealmOptional 不是 swift optional JSONDecoder 认为 json 中应该有一个键值对并尝试解码代码。这就是它抛出错误的原因。我想要解决这个问题。谢谢。
        • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
        【解决方案5】:

        从去年开始,Realm 为可选选项添加了一种新的更简单的方法,使用 @Persisted - docs

        使用方法:

        class Order: Object, Codable {
        
           @Persisted(primaryKey: true) var productOrderId: Int?
        
           @Persisted var name: String?
           @Persisted var standardPrice: Double?
           @Persisted var paid: Bool?
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-31
          相关资源
          最近更新 更多