iOS - JSONEncoder和JSONDecoder介绍

Xcode 9, iOS 11 ,Swift 4出现JSONEncoderJSONDecoder来实现JSON格式的编解码

JSONEncoder

An object that encodes instances of a data type as JSON objects.

编码数据为JSON对象

简单使用

义定义杂货食品结构体,并遵守Codable协议

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

Codable协议为可编解码协议的类型别名

public typealias Codable = Decodable & Encodable

// 编码协议
public protocol Encodable { 

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    func encode(to encoder: Encoder) throws
}

// 解码协议
public protocol Decodable {

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    init(from decoder: Decoder) throws
}

编码例子

func jsonEncoder() {
    let pear = GroceryProduct(name: "Pear",
                              points: 250,
                              description: "A ripe pear")

    // 实例化JSONEncoder
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted // 输出格式

    // 编码pear
    let data = try! encoder.encode(pear)
    // 打印编码之后的数据
    print(String(data: data, encoding: .utf8)!)
}

运行输出

{
    "name" : "Pear",
    "points" : 250,
    "description" : "A ripe pear"
}

也可以设置输出格式为sortedKeys,对keys进行排序

encoder.outputFormatting = .sortedKeys

运行输出

{"description":"A ripe pear","name":"Pear","points":250}

JSONDecoder

An object that decodes instances of a data type from JSON objects.

将JSON对象解码成对应的实例数据

简单使用

func jsonDecoder() {
    // 定义jsonData
    let jsonData = """
        {
           "name": "Durian",
           "points": 600,
           "description": "A fruit with a distinctive scent."
        }
        """.data(using: .utf8)!

    // 实例化JSONDecoder
    let decoder = JSONDecoder()
    // 解码jsonData
    let product = try! decoder.decode(GroceryProduct.self, from: jsonData)

    // 获取实例属性
    print("name: \(product.name), points: \(product.points), des: \(product.description!)")
}

运行输出

name: Durian, points: 600, des: A fruit with a distinctive scent.

一些使用场景

初始化内容

struct Toy: Codable {
    var name: String
}

struct Employee: Codable {
    var name: String
    var id: Int
    var favoriteToy: Toy
}

// 创建Json数据
let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed", id: 7, favoriteToy: toy)

// 实例化JSONEncoder和JSONDecoder
let encoder = JSONEncoder()
let decoder = JSONDecoder()
  • Encoding and Decoding Nested Types

Employee包含Toy属性,属于嵌套类型(nested type)

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

let sameEmployee = try! decoder.decode(Employee.self, from: data)
print(sameEmployee)

运行输出

{ 
  "name":"John Appleseed",
  "id":7,
  "favoriteToy":{"name":"Teddy Bear"}
}

Employee(name: "John Appleseed", id: 7, favoriteToy: DataSaveProject.Toy(name: "Teddy Bear"))

// DataSaveProject工程名
  • Switching Between Snake Case and Camel Case Formats

camel case 表示驼峰形式(如:looksLikeThis)
snake case 表示下划线形式(如:looks_like_this_instead)

// key编码策略
encoder.keyEncodingStrategy = .convertToSnakeCase
let snakeData = try! encoder.encode(employee)
let snakeString = String(data: snakeData, encoding: .utf8)!

// key解码策略
decoder.keyDecodingStrategy = .convertFromSnakeCase
let camelEmployee = try! decoder.decode(Employee.self, from: snakeData)

运行输出

{
  "name":"John Appleseed",
  "id":7,
  "favorite_toy": {"name":"Teddy Bear"}} // 单词之间使用下划线 _

Employee(name: "John Appleseed", id: 7, favoriteToy: DataSaveProject.Toy(name: "Teddy Bear"))
  • Working With Custom JSON Keys

使用自定义key,这里使用gift替换favoriteToy

struct Employee: Codable {
    var name: String
    var id: Int
    var favoriteToy: Toy

    // 1、定义特殊枚举CodingKeys,指定值为String,遵守CodingKey协议
    // 2、将favoriteToy设置gift
    enum CodingKeys: String, CodingKey {
        case name, id, favoriteToy = "gift"
    }
}

编解码实现同上

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

let sameEmployee = try! decoder.decode(Employee.self, from: data)
print(sameEmployee)

运行输出

