Codabel JSON转模

官方文档:Using JSON with Custom Types

JSON 介绍:JavaScript Object Notation

Codable 是 Swift 引入的全新的编解码库,使开发者更方便的解析JSON 或 plist 文件。支持枚举、结构体和类。

协议定义如下:

typealias Codable = Decodable & Encodablepublic protocol Decodable {public init(from decoder: Decoder) throws
}
public protocol Encodable {public func encode(to encoder: Encoder) throws
}

解析 JSON

对于 JSON的编码和解析,使用JSONEncoder用于编码为 data,使用JSONDecoder用于解析为Model。

let data = try! JSONEncoder().encode(模型实例)               // 编码
let model = try! JSONDecoder().decode(模型.self, from: data)// 解码

示例json

///  json数据
let json = """
[{"name": "Banana","points": 200,"description": "A banana grown in Ecuador."},{"name": "Orange","points": 100}
]
"""

遵循 Codable 定义模型

/// 模型
struct GroceryProduct: Codable {var name: Stringvar points: Intvar description: String?
}

JSON 解析为Model

// 开始转换
let decoder = JSONDecoder()
// 数组模型转化  [注意:先将json字符串转为data,同时注意捕获异常]
let products = try decoder.decode([GroceryProduct].self, from: json.data(using:.utf8)!)

Codable 支持枚举

原始值类型枚举

枚举通常有两种变体:

  • 原始值(如IntString)支持的变体
  • 包含关联值的变体。自从在Swift 4.0中引入Codable以来,属于前一类别的枚举一直支持编译器合成。

因此,例如,假设我们正在开发一个包含以下String支持枚举的应用程序,该枚举符合Codable

enum MediaType: String, Codable {case articlecase podcastcase video
}

由于编译器能够自动处理使用原始值对枚举进行编码和解码所需的所有代码,我们通常不必编写更多的代码——这意味着我们现在可以在其他Codable类型中使用上述枚举,例如:

struct Item: Codable {var title: Stringvar url: URLvar mediaType: MediaType
}

如果我们现在将上述Item类型的实例编码为JSON,那么我们将获得以下类型的输出(因为MediaType值将使用支持它们的原始String值自动编码和解码):

{"title": "Swift by Sundell","url": "https://swiftbysundell.com/podcast","mediaType": "podcast"
}

关联值枚举

在Swift 5.5之前,如果我们想使包含关联值的枚举符合Codable,那么我们必须手动编写所有代码。然而,swift5.5不再是这样了,因为编译器已经升级,现在它也能够自动生成此类枚举的序列化代码。

例如,以下Video枚举现在可以Codable,而无需我们提供任何自定义代码:

enum Video: Codable {case youTube(id: String)case vimeo(id: String)case hosted(url: URL)
}

要查看上述类型在编码时是什么样子的,让我们创建一个VideoCollection实例,该实例存储Video值数组:

struct VideoCollection: Codable {var name: Stringvar videos: [Video]
}let collection = VideoCollection(name: "Conference talks",videos: [.youTube(id: "ujOc3a7Hav0"),.vimeo(id: "234961067")
]
)

如果我们然后将上述collection值编码为JSON,那么我们将获得以下结果:

{"name": "Conference talks","videos": [{"youTube": {"id": "ujOc3a7Hav0"
}},{"vimeo": {"id": "234961067"
}}]
}

因此,默认情况下,当我们让编译器自动合成带有关联值的枚举的Codable一致性时,在计算该类型的序列化格式时,将使用我们案例的名称及其中关联值的标签。

