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

时间:2016-04-01 11:03:56

标签: ios json swift rest optional

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

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

class User {
  let id: Int
  let email: String
  let profile: String?
  let name: String
  let motive: String?
  let address: String?
  let profilePhotoUrl: String?
}

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

例如,在“用户个人资料”页面中,需要所有这些字段才能正确显示视图。因此,JSON响应将包括所有这些字段。但是,对于列出用户名称的视图,我不需要emailid,JSON响应也可能不包含这些属性。不幸的是,这会在将JSON响应解析为Swift对象时导致错误并使应用程序崩溃,因为应用程序希望idemailname始终不为零。

我正在考虑将Swift对象的所有属性更改为可选对象,但这样可能会丢掉这种特定于语言的功能的所有好处。此外,我还需要编写更多代码行,以便在应用程序的其他位置解包所有这些选项。

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

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

11 个答案:

答案 0 :(得分:14)

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

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

  2. 使所有属性都可选。

  3. 将所有属性设置为非可选属性,并编写您自己的init(from:)方法,为缺省值指定默认值,如this answer中所述。

  4. 所有这些都应该有效;哪一个是最好的"是基于意见的,因此超出了Stack Overflow答案的范围。选择最适合您特定需求的那个。

答案 1 :(得分:9)

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

struct UserList: Decodable {

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

    var items: [Item]

}

(虽然问题说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
    }
}

请注意,我使用Details而不是通常的Details(from: decoder)创建container.decode(Details.self, forKey: .details)(如果存在)。我使用Details(from: decoder)来执行此操作,以便Details的属性来自与User的属性相同的JSON对象,而不需要嵌套对象。

答案 2 :(得分:4)

前提:

  

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

在模型中标记属性可选仅表示密钥可能会或可能不会。它允许读者在第一眼看上去了解模型的某些事情 如果您只为不同的API响应结构维护一个通用模型并使所有属性都是可选的,那么这是否是一个好的做法是非常有争议的。
我已经这样做了它咬了。有时它很好,有时候还不够清楚。

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

我的2美分:

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

我决定的理由是:

  1. 分离的清晰度

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

    • 如果此用户列表API是唯一需要更多密钥的用户,例如,朋友数量或其他统计信息,该怎么办?
      • 我是否应该继续加载单个模型以支持不同的情况,其他密钥只有一个API响应,而不是另一个?
      • 如果第三个API旨在获取用户信息,但这次的目的略有不同,该怎么办?我是否应该用更多的密钥过载相同的型号?
    • 使用单一模型,随着项目的不断发展,现在基于API案例的关键可用性可能会变得混乱。随着所有选项,我们将有很多可选的绑定和&也许在这里和那里有一些快捷方式,我们可以首先避免使用专用模型。
  3. 编写模型很便宜,但维护案例不是。
  4. 然而,如果我很懒,而且我有强烈的感觉,未来会出现疯狂的变化,我会继续制作所有关键选项并承担相关费用。

答案 3 :(得分:4)

这实际上取决于您处理数据的方式。如果您通过" Codable"处理您的数据。类,然后你必须编写一个自定义解码器,以便在你没有获得某些预期值时抛出异常。像这样:

 class User: Codable {
    let id: Int
    let email: String
    let profile: String?
    let name: String
    let motive: String?
    let address: String?
    let profilePhotoUrl: String?

     //other methods (such as init, encoder, and decoder) need to be added below.
    }

因为我知道如果我没有获得所需的最低参数,我将需要返回错误,您需要类似错误枚举的内容:

    enum UserCodableError: Error {
         case missingNeededParameters
         //and so on with more cases
    }

您应该使用编码密钥来保持服务器的一致性。在User Object中执行此操作的方法如下:

    fileprivate enum CodingKeys: String, CodingKey {
       case id = "YOUR JSON SERVER KEYS GO HERE"
       case email
       case profile
       case name
       case motive
       case address
       case profilePhotoUrl
    }

