Written in the beginning

To all the great mothers in the world:

Happy holidays, good health, thank you for your pay, thank you for your love!

—- on Mother’s Day

preface

For most applications, the most common tasks is to send and receive network data, but before doing this, we need to encode or serialization converts the data to the right format to send, then need to receive the network data is converted to the appropriate format, so you can use them in the application, This process is called decoding or deserialization.

How to define this format! JSON is the most commonly used format for sending and receiving data on the Internet, but before Swift4.0, everyone used third-party open source libraries to parse the JSON format.

Finally, Apple has added native support for JSON parsing in the Foundtion module of Swift4.0, which is powerful and easy to use, Next, let me show you how to encode and decoding your data in SWIFT.

Introduction to Basic Knowledge

It is a combination of two protocols for the Decodable and Encodable domains. The source code is as follows:

public typealias Codable = Decodable & Encodable
Copy the code

As you can easily guess, JSON and data model conversions can be done in a Codable format.

In Swift4.0, Apple provides JSONEncoder and JSONDecoder objects to handle JSON encoding and decoding. The core code is as follows:

let encoder = JSONEncoder(a)let decoder = JSONDecoder(a)Copy the code

Now that the concepts have been introduced, are you ready for the challenge?

JSON to data model

TASK 1: Simple data structures

If your JSON structure is consistent with the data model structure you are using, the parsing process will be very simple, as shown below:

Here is the JSON data for a song, which I will now convert into a SongModel.

let song = """ { "singer": "The Chainsmokers", "name": "Something Just Like This", } """
Copy the code

SongModel

/// SongModel follows the Codable protocol
struct SongModel: Codable {
    var singer: String?
    var name: String?
}
Copy the code

The conversion process is as follows:

if let jsonData = song.data(using: String.Encoding.utf8) {
    if let sSong = try? JSONDecoder().decode(SongModel.self, from: jsonData) {
        dump(sSong)
    }
}

Copy the code

The output is as follows:

 JSONDecoderDemo.SongModel
   singer: Optional("The Chainsmokers")
    - some: "The Chainsmokers"
   name: Optional("Something Just Like This")
    - some: "Something Just Like This"
Copy the code

This completes the analysis, is not very simple!

NOTE: In data model member variables, basic data types such as: String, Int, Float, etc., are all implemented in the Codable range, so if your data type contains only the properties of these basic data types, just add the Codable protocol to the type declaration without writing any code for the actual implementation.

TASK 2: Parsing arrays

Let’s say this is the JSON data we received for an Album Album, and now we want to convert it to the AlbumModel data model.

let album = """ { "singer": "The Chainsmokers", "name": "Something Just Like This", "songs":[ "Something Just Like This", "Closer", "Young", "All We Know" ] } """
Copy the code

AlbumModel

struct AlbumModel: Codable {
    var singer: String?
    var name: String?
    var songs: [String]?
}
Copy the code

The conversion process is as follows:

if let jsonData = album.data(using: String.Encoding.utf8) {
    if let sSong = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
        dump(sSong)
    }
}
Copy the code

The output is:

 JSONDecoderDemo.AlbumModel
   singer: Optional("The Chainsmokers")
    - some: "The Chainsmokers"
   name: Optional("Something Just Like This")
    - some: "Something Just Like This"
   songs: Optional(["Something Just Like This"."Closer"."Young"."All We Know"])
     some: 4 elements
      - "Something Just Like This"
      - "Closer"
      - "Young"
      - "All We Know"
Copy the code

And the above conversion is exactly the same, you must be now in the mind has been silently muttering: so simple still use you?

Then please prepare for a new challenge!

TASK 3: Structure inconsistency

JSON data format are demonstrated above and the member variables in the data model of one to one correspondence, but in actual development, you will often meet with the format of the data source and data model structure is inconsistent, in many cases may be a good server and the client is not regulated the format of the interface, and then began to develop, the need to debug, When the client receives the message, it is confused:

NOTE: I strongly recommend that you define the interface document, define, define, and say important things three times before you do functional development.

The data formats defined by the server and the client are different, but of course one side or the other has to compromise to be compatible, so when the client wants to be compatible, what do you do? Let’s start with an example:

For example, the data returned by the server is:

