注册
iOS

如何用原生的方式来定义Swift JSON Model

在Swift开发中,处理JSON数据序列化是一项常见任务。由于Swift的类型安全特性,处理类似JSON这样的弱类型数据一直是一个挑战。然而,Swift 4引入了一个令人欣喜的特性,即Codable协议。Codable协议为我们提供了一种简洁的方式来序列化和反序列化JSON数据。


尽管Codable协议在处理大多数情况下表现得很出色,但它并不能完全满足所有需求。例如,它不支持自动类型转换,也无法友好地处理默认值。


如果我们能解决这些问题,就能更完美地处理JSON数据了。我们可以自定义解码器和编码器,以提供更高级的功能。通过自定义解码器,我们可以实现类型的自动转换,将JSON数据转换为目标类型,而无需手动处理。此外,我们还可以通过自定义编码器,在编码过程中为属性设置默认值,以确保生成的JSON数据符合预期。


总之,通过充分利用Swift的特性和自定义解码器、编码器,我们可以更好地处理JSON数据,满足我们更复杂的需求。
传送门ObjMapper


Codable坑点1:不支持类型转换

// JSON:
{
"uid":"123456",
"name":"Harry",
"age":10
}

// Model:
struct Dog: Codable{
var uid: Int
var name: String?
var age: Int?
}

在json转换过程中,我们常常与遇到类型模型与json的类型不一致的情况,就像上面的uid字段,uid在json中是String,但是我们的模型是Int,由于swift是类型安全的,所以,转换就不会成功。


Codable坑点2:不支持默认值


话不多说,上代码

struct Activity: Codable {
enum Status: Int {
case start = 1//活动开始
case processing = 2//活动进行中
case end = 3//活动结束
}

var name: String
var status: Status//活动状态
}

这儿有一个活动,活动现目前有三种状态,到目前为止,一切都很美好。有一天,突然说需要给活动添加已下架的状态,what?

//JSON
{
"name": "元旦迎新活动",
"status": 4
}

用Activity解析上面的JSON就会报错,我们如何规避呢,像下面一样

var status: Status?

答案是no、no、no,因为可选值的解码所表达的是“如果不存在,则置为 nil”,而不是“如果解码失败,则置为 nil”。


解决方案


有没有更好的方式来处理上面这两个问题呢?具体代码见ObjMapper,这儿简单描述下如何使用。


1、Model与JSON相互转换

// JSON:
{
"uid":888888,
"name":"Tom",
"age":10
}

// Model:
struct Dog: Codable{
//如果字段不是可选类型,则使用Default,提供一个默认值,像下面一样
@Default<Int.Zero> var uid: Int
//如果是可选类型,则使用Backed
@Backed var name: String?
@Backed var age: Int?
}

//JSON to model
let dog = Dog.decodeJSON(from: json)

//model to json
let json = dog.jsonString

当 JSON/Dictionary 中的对象类型与 Model 属性不一致时,ObjMapper 将会进行如下自动转换。自动转换不支持的值将会被设置为nil或者默认值。


6aaae3a325f6386c70938729499a9393.png

2、Model的嵌套

let raw_json = """
{
"author":{
"id": 888888,
"name":"Alex",
"age":"10"
},
"title":"model与json互转",
"subTitle":"如何优雅的转换"
}
"""

// Model:
struct Author: Codable{
@Default<Int.Zero> var id: Int
@Default<String.Empty> var name: String
//使用Backed后,如果类型不匹配,则类型会自动转换
//比如,上面的json中,age是个字符串,我们定义的模型是Int,
//那么声明@Backed后,会自动转换成Int类型
@Backed var age: Int?
}

struct Article: Codable {
//如果json中的title为nil或者不存在,则会给title赋一个默认值
@Default<String.Empty> var title: String
var subTitle: String?
var author: Author
}

//JSON to model
let article = Article.decodeJSON(from: raw_json)

//model to json
let json = article.jsonString
print(article?.jsonString ?? "")

3、自定义类型的可选值


话不多说,上代码

struct Activity: Codable {
enum Status: Int {
case start = 1//活动开始
case processing = 2//活动进行中
case end = 3//活动结束
}

@Default<String.Empty> var name: String
var status: Status//活动状态
}

这儿有一个活动,活动现目前有三种状态,到目前为止,一切都很美好。有一天,突然说需要给活动添加已下架的状态,what?

