Skip to content
Hironobu Iga

Converting between camelCase and snake_case with Codable

Using JSONDecoder's keyDecodingStrategy and keyEncodingStrategy to absorb the difference in naming conventions without hand-writing CodingKeys.

Published

This article is also published elsewhere. https://qiita.com/iganin/items/6c6a588dd73731097698

Originally written in Japanese. This is a translation of the same piece.

Introduction

With Xcode 9.3, the default Swift version goes from 4.0 to 4.1. There are a number of improvements, and one of them looks useful around Codable, so I am sharing it as a note to self.

What follows is based on the beta as of 20 February. It may change by the time the final version ships, so please keep that in mind.

camelCase and snake_case

On iOS it is common to use camelCase for property names. A struct definition might look like this:

struct Person: Codable {
    let firstName: String
    let lastName: String
    let age: String
    let gender: String
}

Meanwhile, server API responses sometimes use snake_case:

{
  "first_name": "Tetsuya",
  "last_name": "Hayashi",
  "age": "30",
  "gender": "male"
}

Trying to parse a response like that with the Codable-conforming struct above does not work as-is, because firstName and first_name are different names. Until now you had to either write Person in snake_case or define CodingKeys separately, like this. It is bearable with only a few properties, but defining CodingKeys every time feels redundant.

struct Person: Codable {
    let firstName: String
    let lastName: String
    let age: String
    let gender: String

    private enum CodingKeys: String, CodingKey {
        case firstName = "first_name"
        case lastName = "last_name"
        case age
        case gender
    }
}

Swift 4.1 adds properties to JSONDecoder and JSONEncoder that solve this.

keyDecodingStrategy and keyEncodingStrategy

JSONDecoder gains a keyDecodingStrategy property and JSONEncoder a keyEncodingStrategy. Setting them makes converting between camelCase and snake_case straightforward.

Parsing the earlier JSON into the Person struct now looks like this. No more CodingKeys — much tidier.

let personJson = """
{
  "first_name": "Tetsuya",
  "last_name": "Hayashi",
  "age": "30",
  "gender": "male"
}
""".data(using: .utf8)!

let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase // convert from snakeCase here

do {
    let person = try jsonDecoder.decode(Person.self, from: personJson)
    print(person)
} catch {
    print(error.localizedDescription)
}

The reverse works too — converting camelCase to snake_case when encoding:

let person = Person(firstName: "Tetsuya", lastName: "Hayashi", age: "30", gender: "male")

let jsonEncoder = JSONEncoder()
jsonEncoder.keyEncodingStrategy = .convertToSnakeCase // convert to snakeCase here
do {
    let json = try jsonEncoder.encode(person)
    print(json)
} catch {
    print(error.localizedDescription)
}

Thoughts

Codable already struck me as a big convenience when it arrived, and it keeps getting better. Writing code is going to be that much more fun.

References