我应该对可以从JSON解析的对象模型的属性使用optional吗?

Har*_*ngk 21 rest json optional ios swift

我的iOS应用程序有一个非常常见的设置:它向使用JSON对象响应的API服务器进行HTTP查询.然后将这些JSON对象解析为适当的Swift对象.

最初,我将属性划分为必需的属性和可选属性,主要基于我的API服务器的数据库要求.例如id,emailname如此他们使用非可选类型需要的字段.其他人可以NULL在数据库中,因此它们是可选类型.

class User {
  let id: Int
  let email: String
  let profile: String?
  let name: String
  let motive: String?
  let address: String?
  let profilePhotoUrl: String?
}
Run Code Online (Sandbox Code Playgroud)

最近,我开始怀疑这是否是一个很好的设置.我发现尽管某些属性可能总是在数据库中,但这并不意味着这些属性将始终包含在JSON响应中.

例如,在"用户配置文件"页面中,需要所有这些字段才能正确显示视图.因此,JSON响应将包括所有这些字段.但是,对于列出用户名称的视图,我不需要email或者id,JSON响应也可能不包括这些属性.不幸的是,这将导致错误和解析JSON响应到斯威夫特对象时,因为应用程序崩溃预计的应用id,email,name是永远不会比零.

我正在考虑将Swift对象的所有属性更改为可选对象,但感觉就像丢掉了这种特定于语言的功能的所有好处.此外,我将不得不编写更多代码行来打开应用程序中其他地方的所有这些选项.

另一方面,JSON对象本质上不能与Swift的严格静态类型和nil检查非常互操作,所以最好简单地接受这种烦恼.

我应该过渡到每个属性作为选项的模型吗?或者,还有更好的方法?我很感激这里有任何评论.

Cha*_*tka 15

你有三种方法可以解决这个问题:

  1. 始终发送所有JSON数据,并使您的属性不可选.

  2. 使所有属性可选.

  3. 使所有属性不可选,并编写自己的init(from:)方法以将缺省值分配给缺失值,如本答案中所述.

所有这些都应该奏效; 哪一个是"最好的"是基于意见的,因此超出了Stack Overflow答案的范围.选择最方便满足您特殊需求的产品.


rob*_*off 9

首先要问的是:"列出用户名称的视图"的元素是否需要与"用户个人资料页面"后面的模型对象是同一类型的对象?也许不是.也许你应该专门为用户列表创建一个模型:

struct UserList: Decodable {

    struct Item: Decodable {
        var id: Int
        var name: String
    }

    var items: [Item]

}
Run Code Online (Sandbox Code Playgroud)

(虽然问题说JSON响应可能不包括id,但它似乎不是没有id的用户列表特别有用,所以我在这里需要它.)

如果你真的希望它们是同一种对象,那么你可能希望将用户建模为具有服务器始终发送的核心属性,以及可能为零的"详细信息"字段:

class User: Decodable {
    let id: Int
    let name: String
    let details: Details?

    struct Details: Decodable {
        var email: String
        var profile: String?
        var motive: String?
        var address: String?
        var profilePhotoUrl: String?
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)
        details = container.contains(.email) ? try Details(from: decoder) : nil
    }

    enum CodingKeys: String, CodingKey {
        case id
        case name

        case email // Used to detect presence of Details
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我创建了Details,如果它存在,使用Details(from: decoder),而不是通常container.decode(Details.self, forKey: .details).我这样做是Details(from: decoder)为了使得属性Details来自与JSON对象相同的属性User,而不是需要嵌套对象.


sta*_*Man 5

前提:

部分表示是 REST 中的常见模式。这是否意味着 Swift 中的所有属性都需要是可选项?例如,客户端可能只需要一个视图的用户 ID 列表。这是否意味着所有其他属性(姓名、电子邮件等)都需要标记为可选?这是 Swift 的好习惯吗?

模型中可选的标记属性仅表示密钥可能会或可能不会出现。它允许读者在第一眼中了解有关模型的某些信息。
如果您只为不同的 API 响应结构维护一个通用模型,并且将所有属性都设为可选,那么这是否是好的做法是很有争议的。
我已经这样做了,它咬人。有时很好,有时只是不够清楚。

为多个 API 保留一个模型就像设计一个ViewController具有许多 UI 元素的模型,并根据特定情况确定应显示或不显示哪些 UI 元素。
这增加了新开发人员的学习曲线,因为需要更多地了解系统。


我的 2 美分:

假设我们继续使用 Swift 的Codable编码/解码模型,我会将其分解为单独的模型,而不是维护具有所有可选和/或默认值的通用模型。

我的决定的原因是:

  1. 分离度

    • 每个型号都有特定用途
    • 更清洁的自定义解码器的范围
      • 当 json 结构需要一点预处理时很有用
  2. 考虑稍后可能会出现的特定于 API 的附加密钥

    • 如果这个用户列表 API 是唯一需要更多键的 API,比如朋友数量或其他一些统计信息,该怎么办?
      • 我是否应该继续加载一个模型来支持不同的情况,其中额外的密钥只出现在一个 API 响应中,而不出现在另一个 API 响应中?
      • 如果第三个 API 旨在获取用户信息,但这次的目的略有不同,该怎么办?我应该用更多的键重载同一个模型吗?
    • 使用单一模型,随着项目的继续进展,现在非常基于 API 案例的关键可用性可能会变得混乱。由于所有都是可选的,我们将有很多可选的绑定,也许还有一些快捷的 nil 合并,我们本来可以用专用模型避免的。
  3. 编写模型很便宜,但维护案例则不然。

但是,如果我很懒惰并且我强烈感觉疯狂的变化不会出现,我会继续制作所有键可选并承担相关成本。