{
  "name":"John Appleseed",
  "id":7,
  "gift":{"name":"Teddy Bear"}}

Employee(name: "John Appleseed", id: 7, favoriteToy: DataSaveProject.Toy(name: "Teddy Bear"))
  • Working With Flat JSON Hierarchies

去除嵌套类型,实现如下数据结构

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : "Teddy Bear"
}

修改Employee结构体,单独实现编解码协议

struct Employee: Encodable {
    var name: String
    var id: Int
    var favoriteToy: Toy

    // 1、定义编码key
    enum CodingKeys: CodingKey {
        case name, id, gift
    }

    // 2、实现编码协议
    func encode(to encoder: Encoder) throws {
        // 3、创建key编码容器,可理解为字典
        var container = encoder.container(keyedBy: CodingKeys.self)

        // 4、使用容器container编码name, id, gift属性
        try container.encode(name, forKey: .name)
        try container.encode(id, forKey: .id)
        try container.encode(favoriteToy.name, forKey: .gift)
    }
}

// 实现解码协议
extension Employee: Decodable {
    init(from decoder: Decoder) throws {
        // 获取key编码容器
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // 获取name, id, gift等属性值
        name = try container.decode(String.self, forKey: .name)
        id = try container.decode(Int.self, forKey: .id)
        let gift = try container.decode(String.self, forKey: .gift)
        favoriteToy = Toy(name: gift)
    }
}

初始化内容同上

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

let sameEmployee = try! decoder.decode(Employee.self, from: data)
print(sameEmployee)
  • Working With Deep JSON Hierarchies

实现身层级JSON数据

{
  "name" : "John Appleseed",
  "id" : 7,
  "gift" : {
    "toy" : {
      "name" : "Teddy Bear"
    }
  }
}

可以看到name属性在toy之内,toy属性在gift之内,为了实现该数据结构,需要为gift属性使用嵌套key容器(nested keyed containers)

struct Employee: Encodable {
    var name: String
    var id: Int
    var favoriteToy: Toy

    // 1、定义编码key
    enum CodingKeys: CodingKey {
        case name, id, gift
    }

    enum GiftKeys: CodingKey {
        case toy
    }

    // 2、实现编码协议
    func encode(to encoder: Encoder) throws {
        // 3、创建key编码容器,可理解为字典
        var container = encoder.container(keyedBy: CodingKeys.self)

        // 4、使用容器container编码name, id, gift属性
        try container.encode(name, forKey: .name)
        try container.encode(id, forKey: .id)

        //5、获取嵌套容器
        var giftContainer = container.nestedContainer(keyedBy: GiftKeys.self, forKey: .gift)
        try giftContainer.encode(favoriteToy, forKey: .toy)
    }
}

// 实现解码协议
extension Employee: Decodable {
    init(from decoder: Decoder) throws {
        // 获取key编码容器
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // 获取name, id, gift等属性值
        name = try container.decode(String.self, forKey: .name)
        id = try container.decode(Int.self, forKey: .id)

        // 获取嵌套容器
        let giftContainer = try container.nestedContainer(keyedBy: GiftKeys.self, forKey: .gift)
        favoriteToy = try giftContainer.decode(Toy.self, forKey: .toy)
    }
}

运行上面测试内容输出

{
 "name":"John Appleseed",
 "id":7,
 "gift":{
    "toy":{"name":"Teddy Bear"}
   }
}

Employee(name: "John Appleseed", id: 7, favoriteToy: DataSaveProject.Toy(name: "Teddy Bear"))
  • Encoding and Decoding Dates

实现日期的编解码

struct Toy: Codable {
    var name: String
}

struct Employee: Codable {
    var name: String
    var id: Int
    var birthday: Date
    var favoriteToy: Toy
}

extension DateFormatter {
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd-MM-yyyy"
        return formatter
    }()
}

简单使用

// 创建数据
let toy = Toy(name: "Teddy Bear")
let employee = Employee(name: "John Appleseed",
                        id: 7,
                        birthday: Date(),
                        favoriteToy: toy)

// 实例化JSONEncoder和JSONDecoder
let encoder = JSONEncoder()
let decoder = JSONDecoder()

// 设置日期编解码策略
encoder.dateEncodingStrategy = .formatted(.dateFormatter)
decoder.dateDecodingStrategy = .formatted(.dateFormatter)

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

