Swift 使用Codable协议进行json转模型

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 使用Codable协议进行json转模型相关的知识,希望对你有一定的参考价值。

参考技术A 1.序列化和反序列化

2.encode和decode

3.Codable

实际上,Codable就是指的编码和解码协议

4.json转模型
4.1在用原生Codable协议的时候,需要遵守协议Codable,结构体,枚举,类都可以遵守这个协议,一般使用struct

注意,你的json里面的字段可能没有值,因此需要设置可选值
4.2 json数据里面的字段和model字段不一致
解决办法:实现 enum CodingKeys: String, CodingKey 这个映射关系

4.3如果你的模型里面带有嵌套关系,比如你的模型里面有个其他模型或者模型数组,那么只要保证嵌套的模型里面依然实现了对应的协议

4.4如果json里面数据类型和model数据类型不一致,最常见的有(Bool和Int,Int和String)这些在后台弱类型语言上是不加区分
解决办法:定义了一个可能是Bool或者Int的类型

下面是一个Int 或者String类型的

因此在模型设计的时候就可以这样了:

4.4使用JSONDecoder进行json转model

先使用JSONSerialization进行一次序列化操作

看一下decode源码:

可以看到在转model的时候,先进行一次序列化操作,decode内部又进行一次反序列化操作,苹果这样设计估计是在参数传递的时候想让我们传递字节流

至此就可以使用swift原生协议Codable进行json转model了

如何从 Swift Codable 中排除属性?

【中文标题】如何从 Swift Codable 中排除属性?【英文标题】:How to exclude properties from Swift Codable? 【发布时间】:2017-11-23 03:29:34 【问题描述】:

Swift 的Encodable/Decodable 协议与 Swift 4 一起发布,使 JSON(反)序列化非常愉快。但是,我还没有找到一种方法来细粒度地控制哪些属性应该被编码,哪些应该被解码。

我注意到从随附的CodingKeys 枚举中排除该属性会完全将该属性排除在进程之外,但是有没有办法进行更细粒度的控制?

【问题讨论】:

你是说你有一些你想编码的属性,但你想解码不同的属性? (即您希望您的类型不是可往返的?)因为如果您只关心排除该属性,则给它一个默认值并将其排除在 CodingKeys 枚举之外就足够了。 无论如何,您始终可以手动实现Codable 协议(init(from:)encode(to:))的要求,以完全控制流程。 我的具体用例是避免给解码器过多的控制权,这可能导致通过覆盖内部属性值远程获取 JSON。下面的解决方案就足够了! 我希望看到一个答案/新的 Swift 功能,它只需要处理特殊情况和排除的键,而不是重新实现您通常应该免费获得的所有属性。跨度> 【参考方案1】:

要编码/解码的键列表由称为CodingKeys 的类型控制(注意末尾的s)。编译器可以为您合成它,但始终可以覆盖它。

假设您想从编码解码中排除属性nickname

struct Person: Codable 
    var firstName: String
    var lastName: String
    var nickname: String?
    
    private enum CodingKeys: String, CodingKey 
        case firstName, lastName
    


如果您希望它是非对称的(即编码但不解码,反之亦然),您必须提供自己的 encode(with encoder: )init(from decoder: ) 实现:

struct Person: Codable 
    var firstName: String
    var lastName: String
    
    // Since fullName is a computed property, it's excluded by default
    var fullName: String 
        return firstName + " " + lastName
    

    private enum CodingKeys: String, CodingKey 
        case firstName, lastName, fullName
    

    // We don't want to decode `fullName` from the JSON
    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
    

    // But we want to store `fullName` in the JSON anyhow
    func encode(to encoder: Encoder) throws 
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(firstName, forKey: .firstName)
        try container.encode(lastName, forKey: .lastName)
        try container.encode(fullName, forKey: .fullName)
    

【讨论】:

您需要给nickname 一个默认值才能使其工作。否则,将无法为 init(from:) 上的属性分配任何值。 @ItaiFerber 我将它切换为一个可选的,它最初在我的 Xcode 中 您确定必须在非对称示例中提供encode 吗?由于这仍然是标准行为,我认为不需要它。只是decode,因为这就是不对称的来源。 @MarqueIV 是的,你必须这样做。由于fullName 无法映射到存储属性,因此您必须提供自定义编码器和解码器。 刚刚在 Swift 5 中测试过。你应该只需要为你不解码的属性定义一个常量。您无需将密钥显式添加到CodingKeys。所以,var nickname: String get "name" 就足够了。【参考方案2】:

另一种从编码器中排除某些属性的方法,可以使用单独的编码容器

struct Person: Codable 
    let firstName: String
    let lastName: String
    let excludedFromEncoder: String

    private enum CodingKeys: String, CodingKey 
        case firstName
        case lastName
    
    private enum AdditionalCodingKeys: String, CodingKey 
        case excludedFromEncoder
    

    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let anotherContainer = try decoder.container(keyedBy: AdditionalCodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)

        excludedFromEncoder = try anotherContainer(String.self, forKey: . excludedFromEncoder)
    

    // it is not necessary to implement custom encoding
    // func encode(to encoder: Encoder) throws

    // let person = Person(firstName: "fname", lastName: "lname", excludedFromEncoder: "only for decoding")
    // let jsonData = try JSONEncoder().encode(person)
    // let jsonString = String(data: jsonData, encoding: .utf8)
    // jsonString --> "firstName": "fname", "lastName": "lname"


解码器也可以使用同样的方法

【讨论】:

解码器在执行解码时会自动选择正确的容器还是我应该为此定义条件逻辑? @Citizen_5 解码器具有同时使用 encodingkey 枚举(常规 CodingKeys 和自定义枚举)的逻辑,但编码器只知道 CodingKeys,这就是它以这种方式工作的原因。【参考方案3】:

如果我们需要从结构中的大量属性中排除对几个属性的解码,请将它们声明为可选属性。解包选项的代码比在 CodingKey 枚举下写很多键要少。

我建议使用扩展来添加计算实例属性和计算类型属性。它将可编码的一致性属性与其他逻辑分开,因此提供了更好的可读性。

【讨论】:

【参考方案4】:

您可以使用计算属性:

struct Person: Codable 
  var firstName: String
  var lastName: String
  var nickname: String?

  var nick: String 
    get 
      nickname ?? ""
    
  

  private enum CodingKeys: String, CodingKey 
    case firstName, lastName
  

【讨论】:

这是我的线索 - 使用 lazy var 有效地使其成为运行时属性将其从 Codable 中排除。 计算属性不能偷懒,能不能扩展一下思路? @ChrisH @BrunoMuniz 老实说不记得我为什么要发表这个评论,但我很可能指的是 lazy var x:String = //Computed string () 之类的东西,我用它而不是计算属性,尽管我真的无法想象为什么。【参考方案5】:

虽然这可以完成,但最终会变得非常unSwifty甚至unJSONy。我想我知道你从哪里来,#ids 的概念在 HTML 中很普遍,但它很少被传送到 JSON 的世界,我认为这是一个好东西(TM )。

如果您使用递归哈希对其进行重构,一些Codable 结构将能够很好地解析您的JSON 文件,即如果您的recipe 只包含ingredients 的数组,而ingredients 又包含(一个或多个)ingredient_info。这样,解析器将首先帮助您将网络缝合在一起,并且您只需通过简单的遍历结构提供一些反向链接如果您真的需要它们。由于这需要彻底修改您的JSON您的数据结构,我只勾勒出这个想法供您考虑。如果您认为可以接受,请在 cmets 中告诉我,然后我可以进一步详细说明,但根据具体情况,您可能无权更改其中任何一个。

【讨论】:

【参考方案6】:

我已经使用协议及其扩展以及 AssociatedObject 来设置和获取图像(或任何需要从 Codable 中排除的属性)属性。

这样我们就不必实现自己的编码器和解码器了

这是代码,为简单起见保留相关代码:

protocol SCAttachmentModelProtocol
    var image:UIImage? get set
    var anotherProperty:Int get set

extension SCAttachmentModelProtocol where Self: SCAttachmentUploadRequestModel
    var image:UIImage? 
        set
            //Use associated object property to set it
        
        get
            //Use associated object property to get it
        
    

class SCAttachmentUploadRequestModel : SCAttachmentModelProtocol, Codable
    var anotherProperty:Int

现在,每当我们想要访问 Image 属性时,我们都可以在对象上使用来确认协议 (SCAttachmentModelProtocol)

【讨论】:

以上是关于Swift 使用Codable协议进行json转模型的主要内容,如果未能解决你的问题,请参考以下文章

Swift下面字典(json)和模型的转换

如何从 Swift Codable 中排除属性?

Swift4 中的 Codable 和 XMLParser

如何使用 Codable 和 Swift 解析这个嵌套的 JSON?

Swift IOS 无法使用 Codable 访问 json 数组数据

如何使用 Swift 创建 JSON Codable [关闭]