let album = """ { "singer": "The Chainsmokers", "name": "Something Just Like This", "songList":[ "Something Just Like This", "Closer", "Young", "All We Know" ] } """
Copy the code

As you can see, songs has been replaced by songList. Let’s execute the original code and look at the output:

 JSONDecoderDemo.AlbumModel
   singer: Optional("The Chainsmokers")
    - some: "The Chainsmokers"
   name: Optional("Something Just Like This")
    - some: "Something Just Like This"
  - songs: nil
Copy the code

The resolution fails when the songs field turns nil, so how do you make it compatible without changing the member variables of the data model that I defined earlier? The CodingKey protocol is used to map variables in the data model. First, add a special enumeration type to the data model:

private enum CodingKeys: String.CodingKey
Copy the code

After the data model is added, the code is as follows:

struct AlbumModel: Codable {
    var singer: String?
    var name: String?
    var songs: [String]?
    
    enum CodingKeys: String.CodingKey {
        case singer = "singer"
        case name = "name"
        case songs = "songList"}}Copy the code

The output is:

 JSONDecoderDemo.AlbumModel
   singer: Optional("The Chainsmokers")
    - some: "The Chainsmokers"
   name: Optional("Something Just Like This")
    - some: "Something Just Like This"
   songs: Optional(["Something Just Like This"."Closer"."Young"."All We Know"])
     some: 4 elements
      - "Something Just Like This"
      - "Closer"
      - "Young"
      - "All We Know"
Copy the code

In this way, we can normally parse JSON data, is not very powerful.

Let’s make it harder!

When the JSON structure of your record is like this, how do you parse it?

let album = """ { "singer": "The Chainsmokers", "name": "Something Just Like This", "songs": "Something Just Like This" } """
Copy the code

Based on the example given above, you will find that it still does not match your data model. The original songs field is not an array format, so how to properly parse into the data model, at this time we need to implement the encoding and decoding logic.

Start by adding the following code to AlbumModel:

struct AlbumModel: Codable {
    var singer: String?
    var name: String?
    var songs: [String]?

    / / 1
    enum CodingKeys: String.CodingKey {
        case singer = "singer"
        case name = "name"
        case songs = "songs"
    }
    
    // Convert JSON to Model
    init(from decoder: Decoder) throws {
    	/ / 2
        let container = try decoder.container(keyedBy: CodingKeys.self)
        / / 3
        singer = try container.decode(String.self, forKey: .singer)
        name = try container.decode(String.self, forKey: .name)
        let ss = try container.decode(String.self, forKey: .songs)
        songs = [ss]
    }
    
    // Code: convert Model to JSON
    func encode(to encoder: Encoder) throws {
    	/ / 4
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(singer, forKey: .singer)
        try container.encode(name, forKey: .name)
        try container.encode(songs, forKey: .songs)
    }
}
Copy the code

Explain the following:

  1. Create the CodingKeys enumeration to map the JSON field.
  2. Create a decoder container to store JSON properties.
  3. Extract the artist and album names and playlists from the container using the appropriate types and encoding keys. Since the playlists are array types, you need to convert the extracted songs to an array.
  4. Create a KeyedEncodingContainer to encode attributes in the data model.

The conversion process is as follows:

if let jsonData = album.data(using: String.Encoding.utf8) {
    if let aAlbum = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
        dump(aAlbum)
    }
}
Copy the code

The results are as follows:

 JSONDecoderDemo.AlbumModel
   singer: Optional("The Chainsmokers")
    - some: "The Chainsmokers"
   name: Optional("Something Just Like This")
    - some: "Something Just Like This"
   songs: Optional(["Something Just Like This"])
     some: 1 element
      - "Something Just Like This"
Copy the code

You can see that with the above code, you can convert the original String in JSON to an array type in the data model.

Note: If you need to use CodingKeys to resolve field inconsistencies, you must include other attributes in the enumeration, such as singer and name, even if they do not need to be mapped. Otherwise, an error will be reported.

TASK 4: Complex nesting

In addition to working with simple data models, Codable can also work with complex nested data models. Here’s what a nested data model is:

For example, I have a data model for albums called AlbumModel, which has the property of SongModel embedded in it, which is nesting. Nested data models and nested submodels must comply with the Codable protocol. Here is an example of a nested data model:

/// Album model
struct AlbumModel: Codable {
    / / the album name
    var albumName: String?
    // Release time
    var releaseTime: String?
    // Playlist (nested)
    var songs: [SongModel]?
}

// Song model
struct SongModel: Codable {
    // Singer (nested)
    var singer: SingerModel?
    / / the song
    var name: String?
}

/// The singer model
struct SingerModel: Codable {
    / / name
    var name: String?
    / / age
    var age: Int?
}
Copy the code

JSONThe data structure

    let album = """ { "albumName": "Something Just Like This", "releaseTime": "2017-02-22", "songs":[ { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "Something Just Like This" }, { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "Closer" }, { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "Young" }, { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "All We Know" } ] } """
Copy the code

The conversion process is as follows:

if let jsonData = album.data(using: String.Encoding.utf8) {
    if let aAlbum = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
        dump(aAlbum)
    }
}
Copy the code

The output is:

JSONDecoderDemo.AlbumModel
   albumName: Optional("Something Just Like This")
    - some: "Something Just Like This"
   releaseTime: Optional("2017-02-22")
    - some: "2017-02-22"
   songs: Optional([JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Something Just Like This")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Closer")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Young")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("All We Know"))) some: 4 elements
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("Something Just Like This")
          - some: "Something Just Like This"
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("Closer")
          - some: "Closer"
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("Young")
          - some: "Young"
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("All We Know")
          - some: "All We Know"
Copy the code

Now that this nesting is solved, let’s take a look at the code for a more difficult one:

let album = """ { "albumName": "Something Just Like This", "releaseTime": "2017-02-22", "songs": { "favorite":[ { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "Something Just Like This" }, { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "Closer" }, { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "Young" }, { "singer": { "name":"The Chainsmokers", "age": 30 }, "name": "All We Know" } ] } } """
Copy the code

The JSON structure is a bit deeper than the data model AlbumModel, so how do you analyze it?

We use nestedContainer to handle this nesting. We first add the following code to AlbumModel:

/// Album model
struct AlbumModel: Codable {
    / / the album name
    var albumName: String?
    // Release time
    var releaseTime: String?
    / / song list
    var songs: [SongModel]?
    
    / / 1
    enum CodingKeys: String.CodingKey {
        case albumName, releaseTime, songs
    }
    / / 2
    enum favoriteKeys: CodingKey {
        case favorite
    }

    // Convert JSON to Model
    init(from decoder: Decoder) throws {
        / / 3
        let container = try decoder.container(keyedBy: CodingKeys.self)
        albumName = try container.decode(String.self, forKey: .albumName)
        releaseTime = try container.decode(String.self, forKey: .releaseTime)
        / / 4
        let favoriteContainer = try container.nestedContainer(keyedBy: favoriteKeys.self, forKey: .songs)
        songs = try favoriteContainer.decode([SongModel].self, forKey: .favorite)
    }

    // Code: convert Model to JSON
    func encode(to encoder: Encoder) throws {
        / / 5
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(albumName, forKey: .albumName)
        try container.encode(releaseTime, forKey: .releaseTime)
        / / 6
        var favoriteContainer = container.nestedContainer(keyedBy: favoriteKeys.self, forKey: .songs)
        try favoriteContainer.encode(songs, forKey: .favorite)
    }
}
Copy the code
// Song model
struct SongModel: Codable {
    / / singer
    var singer: SingerModel?
    / / the song
    var name: String?
}

/// The singer model
struct SingerModel: Codable {
    / / name
    var name: String?
    / / age
    var age: Int?
}
Copy the code

Parsing the following:

  1. First create the top CodingKeys
  2. Create the CodingKeys for the nested layer
  3. Create the container corresponding to the top-level CodingKeys and decode it
  4. Create the container for the nested layer and decode favorite
  5. Create an encoding container and encode albumName and releaseTime
  6. Get the nested container and code favorite

The conversion process:

if let jsonData = album.data(using: String.Encoding.utf8) {
    if let aAlbum = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
        dump(aAlbum)
    }
}
Copy the code