//JSON
{
"name": "元旦迎新活动",
"status": 4
}

用Activity解析上面的JSON就会报错,我们如何规避呢,像下面一样

var status: Status?

答案是no、no、no,因为可选值的解码所表达的是“如果不存在,则置为 nil”,而不是“如果解码失败,则置为 nil”,那就用我们的Default吧,请看下面代码:

struct Activity: Codable {
///Step 1:让Status遵循DefaultValue协议
enum Status: Int, Codable, DefaultValue {
case start = 1//活动开始
case processing = 2//活动进行中
case end = 3//活动结束
case unknown = 0//默认值,无意义

///Step 2:实现DefaultValue协议,指定一个默认值
static func defaultValue() -> Status {
return Status.unknown
}
}

@Default<String.Empty> var name: String
///Step 3:使用Default
@Default<Status> var status: Status//活动状态
}

//{"name": "元旦迎新活动", "status": 4 }
//Activity将会把status解析成unknown

4、为普通类型设置不一样的默认值


本库已经内置了很多默认值,比如Int.Zero, Bool.True, String.Empty...,如果我们想为字段设置不一样的默认值,见下面代码:

public extension Int {
enum One: DefaultValue {
static func defaultValue() -> Int {
return 1
}
}
}

struct Dog: Codable{
@Backed var name: String?
@Default<Int.Zero> var uid: Int
//如果json中没有age字段或者解析失败,则模型的age被设置成默认值1
@Default<Int.One> var age: Int
}

5、数组支持


对于数组,可以使用@Backed,@Default来解析

// JSON:
let raw_json = """
{
"code":0,
"message":"success",
"data": [{
"name": "元旦迎新活动",
"status": 4
}]
}
"""

struct Activaty: Codable{
@Default<String.Empty> var name: String
@Default<Int.Zero> var status: Int
}

// 如果数组是可选类型,可以使用@Backed
struct Response1: Codable {
@Default<Int.Zero> var code: Int
@Default<String.Empty> var message: String
@Backed var data: [Activaty]?
}

// 为数组,设置默认值,如果数组不存在或者解析错误,则使用默认值
struct Response2: Codable {
@Default<Int.Zero> var code: Int
@Default<String.Empty> var message: String
@Default<Array.Empty> var data: [Activaty]
}
//JSON to model
let rsp1 = Response1.decodeJSON(from: raw_json)
let rsp2 = Response2.decodeJSON(from: raw_json)

//model to json
let json1 = rsp1.jsonString
let json2 = rsp2.jsonString
// print(rsp1?.jsonString ?? "")
// print(rsp2?.jsonString ?? "")

6、设置通用类型


我们在开发过程中,第一个遇到的json可能是这样的:

// JSON:
{
"code":0,
"message":"success",
"data":[]//这个data可以是任何类型
}

由于data字段的类型不固定,有时候为了统一处理,我们定义模型可以像下面这样,有枚举类型JsonValue来表示。

struct Response: Codable { 
var code: Int
var message: String
var data: JsonValue?
}

如果要取data字段的值,我们可以这样用data?.intValue或者data?.arrayValue等等,具体使用见源码。


注意:这种对于data是一个简单的model(比如就是一个整形、字符串等等),可以起到事半功倍的效果;如果data是一个大型model,建议还是将data指定为具体类型。


7、如果是从1.0.x升级到2.0版本,修改了DefaultValue协议。如果之前的代码中使用了DefaultValue协议,则会报错,修改如下:

原来为:
///Step 1:让Status遵循DefaultValue协议
enum Status: Int, Codable, DefaultValue {
case start = 1//活动开始

///Step 2:实现DefaultValue协议,指定一个默认值
static let defaultValue = Status.unknown
}

修改成:
///Step 1:让Status遵循DefaultValue协议
enum Status: Int, Codable, DefaultValue {
case start = 1//活动开始

///Step 2:实现DefaultValue协议,返回一个默认值
static func defaultValue() -> Status {
return Status.unknown
}
}


参考文档

  1. 用 Codable 协议实现快速 JSON 解析
  2. Swift 4 踩坑之 Codable 协议
  3. 使用 Property Wrapper

不喜勿喷,有问题请留言😁😁😁,欢迎✨✨✨star✨✨✨和PR


作者:大儿童梦里花开
链接:https://juejin.cn/post/7260776783394422821
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册