然后,你需要编写你的解码器。一种方法就是这样:

    required init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    guard let id = try? values.decode(Int.self, forKey: .id), let email = try? values.decode(String.self, forKey: .email), let name = try? values.decode(String.self, forKey: .name) else {
        throw UserCodableError.missingNeededParameters
    }

    self.id = id
    self.email = email
    self.name = name

    //now simply try to decode the optionals
    self.profile = try? values.decode(String.self, forKey: .profile)
    self.motive = try? values.decode(String.self, forKey: .motive)
    self.address = try? values.decode(String.self, forKey: .address)
    self.profilePhotoUrl = try? values.decode(String.self, forKey: .profilePhotoUrl)
}

注意事项:您应该编写自己的编码器以保持一致。

所有这一切都可以,只需要一个很好的调用声明:

    if let user = try? JSONDecoder().decode(User.self, from: jsonData) {
        //do stuff with user
    }

这可能是处理此类问题的最安全,最迅速,最面向对象的方式。

答案 4 :(得分:3)

如果服务器为其他属性提供Null值,您可以选择选项并安全解包。或者在解包时,如果值为nil

,则可以将空字符串赋值给property
profile = jsonValue ?? ""

其他情况,因为其他属性是String数据类型,您可以将默认值指定为空字符串

class User {
  let id: Int
  let email: String
  let profile: String = ""
  let name: String
  let motive: String = ""
  let address: String = ""
  let profilePhotoUrl: String = ""
}

答案 5 :(得分:3)

我通常会将所有非关键属性设置为可选,然后使用可用的初始化程序。这使我能够更好地处理JSON格式的任何更改或破坏的API契约。

例如:

class User {
  let id: Int
  let email: String
  var profile: String?
  var name: String?
  var motive: String?
  var address: String?
  var profilePhotoUrl: String?
}

这意味着我永远不会有没有id或电子邮件的用户对象(让我们假设那些总是需要与用户关联的用户对象)。如果我获得没有id或电子邮件的JSON有效负载,则User类中的Initializer将失败并且不会创建用户对象。然后,我对失败的初始化程序进行了错误处理。

我更倾向于拥有一个带有可选属性的swift类,而不是一堆带有空字符串值的属性。

答案 6 :(得分:3)

是的,如果API中不需要该属性,并且如果您想在强制属性中使用某个值,则应使用可选项,然后指定空白值:

class User {
  let id: Int?
  let email: String? = ""
  let profile: String?
  let name: String? = ""
  let motive: String?
  let address: String?
  let profilePhotoUrl: String?
}

答案 7 :(得分:3)

我建议将所有non-scalar(String, Custom Types etc) properties保留为可选,scalar(Int, Float, Double etc)作为非可选(有一些例外),方法是指定默认值和带有空数组的集合。 e.g,

class User {
    var id: Int = 0
    var name: String?
    var friends: [User] = []
    var settings: UserSettings?
}

无论服务器发生什么情况,这都可以确保您获得无崩溃的应用程序。我宁愿在崩溃时出现异常行为。

答案 8 :(得分:3)

在我看来,我会选择2种解决方案中的一种:

  1. init funcJSON编辑为object,使用所有道具的默认对象值(id = -1, email = '')初始化,然后使用可选的检查功能读取JSON。
  2. 为该特定情况创建新的class/struct

答案 9 :(得分:0)

与其他选项相比,我更倾向于使用可用的初始化程序。

因此,将所需属性保留为非选项并仅在响应中存在对象时创建对象(可以使用if-let或gaurd-let在响应中检查此对象),否则无法创建对象。 / p>

使用这种方法,我们避免将非选项作为选项,并且难以在整个程序中处理它们。

此外,选项不适用于防御性编程,因此不要通过制作非选择性"来滥用选项。属性作为选项。

答案 10 :(得分:-1)

我更喜欢可选属性,因为您无法承诺JSON值始终存在,并且响应属性名称的任何更改都会导致应用程序崩溃。

如果您不使用可选值,则必须在解析时控制参数,并在需要无崩溃应用时添加默认值。你不会知道它是否是来自服务器的空字符串。

可选值是您最好的朋友。

object mapper用于可变和不可变的属性。

realm-swift表示默认的非可选值。