How to set custom CodingKey for the convertFromSnakeCase decoding strategy
Table of Contents
Swift uses Camel case naming convention, but not all back-end languages use the same convention. Ruby, for example, uses Snake case when naming variables. This naming convention usually reflects on JSON API key naming.
Here is an example of a snake case API response.
[
{
"id": 1,
"node_id": "MDQ6VGVhbTE=",
"url": "https://api.github.com/teams/1",
"html_url": "https://github.com/orgs/github/teams/justice-league",
"name": "Justice League",
"slug": "justice-league",
"description": "A great team.",
"privacy": "closed",
"permission": "admin",
"members_url": "https://api.github.com/teams/1/members{/member}",
"repositories_url": "https://api.github.com/teams/1/repos",
"parent": null
}
]
So, you probably encounter both camelCase and snake_case naming in your API response.
Luckily, Apple provided an easy way to convert from snake to camel case with .convertFromSnakeCase
decoding strategy. In this article, I will explore the .convertFromSnakeCase
decoding strategy and highlight things you should know when using it with a custom coding key.
What is the convertFromSnakeCase decoding strategy
convertFromSnakeCase is a decoding strategy that converts snake-case keys to camel-case keys.
For demonstration, I will try to convert a snake-case JSON to a User struct with camel-case naming.
Here is our User struct.
struct User: Decodable {
let firstName: String
let lastName: String
}
And here is a JSON string and how we decode to User struct.
let jsonString = """
{
"first_name": "John",
"last_name": "Doe"
}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let user = try! decoder.decode(User.self, from: data)
print(user)
// User(firstName: "John", lastName: "Doe")
JSON has a snake case naming, but our user struct use camel case. Using .convertFromSnakeCase
will handle the key conversion for us.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
How convertFromSnakeCase do the conversion
Behind the scene, this strategy follows these three steps to convert JSON keys to camel-case:
- Capitalize each word that follows an underscore.
- Remove all underscores that aren’t at the very start or end of the string.
- Combine the words into a single string.
Normally, you shouldn't need to care about these rules, but if you have some fancy naming which the above rules do not meet your expectation, these rules are crucial when you try to do a custom key conversion.
There are two namings that I want to highlight here.
- Name with underscores.
- Name with inconsistent capital letters.
Underscore
There is some unclear behavior for rules number one and two. That's why I want to make it more clear in this section.
Capitalize each word that follows an underscore.
This only applies to underscore between words, not at the very start and end of the string.
The following examples show the result of applying this strategy:
// 1
_private converts to _private not _Private
// 2
__private converts to: __private not __Private
1 Letter p follows an underscore, but it doesn't get converted to _Private
.
2 Underscore at the very start can be any numbers. In this case, we have two underscores, and p is not capitalized.
Remove all underscores that aren’t at the very start or end of the string.
For this rule, underscores at the very start and end can be in any numbers. As long as they are not between words, they are all kept.
The following examples show the result of applying this strategy:
// 1
___leading_ converts to ___leading_, not _leading_
// 2
_trailing___ converts to _trailing___, not _trailing_
// 3
first__name converts to firstName
// 4
last___name converts to lastName
1 Underscore at the very start can be any numbers. In this case, we have three underscores, and all of them are preserved.
2 The same rule applies to underscores at the very end.
3, 4 Regardless of numbers, all underscores between each word will be removed.
I don't think this will be a problem since you probably name your key with many underscores, but it is good to know the rule since you need to use it when doing a custom key naming.
Inconsistant captial letters
This is more likely to happen, and you might want to take a closer look at this problem.
From rule number one, the .convertFromSnakeCase
strategy only capitalizes each word, and it can't infer capitalization for acronyms or initialisms such as WYSIWYG or URI.
Capitalize each word that follows an underscore.
The following examples show the result of applying this strategy:
avatar_url
converts to avatarUrl
.
customer_id
converts to customerId
.
html_string
converts to htmlString
.
This might not be a serious issue, but if you want it to be capitalized the way you want, you need to specify a custom CodingKey
. This is where your knowledge about the converting rules is put into use.
Custom CodingKey
For demonstration, let create a new struct with acronym and initialisms capitalized.
struct SnakeToCamel: Decodable {
let avatarURL: String
let customerID: String
let HTMLString: String
}
If you try to decode the following JSON String to SnakeToCamel
struct, you will get a key not found decoding error.
let jsonString = """
{
"avatar_url": "Avatar URL",
"customer_id": "Customer ID",
"html_string": "HTML String"
}
"""
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let snakeToCamel = try! decoder.decode(SnakeToCamel.self, from: data)
print(snakeToCamel)
You will get an error that looks like this:
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "avatarURL", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "avatarURL", intValue: nil) ("avatarURL"), with divergent representation avatarUrl, converted to avatar_url.", underlyingError: nil))
This error comes from the fact that our Swift variable key not matched the JSON key. If you use Codable before, you know you can have a different mapping between JSON key and Swift variable name. You can do this with the help of CodingKey.
Problem
Let's try to add a custom coding key for our mapping.
We add a coding key to map between JSON key and our property names in the following example.
struct SnakeToCamel: Decodable {
let avatarURL: String
let customerID: String
let HTMLString: String
enum CodingKeys: String, CodingKey {
case avatarURL = "avatar_url"
case customerID = "customer_id"
case HTMLString = "html_string"
}
}
Try to decode again, and you will get a similar error. You get the same key not found error, but the key is the one you just specified (avatar_url
) this time.
Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "avatar_url", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "avatar_url", intValue: nil) ("avatar_url").", underlyingError: nil))
Solution
The solution is to set a coding key to the value that matches the one after it gets converted by the .convertFromSnakeCase
decoding strategy. In this case, it would be the following:
avatar_url
converts to avatarUrl
.
customer_id
converts to customerId
.
html_string
converts to htmlString
.
So, our coding key should look like this.
struct SnakeToCamel: Decodable {
let avatarURL: String
let customerID: String
let HTMLString: String
enum CodingKeys: String, CodingKey {
case avatarURL = "avatarUrl"
case customerID = "customerId"
case HTMLString = "htmlString"
}
}
Try to decode again. This time it should be successfully decoded.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Conclusion
When you set a custom coding key, you have to map your property name to the value after converted with a specified decoding strategy. So to properly do it with .convertFromSnakeCase
, you have to understand how it converts from snake case to camel case. That is what we learned in How convertFromSnakeCase do the conversion.
Read more article about Swift or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to define custom Environment Values in SwiftUI
It might not be obvious that we can create a custom environment value, but we can do that. The steps to create one are not as straightforward as we usually do, but it isn't hard if you know how to do it.
How to make a custom button style with UIButton.Configuration in iOS 15
Learn how to create an outline button style.