The output is as follows:

 JSONDecoderDemo.AlbumModel
   albumName: Optional("Something Just Like This")
    - some: "Something Just Like This"
   releaseTime: Optional("2017-02-22")
    - some: "2017-02-22"
   songs: Optional([JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Something Just Like This")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Closer")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Young")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("All We Know"))) some: 4 elements
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("Something Just Like This")
          - some: "Something Just Like This"
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("Closer")
          - some: "Closer"
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("Young")
          - some: "Young"
       JSONDecoderDemo.SongModel
         singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
           some: JSONDecoderDemo.SingerModel
             name: Optional("The Chainsmokers")
              - some: "The Chainsmokers"
             age: Optional(30)
              - some: 30
         name: Optional("All We Know")
          - some: "All We Know"
Copy the code

Challenge success, see here is not already a little dizzy, to tell the truth, I do not know what I am expressing, I also dizzy, ha ha!

Task 6: Handling derived classes

Let’s look at the next special data model structure and how it should be transformed.

When a class complies with the Codable protocol, it can easily JSON and dejson itself using the JSONEncoder and JSONDecoder, but if another class inherits from it, The jsonization and dejsonization of this subclass is not so convenient.

Let’s start with an example. Song is a subclass of Music:

class Music: Codable {
    var kind: String?
}

class Song: Music {
    var name: String?
}
Copy the code

JSONData as follows:

let jsonString = """ { "kind": "popular", "name": "Something Just Like This" } """
Copy the code

Data parsing:

if let jsonData = jsonString.data(using: String.Encoding.utf8) {
    if let song = try? JSONDecoder().decode(Song.self, from: jsonData) {
        dump(song)
    }
}
Copy the code

The results of:

 JSONDecoderDemo.Song #0
   super: JSONDecoderDemo.Music
     kind: Optional("popular")
      - some: "popular"
  - name: nil
Copy the code

This means that Codable is invalid in the lineage. If you declare that a derived class follows this protocol, you will report an error like this:

Redundant conformance of 'Song' to protocol 'Decodable'
Redundant conformance of 'Song' to protocol 'Encodable'
Copy the code

How can we solve this problem?

In this case, it’s time to implement a Codable protocol on your own with the following code:

class Song: Music {
    var name: String?
    
    enum CodingKeys: String.CodingKey {
        case type
        case name
    }
    
    init(type: String.name songName:String) {
        self.name = songName
        super.init(type: type)
    }
    
    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)
        type = try container.decode(String.self, forKey: .type)
        name = try container.decode(String.self, forKey: .name)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(type, forKey: .type)
        try container.encode(name, forKey: .name)
    }
}
Copy the code

Conversion results:

 JSONDecoderDemo.Song #0
   super: JSONDecoderDemo.Music
     type: Optional("popular")
      - some: "popular"
   name: Optional("Something Just Like This")
    - some: "Something Just Like This"
Copy the code

The result shows that I have successfully converted the JSON to the corresponding data model. For the derived class, we only need to refer to the above code and implement our own Codable protocol to avoid the above error.

Convert data model to JSON

When an object implementing the Codable protocol wants to be converted to JSON, the JSONEncoder is used to do so.

This transformation is relatively simple, here is a simple example!

let song = Song(type: "popular", name: "Something Just Like This")

if let jsonData = try? JSONEncoder().encode(song){
    if let jsonString = String.init(data: jsonData, encoding: String.Encoding.utf8){
        print("jsonString:" + "\(jsonString)")}}Copy the code

The output:

jsonString:{"type":"popular"."name":"Something Just Like This"}
Copy the code

It’s Easy to convert the data model to JSON.

conclusion

Here is the end of this article, first of all, thank you very much for your patience to see here, to tell the truth, I am a little painful when preparing this article, the more I write, the more boring, often in the process of writing the brain has been thinking: so boring content even I can not write down, there will be readers willing to read it? However, there is no turning back. After all, I also spent several days preparing the material, so I still finished writing with loneliness. The content is too boring, I hope you don’t dislike it.

Previous articles:

  • IOS handles network data elegantly, do you? Why don’t you read this
  • UICollectionView custom layout! Just read this one
  • Swift explore the SupplementaryView and Decoration View of UICollectionView
  • Swift custom layout achieves Cover Flow effect

Please have a drink ☕️ like + follow oh ~

  1. After reading, remember to give me a like oh, have 👍 have power

  2. Follow the public account — HelloWorld Jieshao, the first time to push new postures