Swift 4.1 feature - keyEncoding/keyDecoding strategy

Can't believe it's been 3 and half years but I still didn't run a single Swift program over command line. However, that is no longer going to happen. The occasion is an unreleased Swift version 4.1 (As of 02/14/2018) and my desire to try out new cool feature in this release. Of course, I can wait but this opportunity will allow me to try something new and experience this feature early on.

For the sake of convenience, this tutorial is divided into following sections, starting from downloading bleeding edge Swift version to run a Codable feature through the terminal.

  1. Downloading Swift 4.1 Development

  2. Installing Swift

  3. Changing the Swift version used by the command line

  4. Setting up the base project

  5. Adding demo

  6. Some thoughts and observations

    1. Download Swift 4.1 Development

    Since it is too early, Swift 4.1 won't be available through any of the Xcode versions. So our only option is to go to Swift.org page, browse to Downloads section and download the Swift 4.1 Development package which is automatically created from swift-4.1-branch

    After you download it, double-click the pkg file and go through the regular installation process.

    1. Installing Swift

    Next step is to verify and install the Swift version that you just downloaded. You can see the currently downloaded Swift snapshots in the default directory under ~/Library/Developer/Toolchains

    So if I want to see all the Swift versions on my machine, I will browse to ~/Library/Developer/Toolchains and just list of snapshots present under that directory.

    cd ~/Library/Developer/Toolchains

    tp-6

    As you can see from above screenshot, I have two versions installed on my machine. The one that came with Xcode 9.2 and another one I just installed.

    However, if I run swift --version, it still shows me the old Swift version 4.0.3. We need to change it before trying to run any feature exclusive to Swift 4.1.

    tp-1-1

    1. Changing the Swift version over the command line

    Taken directly from Swift.org page

    To select any other installed toolchain (Re: Swift version), use its identifier in the TOOLCHAINS variable. The identifier can be found in toolchain’s Info.plist file. Type the following command in terminal

    /usr/libexec/PlistBuddy -c "Print CFBundleIdentifier:" ~/Library/Developer/Toolchains/swift-4.1-DEVELOPMENT-SNAPSHOT-2018-02-08-a.xctoolchain/Info.plist

    Please note how we have pointed above command towards Swift-4.1 development snapshot. Please make sure to do this to avoid selecting existing Swift version

    This will give us the identifier present in a plist file associated with that Swift version. Which we can now use to select our latest Swift snapshot. For example, if above command gives output as follows,

    org.swift.4120180208a

    This value can now be used to set the current Swift version as follows,

    export TOOLCHAINS=org.swift.4120180208a

    Now, if you again type swift --version, it should now point to Swift 4.1

    tp-1--1--1

    1. Setting up the base project

    We will now set up the base project to run the demo over command line. Unfortunately, since this is a dev version we cannot use Xcode to run this Swift 4.1 compatible code. First off, you will need to create a scaffold project to run your code with.

    A. For the simplest case, navigate to the Desktop
    cd ~/Desktop

    B. We will now follow these steps to build executables

    Make a directory named Codable and step into it

    mkdir Codable
    cd Codable
    

    Now run Swift package's init method with type executable

    swift package init --type executable

    This will generate an executable package under current directory

    C. Now stay in the same folder and run,
    swift run Codable

    If everything was done correctly, you will see the following output

    tp-2

    Once these steps are done, you're good to start with actual development adding new features that are supported exclusively on Swift 4.1

    1. Adding demo

      In this step, we will add a demo to show the capabilities of keyCodingStrategy feature. Please remember that you are in the base folder of this scaffold project. Every time you run swift run Codable, it will execute the main.swift file present in Sources/Codable folder. This file acts as an entry point for this project.

      Since you're inside one project, it is safe to ignore the word Codable while running the above-mentioned command. You can even run the project with only swift run

      Browse into Sources/Codable folder and make a file called Person.swift. This file will represent the Person struct we are going to decode incoming JSON into. Add the following content and save the file

      struct Person: Codable {
          let personName: String
          let personCity: String
          let personSalary: Int
      }    
      

    Now we will create another file which will have the dummy JSON response. For the sake of simplicity, let's assume this JSON comes directly from the remote API call. Create a file called jsonResponse.swift and add following content to it.

    import Foundation
    
    let jsonString = """
    [
        {
            "person_name": "Jayesh",
            "person_city": "Boston",
            "person_salary": 4000
        },
        {
            "person_name": "Amit",
            "person_city": "Mumbai",
            "person_salary": 40000
        }
    ]
    """
    
    let jsonData = Data(jsonString.utf8)
        
    

    In next few lines, we will use this JSON to demonstrate decoding capabilities of new Swift 4.1 feature

    In the next and final step, create a file called customCoding.swift and add following content to it. This file will contain logic to encode and decode data from JSON.

    import Foundation
    
    func decodeValues() {
        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        do {
            let people = try decoder.decode([Person].self, from: jsonData)
            // Prints: [Codable.Person(personName: "Jayesh", personCity: "Boston", personSalary: 4000), Codable.Person(personName: "Amit", personCity: "Mumbai", personSalary: 40000)]
            print(people)
        } catch {
            print(error.localizedDescription)
        }
    }
    
    func encodeValues() {
        let encoder = JSONEncoder()
        encoder.keyEncodingStrategy = .convertToSnakeCase
    
        let people = [Person(personName: "Pakshata", personCity: "Pune", personSalary: 50000), Person(personName: "Roger", personCity: "Zurich", personSalary: 500)]
    
        do {
            let encodedPeople = try encoder.encode(people)
            // Prints: [{"person_name":"Pakshata","person_city":"Pune","person_salary":50000},{"person_name":"Roger","person_city":"Zurich","person_salary":500}]
            print(String(data: encodedPeople,encoding: .utf8)!)
        } catch {
            print(error.localizedDescription)
        }
    }
    

    Also, change the content of main.swift file to call methods to encode and decode values

    decodeValues()
    encodeValues()
    

    Before you run the code, make sure to run swift --version in the terminal and see if Swift 4.1 version is selected. If this is not the case, you can follow the instructions mentioned in step 3 above

    Now you are ready to run the code from command line as follows,

    swift run Codable

    If everything went well, it will produce the following output,

    tp-2--1-

  7. Some thoughts and observations

    Swift community has brought us with this new feature to handle snake and camel case painlessly. It's certainly a spectacular improvement. You can verify this change by removing the line which sets keyDecodingStrategy to encoder/decoder and these operations will throw a missing key exception.

    It is important to note that even though keyDecodingStrategy allows us to skip CodingKeys, camel case and snake case keys should be properly mapped to each other. If no valid mapping exists, you will get missing key exception

    Some examples of valid mappings,

    firstName - first_name
    cityName - city_name
    thisIsAwesomeTheroy - this_is_awesome_theory
    

    Some examples of invalid mapping,

    params - regex
    firstname - first_name
    LastPerson - last_person
    

    You might also have observed that how easy it is to create a Swift package and subsequently run the program over command line.

    Other caveats are, unless your file is standalone, meaning it does not depend on method or structs offered by other files, you cannot compile it individually. You will have to compile it together as the entire project. In the above example, you can compile Person.swift and jsonResponse.swift files using swiftc <file_name> command. However, an attempt to compile customCoding.swift will result in compiler error since this file uses variables and structs provided by other two files.

    In addition, you may also need to import appropriate frameworks in related files. For these examples, we are not doing anything fancy so using just import Foundation works. But this might not be the case with more complex codes.

    Sad that at this point Swift 4.1 is still in development. Hopefully, Apple will release the next Xcode version soon and we will all be able to use new Codable feature in the app, removing all the hard-coded snake case keys

References:

  1. Swift.org
  2. Swift 4.1 improves Codable with keyDecodingStrategy