如果我们想自定义关联值枚举 key,还是通过自定义``CodingKey`,可以参考Codable synthese for Swift enums

进行键转换

如果在后台系统使用的命名规则与前端不一致情况下,我们就需要进行键自定义转换。比如后台字段返回下划线命名法,需要转换为驼峰命名法,所以在字段映射的时候就需要修改一下。

主要有两种方式

  1. 【推荐】实现CodingKey协议 进行枚举映射。
  2. 通过Decoder的keyDecodingStrategy中convertFromSnakeCase统一转化
/// 示例json
let json = """
[{"product_name": "Bananas","product_cost": 200,"description": "A banana grown in Ecuador."},{"product_name": "Oranges","product_cost": 100,"description": "A juicy orange."}
]
""".data(using: .utf8)!
/// 对象模型
struct GroceryProduct: Codable {var name: Stringvar points: Intvar description: String?/// 自定义字段属性/// 注意 1.需要遵守Codingkey  2.每个字段都要枚举private enum CodingKeys: String, CodingKey {case name = "product_name"case points = "product_cost"case description}
}let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)

下划线转化与自定义

某些时候后台使用的是下划线命名法(比如mysql和python),返回的字段名就是下滑线的。swift4.1之后可以自动将下划线命名转化为驼峰命名法。

当然也可以用自定义属性来改变,但是如果字段过多怎很麻烦。

/// 示例json
let json = """
[{"product_name": "Bananas","product_cost": 200,"description": "A banana grown in Ecuador."},{"product_name": "Oranges","product_cost": 100,"description": "A juicy orange."}
]
""".data(using: .utf8)!
/// 对象模型
struct GroceryProduct: Codable {var productName: Stringvar productCost: Int  // 不允许部分转化 部分不转 如product_cost会报错异常var description: String?
}let decoder = JSONDecoder()
/// 通过keyDecodingStrategy 来控制
decoder.keyDecodingStrategy = .convertFromSnakeCase  // 编码策略  使用从蛇形转化为大写 encode时同样也可降驼峰命名法转化为下划线
let products = try decoder.decode([GroceryProduct].self, from: json)

自定义转换规则

字段转化策略除了使用默认的和下划线转驼峰之外,还可以自定义转化规则,比如下面示例,将 key 大写映射到小写。

/// 定义模型
struct Address: Codable {var street: Stringvar zip: Stringvar city: Stringvar state: String
}/// 重点  实现CodingKey协议
struct AnyKey: CodingKey {var stringValue: Stringvar intValue: Int?init?(stringValue: String) {self.stringValue = stringValue}init?(intValue: Int) {self.stringValue = String(intValue)self.intValue = intValue}
}转化模型
let jsonString = """
{"State":"California","Street":"Apple Bay Street","Zip":"94608","City":"Emeryville"}
"""let decoder = JSONDecoder()
/// 建议custom 使用扩展KeyDecodingStrategy [方便管理]
decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in// 转化规则let lastKey = keys.last!guard lastKey.intValue == nil else { return lastKey }let stringValue = lastKey.stringValue.prefix(1).lowercased() + lastKey.stringValue.dropFirst()  /// 将首字母大写的转化为小写的return AnyKey(stringValue: stringValue)!
})if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {print(address)
}/*prints:Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")*/

参考链接:keyDecodingStrategy.customXXX的使用 – StackOverflow

重写Decode 和Encode 方法

Decode 协议和 Encode 协议有默认方法,所以我们可以实现其方法,在编码和解码时进行自定义操作。如多级嵌套字段属性转化。

如下 json 直接获取二级字段作为属性

/// 示例json
let json = """
{"order":{"product_name": "Bananas","product_cost": 10,"description": null}
}
""".data(using: .utf8)!

模型定义,指明对应的CodingKey, 实现Encodable 和Decodable协议方法

/// 对象模型
struct GroceryProduct: Codable {var productName: String  // 第二层字段var productCost: Int     // 第二层字段/// 定义第一层嵌套 编码键private enum OrderKeys: CodingKey {case order}/// 定义第二层嵌套 编码键private enum CodingKeys: String, CodingKey {case productNamecase productCost}/// 实现 Decodable 协议 【tip: 代码提示不会显示init方法,建议进入协议内复制过来】/// 实现 键与属性的映射赋值init(from decoder: Decoder) throws{// 获取对应的容器let orderContainer = try decoder.container(keyedBy: OrderKeys.self)let container = try orderContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: OrderKeys.order)// 对属性赋值productName = try container.decode(String.self, forKey: .productName)productCost = try container.decode(type(of: productCost), forKey: .productCost)}func encode(to encoder: Encoder) throws{var orderContainer =  encoder.container(keyedBy: OrderKeys.self)var container = orderContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: OrderKeys.order)try container.encode(productName, forKey: .productName)try container.encode(productCost, forKey: .productCost)}}

JSON 与Model 转换

let decoder = JSONDecoder()
/// 通过keyDecodingStrategy 来控制
decoder.keyDecodingStrategy = .convertFromSnakeCase
let products = try decoder.decode(GroceryProduct.self, from: json)// 模型对象转化为json
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.keyEncodingStrategy = .convertToSnakeCase
let jsonStr = try! encoder.encode(products)print("模型转json:\(String.init(data: jsonStr, encoding: .utf8))")
//模型转json:Optional("{\n  \"order\" : {\n    \"product_cost\" : 10,\n    \"product_name\" : \"Bananas\"\n  }\n}")

注意事项

模型默认值问题

对于模型的定义,我们有时候希望设置默认值,然而 Codable 却不支持,如果字段不存在或者为 nil,解析时候默认值会崩溃(我也会崩溃,因为一般无法保证后台一定会有该字段)。

"No value associated with key CodingKeys(stringValue: \"description\", intValue: nil) (\"description\")."

这时候建议使用可选属性。

/// 示例json
let json = """{"product_name": "Bananas","product_cost": 10,"description": null}
""".data(using: .utf8)!
/// 对象模型
struct GroceryProduct: Codable {var productName: Stringvar productCost: Intvar description: String?  /// 不能确定一定会返回值或者可能返回null 时 建议使用可选
}let decoder = JSONDecoder()
/// 通过keyDecodingStrategy 来控制
decoder.keyDecodingStrategy = .convertFromSnakeCase
let products = try decoder.decode(GroceryProduct.self, from: json)

万一我就是想添加初始值咋办:CleanJSON , 或者设置该属性为private 新增一个计算下属性外部访问。

类继承

如果你使用类继承,会发现父类属性无法自动解析。

复杂的解决方法,重写init(from decoder: Decoder)方法,内部自行解析子类属性和调用父类解析方法。

简单方法,未知?

let jsonData = """
{"name": "李四","age": 18,"address": "地球"
}
""".data(using: .utf8)!class CodablePart: Codable {let name: Stringlet age: UInt}class WholeModel: CodablePart {let address: stringrequired init(from decoder: Decoder) throws {let container = try decoder.container(keyedBy: CodingKeys.self)address = try container.decode(Int.self, forKey: .uncodableNumber) // 设置子类新增属性try super.init(from: decoder)  // 需要调用父类解析解析方法}
}
extension WholeModel {enum CodingKeys: String, CodingKey {case address}
}
// 解析为 model
do {let model = try JSONDecoder().decode(WholeModel.self, from: jsonData)print(model.name)  // 李四print(model.age)   // 18print(model.address)// 地球
} catch {print(error.localizedDescription)
}

使用 Tips

屏蔽部分属性解析

可以使用 lazy 懒加载方式,既可以赋初始值,也使用了懒加载方式,还可以标记属于业务属性。我们还可以通过 private影藏后台属性,定义计算属性作为业务属性使用。

struct Person: Codable {var name: String?var gender: Int?var address: String?/// 如果json中没有该属性,但是自己想用,可以添加lazy并初始化lazy var isSelected:Bool = false}

扩展转为 Data 和 字典

扩展 Data

extension Data {/// 从遵循`Encodable`的实例转换为 `Data`/// - Parameter model:遵循`Encodable`的实例init?<T: Encodable>(from model: T) {guard let modelData =  try? JSONEncoder().encode(model) else {return nil}self = modelData}
}

扩展字典

extension Dictionary where Key == String, Value == Any {/// 从遵循`Encodable`的实例转换为 `Dictionary`,字典类型为:`[String: Any]`/// - Parameter model:遵循`Encodable`的实例init?<T: Encodable>(from model: T) {if let data = Data(from: model) {do {guard let dic = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {debugPrint("model can not transform [String: Any]")return nil}self = dic} catch {debugPrint(error.localizedDescription)return nil}} else {debugPrint("model to data error")return nil}}
}

相关库推荐

CleanJSON:【推荐】解决通过自定义 Decoder 方式解决最大痛点(不能设置默认值问题),且代码入侵非常小。

Kodable:通过 ProperWrapper 方式提供以下功能(会对模型进行代码入侵)

  • 无需编写自己的init(from decoder: Decoder)CodingKeys
  • 提供自定义密钥进行解码
  • 使用.符号访问嵌套值
  • 添加默认值,以防缺少该值
  • 重写解码的值(即修剪字符串)
  • 验证解码的值
  • 自动尝试从其他类型解码StringBool作为回退
  • 变压器协议,在现有功能的基础上实现您自己的额外功能

Codable 基本使用相关推荐

  1. 在关联枚举中使用Codable

    场景:在同一个接口中服务器根据不同的status返回不同的json格式. 服务器返回的json如下: //1 - user is confirmed: {"status": &qu ...

  2. Swift之Codable自定义解析将任意数据类型解析为想要的类型

    一.前言 大多数现代应用程序的共同点是,它们需要对各种形式的数据进行编码或解码.无论是通过网络下载的 Json 数据,还是存储在本地的模型的某种形式的序列化表示形式,对于几乎无任何 Swift 代码库 ...

  3. codable swift_使用Codable进行Swift JSON解析

    codable swift In this tutorial, we'll be discussing the Codable Protocol and its forms in order to p ...

  4. Codable实现json转Model,是时候干掉HandyJSON了!

    自从开始使用Swift做项目,一直都在使用HandyJSON,不可否认,HandyJSON在Swift4.0以前是个好东西,也尝试过其它json转mode的工具,最终发现还是HandyJSON最好用. ...

  5. swift 5.1 Json转换之Codable

    对于开发的人员来说,接受后台的数据,并转换成自己的数据模型是常见的事情.但是作为苹果开发者,并没有一个很好的工具去直接转换,必须借助与第三方的开发的库.那么比较好用的有YYModel.MJExtens ...

  6. Swfit4中Codable解析Any类型的问题(Type 'XX' does not conform to protocol 'Decodable')

    0x01 问题 因为Swift4中引入了新协议"Codable",所以我想在最近的项目中不使用第三方库来解析,而使用原生的特性来处理.在写下面这么一个entity的时候,提示&qu ...

  7. swift之字典转模型kvc、mjextention桥接、反射、HandyJSON、ObjectMapper、Codable

    参考swift4.0字典转模型:https://www.cnblogs.com/shaoting/p/8087153.html =====================kvc字典转模型======= ...

  8. Swift学习总结【持续更新】

    1. try.try?.try!的区别: try:需要用catch捕捉异常,如: do {let data = try encoder.encode(item) try data.write(to: ...

  9. swift通知栏推送_如何使用Swift和Laravel使用推送通知创建iOS加密跟踪应用

    swift通知栏推送 by Neo Ighodaro 由新Ighodaro 如何使用Swift和Laravel使用推送通知创建iOS加密跟踪应用 (How to create an iOS crypt ...

最新文章

  1. 华为鸿蒙2.0打游戏,网友Mate X2升级鸿蒙2.0:部分游戏体验比EMUI更好 功耗却更低...
  2. 《从0到1学习Flink》—— Flink Data transformation(转换)
  3. python什么时候进入中国-python 3.4什么时候发布的
  4. git@github.com: Permission denied (publickey).
  5. SQL的主键和外键约束 小记
  6. IDA python 脚本编程使用参考资料链接
  7. php实现access数据库连接,PHP实现Access数据库连接
  8. 『转载』看c#打印的各种技术
  9. ROS机器人程序设计(原书第2版)3.9 3D可视化
  10. OpenStreetMap/Google/百度/Bing瓦片地图服务(TMS)
  11. Python 2X 版本 痛苦的编码格式,一遍完美解决Unicode、GB2312、GBK编码格式的文章
  12. GRACE数据介绍及下载
  13. (KNN)K-近邻算法介绍和 Facebook签到位置预测案例应用
  14. 三年级计算机上册期末测试题,三年级上册期末试卷
  15. 什么是word文件只读模式?
  16. R语言rvest包网络爬虫
  17. EI、SCI和ISTP检索论文的收录号和期刊号查询方法
  18. creat是什么意思中文翻译_CREAT是什么意思中文翻译
  19. matlab提取电压基波分量,有源电力滤波器三种基波提取方法的对比分析
  20. 关于盗墓笔记的那些事

热门文章

  1. Chrome检查更新总失败?安装细则讲解
  2. Python str类型方法实例概述及常用方法——04
  3. UNITY技巧-查找脚本被哪个场景引用
  4. 【BZOJ】1574: [Usaco2009 Jan]地震损坏Damage
  5. 已知/未知宽高的浮动元素水平居中对齐 和 图片水平垂直居中对齐
  6. Linux下,如何给PHP安装pdo_mysql扩展
  7. 乔春洋:网上品牌战略
  8. windows下wchar_t* 转char*
  9. 5 关于数据仓库维度数据处理的方法探究系列——缓慢变化维处理——全历史记录...
  10. 安全***需要掌握的东西