let sameEmployee = try! decoder.decode(Employee.self, from: data)
print(sameEmployee)

运行输出

{ 
   "id":7,
   "favoriteToy":{"name":"Teddy Bear"},
   "name":"John Appleseed",
   "birthday":"22-10-2019"
}

Employee(name: "John Appleseed", 
           id: 7, 
     birthday: 2019-10-21 16:00:00 +0000, 
  favoriteToy: DataSaveProject.Toy(name: "Teddy Bear"))
  • Encoding and Decoding Subclasses

实现编解码子类

struct Toy: Codable {
    var name: String
}

// 父类
class BasicEmployee: Codable {
    var name: String
    var id: Int

    init(name: String, id: Int) {
        self.name = name
        self.id = id
    }
}

// 子类
class GiftEmployee: BasicEmployee {
    var birthday: Date
    var toy: Toy

    enum CodingKeys: CodingKey {
        case employee, birthday, toy
    }

    init(name: String, id: Int, birthday: Date, toy: Toy) {
        self.birthday = birthday
        self.toy = toy
        super.init(name: name, id: id)
    }

    // 编码
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(birthday, forKey: .birthday)
        try container.encode(toy, forKey: .toy)
        let baseEncoder = container.superEncoder(forKey: .employee)
        try super.encode(to: baseEncoder)
    }

    // 解码
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        birthday = try container.decode(Date.self, forKey: .birthday)
        toy = try container.decode(Toy.self, forKey: .toy)
        let baseDecoder = try container.superDecoder(forKey: .employee)
        try super.init(from: baseDecoder)
    }
}

extension DateFormatter {
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd-MM-yyyy"
        return formatter
    }()
}

简单使用

// 创建数据
let toy = Toy(name: "Teddy Bear")

// 实例化JSONEncoder和JSONDecoder
let encoder = JSONEncoder()
let decoder = JSONDecoder()

// 设置日期编解码策略
encoder.dateEncodingStrategy = .formatted(.dateFormatter)
decoder.dateDecodingStrategy = .formatted(.dateFormatter)

let giftEmployee = GiftEmployee(name: "John Appleseed",
                                id: 7,
                                birthday: Date(),
                                toy: toy)

let data = try! encoder.encode(giftEmployee)
let string = String(data: data, encoding: .utf8)!
print(string)

let sameEmployee = try! decoder.decode(GiftEmployee.self, from: data)
print(sameEmployee.name)

运行输出

{
   "toy": {  "name":"Teddy Bear"},
   "employee": {"name":"John Appleseed","id":7},
   "birthday":"22-10-2019"
}

 John Appleseed
  • Handling Arrays With Mixed Types

复杂类型的编解码

struct Toy: Codable {
    var name: String
}

enum AnyEmployee:Encodable {
    case defaultEmployee(String, Int)
    case customEmployee(String, Int, Date, Toy)
    case noEmployee

    // 编码key
    enum CodingKeys: CodingKey {
        case name, id, birthday, toy
    }

    func encode(to encoder: Encoder) throws {
        // 根据编码key获取编码容器
        var container = encoder.container(keyedBy: CodingKeys.self)

        // 根据类型进行数据处理
        switch self {
        case .defaultEmployee(let name, let id):
            try container.encode(name, forKey: .name)
            try container.encode(id, forKey: .id)
        case .customEmployee(let name, let id, let birthday, let toy):
            try container.encode(name, forKey: .name)
            try container.encode(id, forKey: .id)
            try container.encode(birthday, forKey: .birthday)
            try container.encode(toy, forKey: .toy)
        case .noEmployee:
            let context = EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Invalid employee!")
            throw EncodingError.invalidValue(self, context)
        }
    }
}

extension AnyEmployee: Decodable {
    init(from decoder: Decoder) throws {
        // 1 获取container
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let containerKeys = Set(container.allKeys)
        let defaultKeys = Set([.name, .id])
        let customKeys = Set([.name, .id, .birthday, .toy])

        // 2 对Keys进行匹配
        switch containerKeys {
        case defaultKeys:
            // 获取属性值初始化实例
            let name = try container.decode(String.self, forKey: .name)
            let id = try container.decode(Int.self, forKey: .id)
            self = .defaultEmployee(name, id)
        case customKeys:
            let name = try container.decode(String.self, forKey: .name)
            let id = try container.decode(Int.self, forKey: .id)
            let birthday = try container.decode(Date.self, forKey: .birthday)
            let toy = try container.decode(Toy.self, forKey: .toy)
            self = .customEmployee(name, id, birthday, toy)
        default:
            self = .noEmployee
        }
    }
}

