美文网首页
Swift中的JSON转换

Swift中的JSON转换

作者: 3ni | 来源:发表于2023-04-06 10:33 被阅读0次

本文的主要内容来自于https://benscheirman.com/2017/06/swift-json.html,并在此基础上进行了翻译和精简。更多细节可以直接阅读原文。

本文中的Swift版本为5.8

快速开始

有些朋友不喜欢长篇大论的理论和原理讲解,想快速掌握大概的使用方法,所以我在这里举了两个例子,以满足这方面的需求。

/*
 编码JSON字符串
*/

struct Person: Codable {
    var name: String
    var age: Int32
    var height: Double
    var email: String
}

let person = Person(name: "张三", age: 23, height: 1.78,email: "12345@qq.com")
// 创建json编码器
let encoder = JSONEncoder()
// 友好输出,即包含换行和缩进
encoder.outputFormatting = .prettyPrinted

do {
      // 编码
    let jsonData = try encoder.encode(person)
    let jsonString = String(data: jsonData, encoding: .utf8)
    if jsonString != nil {
        print(jsonString!)
    }
} catch {
    print(error.localizedDescription)
}
/*
 解码JSON字符串
*/

struct Person: Codable {
    var name: String
    var age: Int32
    var height: Double
    var email: String
}
let jsonString = """
{"age":23,"email":"12345@qq.com","name":"张三","height":1.78}
"""

let decoder = JSONDecoder()
// 创建json解码器
let jsonData = jsonString.data(using: .utf8)

do {
      // 解码
    let person = try decoder.decode(Person.self, from: jsonData!)
    print(person)
} catch {
    print(error.localizedDescription)
}

如果上面的例子不能解决你的问题,那么我建议全面系统地学习json字符串的转换,正所谓磨刀不负砍柴工。

简单例子

这次在结构体中加入枚举类型,然后将其编码成json字符串。

enum Hobby: String, Codable {
    case painting
    case reading
    case swimming
}

// 如果支持编码和解码,则必须遵循Codable协议
struct Student: Codable {
    var name: String
    var email: String
    
    var hobby: Hobby
}

let stu1 = Student(name: "张三", email: "zhangsan@qq.com", hobby: .painting)
let encoder = JSONEncoder()

do {
    // 因为编解码可能会失败,所以必须要使用try,并处理发生失败的情况
    let dataString = try encoder.encode(stu1)
    let jsonString = String(data: dataString, encoding: .utf8)
    print(jsonString!)
} catch {
    print(error.localizedDescription)
}

上面是将struct编码成json字符串的过程,解码json字符串到struct的流程与上面类似,可尝试自己编写。

自定义字段名

有时候我们并不想把变量名称或常量名称当作json字符串中的字段名,那么可以重新定义CodingKeys枚举,达到自定义json字段名的效果。例子如下。

struct Student: Codable {
    var name: String
    var email: String
    
   // 需要遵循CodingKey协议
    enum CodingKeys: String, CodingKey {
        case name = "Name"
        case email = "Email"
    }
}

let stu = Student(name: "张三", email: "zhangsan@qq.com")
let encoder = JSONEncoder()

do {
    let jsonData = try encoder.encode(stu)
    let jsonString = String(data: jsonData, encoding: .utf8)
    print(jsonString!)
} catch {
    print(error.localizedDescription)
}

处理日期

JSON中并没有对应的日期类型,如果直接编码的话,会输出一串数字,而不是相应的日期格式,所以在编码之前,可以设置编码器中的日期格式。

struct Foo: Codable {
    var date: Date
}
let foo = Foo(date: Date.now)

let encoder = JSONEncoder()
// 设置日期格式
encoder.dateEncodingStrategy = .iso8601

let jsonData = try! encoder.encode(foo)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)

当然,如果上面不满足需求,那么可以进一步自定义。

let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
encoder.dateEncodingStrategy = .formatted(df)