简单使用

let toy = Toy(name: "Teddy Bear")

let encoder = JSONEncoder()
let decoder = JSONDecoder()

// 设置日期编解码策略
encoder.dateEncodingStrategy = .formatted(.dateFormatter)
decoder.dateDecodingStrategy = .formatted(.dateFormatter)

// 创建数据源
let employees = [AnyEmployee.defaultEmployee("John Appleseed", 7),
                 AnyEmployee.customEmployee("Jack", 22, Date(), toy)]

let employeesData = try! encoder.encode(employees)
let employeesString = String(data: employeesData, encoding: .utf8)!
print(employeesString)

let sameEmployees = try! decoder.decode([AnyEmployee].self, from: employeesData)
print(sameEmployees)

运行输出

[
  {"name":"John Appleseed","id":7},
  {"id":22,"name":"Jack","birthday":"22-10-2019","toy":{"name":"Teddy Bear"}}
]

[
   AnyEmployee.defaultEmployee("John Appleseed", 7), 
   AnyEmployee.customEmployee("Jack", 22, 2019-10-21 16:00:00 +0000, 
   Toy(name: "Teddy Bear"))
]
  • Working With Arrays

数组的编解码

struct Toy: Codable {
    var name: String
}

struct Label: Encodable {
    var toy: Toy

    func encode(to encoder: Encoder) throws {
        // 因为没有设置key,所以使用unkeyedContainer,对于数组是不需要key的
        var container = encoder.unkeyedContainer()

        try container.encode(toy.name.lowercased())
        try container.encode(toy.name.uppercased())
        try container.encode(toy.name)
    }
}

extension Label: Decodable {
    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var name = ""
        while !container.isAtEnd {
            name = try container.decode(String.self)
        }
        toy = Toy(name: name)
    }
}

简单使用

let toy = Toy(name: "Teddy Bear")

let encoder = JSONEncoder()
let decoder = JSONDecoder()

// 创建label
let label = Label(toy: toy)
let labelData = try! encoder.encode(label)
let labelString = String(data: labelData, encoding: .utf8)!
print(labelString)

let sameLabel = try! decoder.decode(Label.self, from: labelData)
print(sameLabel)

运行输出

["teddy bear","TEDDY BEAR","Teddy Bear"]

Label(toy: Toy(name: "Teddy Bear"))
  • Working With Arrays Within Objects

对象数据结构中拥有数组的编解码

struct Toy: Encodable {
    var name: String
    var label: String

    enum CodingKeys: CodingKey {
        case name, label
    }

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

        // 嵌套无key容器
        var labelContainer = container.nestedUnkeyedContainer(forKey: .label)
        // 编码数组数据
        try labelContainer.encode(label.lowercased())
        try labelContainer.encode(label.uppercased())
        try labelContainer.encode(label)
    }
}

extension Toy: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)

        var labelContainer = try container.nestedUnkeyedContainer(forKey: .label)
        var labelName = ""
        while !labelContainer.isAtEnd { // 遍历获取数组最后一个值
            labelName = try labelContainer.decode(String.self)
        }
        label = labelName
    }
}

简单使用

let encoder = JSONEncoder()
let decoder = JSONDecoder()

let toy = Toy(name: "Teddy Bear", label: "Teddy Bear")
let data = try! encoder.encode(toy)
let string = String(data: data, encoding: .utf8)!
print(string)

let sameToy = try! decoder.decode(Toy.self, from: data)
print(sameToy)

运行输出

{
   "name":"Teddy Bear",
    "label":["teddy bear","TEDDY BEAR","Teddy Bear"]
}

Toy(name: "Teddy Bear", label: "Teddy Bear")

参考

JSONEncoder
JSONDecoder
Encoding-and-decoding-in-swift

你可能感兴趣的:(iOS - JSONEncoder和JSONDecoder介绍)