如果还是不能满足需求,那么还可以使用.custom,具体使用方法请查阅相关文档。

处理URL

假设有如下json字符串。

{
    "title": "百度一下",
    "url": "http://baidu.com"
}

那么对应的struct为如下。

struct Webpage: Codable {
    var title: String
    var url: URL
}

解码过程也是相当的简单,如下所示。

let jsonString = """
{
    "title": "百度一下",
    "url": "http://baidu.com"
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
var page = try! decoder.decode(Webpage.self, from: jsonData!)

处理顶层数组

假设有如下json字符串。

[{"name":"张三"},{"name":"李四"},{"name":"王五"}]

解码过程如下。

struct Article: Codable {
    var title: String
}

let articlesString = """
[{"title": "标题1"}, {"title":"标题2"}, {"title":"标题3"}]
"""

let data = articlesString.data(using: .utf8)

let decoder = JSONDecoder()
// 注意这里的类型写法,数组中的元素需要遵循Codable协议
var articles = try! decoder.decode([Article].self, from: data!)

for article in articles {
    print(article.title)
}

更复杂的结构

有些复杂的json字符串可以由简单的结构组成,所以在定义这些json结构时,可以使用嵌套完成复杂json的结构体的定义。如下面的例子。

{
    "meta": {
        "page": 1,
        "total_pages": 4,
        "per_page": 10,
        "total_records": 38
    },
    "breweries": [
        {
            "id": 1234,
            "name": "Saint Arnold"
        },
        {
            "id": 52892,
            "name": "Buffalo Bayou"
        }
    ]
}
struct PagedBreweries : Codable {
    struct Meta : Codable {
        let page: Int
        let totalPages: Int
        let perPage: Int
        let totalRecords: Int
        enum CodingKeys : String, CodingKey {
            case page
            case totalPages = "total_pages"
            case perPage = "per_page"
            case totalRecords = "total_records"
        }
    }

    struct Brewery : Codable {
        let id: Int
        let name: String
    }

    let meta: Meta
    let breweries: [Brewery]
}

自定义编解码

上面例子中的编解码过程都不需要我们干预,非常方便我们使用,但如果想更精准的控制编解码过程,那么就需要自定义编解码过程。

编码

这里以车为例子,里面包含一些基本信息,有些信息没有实际意义,仅仅为了演示需要。

enum CarColor: String, Codable {
    case white
    case black
    case red
    case blue
}

struct Car: Codable {
    var owner: String
    var manufacturer: String
    var price: String
    var color: CarColor
    // 无任何意义,仅用于演示
    var sizes: [Double]
}

然后对车进行扩展,实现encode方法。

extension Car {
    
    enum CodingKeys: String, CodingKey {
        case owner
        case manufacturer
        case price
        case color
        case sizes
    }
    
    func encode(to encoder: Encoder) throws {
        // 获取container,具体解释见后面
        var container = encoder.container(keyedBy: CodingKeys.self)
        do {
            // 加入编码数据,第一项为数据的值,第二项为字段的值
            try container.encode(owner, forKey: .owner)
            try container.encode(manufacturer, forKey: .manufacturer)
            try container.encode("\(price)¥", forKey: .price)
            try container.encode(color, forKey: .color)
            // 获取第二种类型的container,准备对数组进行自定义编码过程
            var sizesContainer = container.nestedUnkeyedContainer(forKey: .sizes)
            for size in sizes {
                // 自定义编码数组过程,将每个数组元素翻倍
                try sizesContainer.encode(size * size)
            }
        } catch {
            print(error.localizedDescription)
        }
    }
}

let car = Car(owner: "张三", manufacturer: "比亚迪", price: "336541.23", color: .red, sizes: [1.0, 2.0, 3.0])

let encoder = JSONEncoder()
let jsonData = try! encoder.encode(car)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)

上面代码中出现了新的事物,即container。什么是container?原文中并没有进行解释,可以简单理解为管理编码数据的对象。

container有三种类型。

  • 有对应键值的container,本质上是字典,最常用的类型。
  • 无对应键值的container,如数组。
  • 单一值container,不带任何类型的元素的原始值。

在上面代码中,注意container必须是可变属性,需要使用var声明。

解码

解码是编码的逆操作,大部分操作相似,下面例子是对车信息的json字符串进行解码。

let jsonString = """
{"sizes":[1,2,3],"manufacturer":"比亚迪","owner":"张三","price":"336541.23¥","color":"red"}
"""

// 扩展car,实现解码操作
extension Car {
    init(from decoder: Decoder) throws {
        do {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let owner = try container.decode(String.self, forKey: .owner)
            let manufacturer = try container.decode(String.self, forKey: .manufacturer)
            
            let suffixPrice = try container.decode(String.self, forKey: .price)
            // 去除价格后面的人民币标识
            let price = String(suffixPrice[..<(suffixPrice.firstIndex(of: "¥") ?? suffixPrice.endIndex)])
            
            let color = try container.decode(CarColor.self, forKey: .color)
            
            // 准备解码数组
            var tempSizes: [Double] = []
            // 自定义解码数组数据
            var sizesArray = try container.nestedUnkeyedContainer(forKey: .sizes)
            while (!sizesArray.isAtEnd) {
                let size = try sizesArray.decode(Double.self)
                tempSizes.append(size)
            }
            self.init(owner: owner, manufacturer: manufacturer, price: price, color: color, sizes: tempSizes)
        } catch {
            print(error.localizedDescription)
            self.init(owner: "", manufacturer: "", price: "", color: .black, sizes: [])
        }
    }
}

let decoder = JSONDecoder()
let jsonData = jsonString.data(using: .utf8)
let car = try! decoder.decode(Car.self, from: jsonData!)
print(car)

处理继承关系

class Person : Codable {
    var name: String?
}

class Employee : Person {
    var employeeID: String?
}

如果对Employee编码的话,会出现什么情况?答案是只会编码Person中的属性。为此,我们需要在各自的类中自定义编码过程,并实现相关调用。

class Person : Codable {
    var name: String?

    private enum CodingKeys : String, CodingKey {
        case name
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
    }
}

class Employee : Person {
    var employeeID: String?

    private enum CodingKeys : String, CodingKey {
        case employeeID = "emp_id"
    }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder) // 试试不加上这个语句会出现什么情况
        var container = encoder.container(keyedBy: CodingKeys.self)
        // try super.encode(to: container.superEncoder())
        try container.encode(employeeID, forKey: .employeeID)
    }
}

let employee = Employee()
employee.name = "张三"
employee.employeeID = "000001"

let encoder = JSONEncoder()
let jsonData = try! encoder.encode(employee)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)

上面编码后的结果如下。

{
    "name": "张三",
    "emp_id": "000001"
}

虽然属性都编码成功了,但是结果是扁平的,我们想改为像继承一样具有层级的json字符串,那么将try super.encode(to: encoder)改为try super.encode(to: container.superEncoder())并放在定义container的后面就可以了,编码结果如下。

{
    "super": {
        "name": "张三"
    },
    "emp_id": "000001"
}

上面结果虽然是我们想要的,但是还差点意思,主要原因在于super不是我们想要的名字,我们想要的是person。

class Employee : Person {
    var employeeID: String?

    private enum CodingKeys : String, CodingKey {
        case employeeID = "emp_id"
        // 增加person
        case person
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        // 指定键值
        try super.encode(to: container.superEncoder(forKey: .person))
        try container.encode(employeeID, forKey: .employeeID)
    }
}

总结

本文只介绍了我经常遇到的几种情况下的使用方法,更多使用技巧和方法请阅读原文和官方文档,

相关文章

网友评论

      本文标题:Swift中的JSON转换

      本文链接:https://www.haomeiwen.com/subject/iqfuddtx.html