How to customize automatic synthesizing Codable for enums with associated values
source link: https://sarunw.com/posts/how-to-customize-automatic-synthesizing-codable-for-enums-with-associated-values/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How to customize automatic synthesizing Codable for enums with associated values
Table of Contents
Part 2 in a series on "Codable synthesis for enums with associated values". In the first part, we learn how easy it is to make enums with associated values conform to Codable protocol. This article will explore how much we can customize synthesized code to fit our needs.
You can easily support sarunw.com by checking out this sponsor.
Synthesized code
To customize default behavior, we must first understand what happened behind the scene.
We will use the same Role
enum from our previous example.
enum Role: Codable {
case admin
case guest(String?)
case member(id: String)
case vipMember(id: String, Int)
}
We learned from the previous article that it will encoded into a nested structure like this.
{
"admin" : {}
}
{
"guest" : {
"_0" : "John"
}
}
{
"member" : {
"id" : "1234"
}
}
{
"vipMember" : {
"id" : "1234",
"_1" : 5
}
}
I think it is easier to think of it in the form of a struct where each case is another struct with associated values as properties.
Here are similar structs which can represent the same JSON structure. This is for demonstration purposes only. It is not how synthesis work.
struct AdminStruct: Codable {}
struct GuestStruct: Codable {
let _0: String?
}
struct MemberStruct: Codable {
let id: String
}
struct VipMemberStruct: Codable {
let id: String
let _1: Int
}
struct RoleStruct: Codable {
let admin: AdminStruct?
let guest: GuestStruct?
let member: MemberStruct?
let vipMember: VipMemberStruct?
}
Given that enums are encoded into a nested structure, there are multiple CodingKeys declarations.
- One that contains the keys for each of the enum cases, which as before, is called
CodingKeys
. - One for each enum case that contains the keys for the associated values. These CodingKeys are prefixed with the capitalized case name, e.g.
AdminCodingKeys
for caseadmin
.
The compiler would generate something like this.
extension Role: Codable {
// contains keys for all cases of the enum
enum CodingKeys: CodingKey {
case admin
case guest
case member
case vipMember
}
// contains keys for all associated values of `case admin`
enum AdminCodingKeys: CodingKey {
}
// contains keys for all associated values of `case guest`
enum GuestCodingKeys: CodingKey {
case _0
}
// contains keys for all associated values of `case member`
enum MemberCodingKeys: CodingKey {
case id
}
// contains keys for all associated values of `case vipMember`
enum VipMemberCodingKeys: CodingKey {
case id
case _1
}
}
You can easily support sarunw.com by checking out this sponsor.
Customization
We have learned that enum with associated values encoded into the nested structure and generated multiple CodingKeys under the hood.
You can think of it like a nested struct
. So you can customize it just like how you did with a struct.
Map case to other names
You can map any case to a different name by specifying a string value to CodingKeys
case. In this case, vipMember
will be mapped to the vip
.
extension Role: Codable {
// contains keys for all cases of the enum
enum CodingKeys: String, CodingKey {
case admin
case guest
case member
case vipMember = "vip"
}
...
}
Role.vipMember(id: "1234", 5)
will encode to the following JSON.
{
"vip" : {
"id" : "1234",
"_1" : 5
}
}
Map value to other names
You can map any value key by specifying a string value, but instead of doing it on CodingKeys
, you do this on an enum case coding keys.
In this case, numberOfYears
will be mapped to the second value of vipMember(id: String, Int)
. Notice that we need to do this on corresponding coding keys, VipMemberCodingKeys
.
extension Role: Codable {
...
enum VipMemberCodingKeys: String, CodingKey {
case id
case _1 = "numberOfYears"
}
}
Role.vipMember(id: "1234", 5)
will encode to the following JSON.
"vipMember" : {
"id" : "1234",
"numberOfYears" : 5
}
Exclude cases
You can control which cases in an enum should be codable by modifying CodingKeys
declarations. You can exclude any case by removing it from the CodingKeys
declaration.
In this case, we remove vipMember
from CodingKeys
. The compiler will no longer synthesize the code for vipMember
.
extension Role: Codable {
// contains keys for all cases of the enum
enum CodingKeys: CodingKey {
case admin
case guest
case member
// case vipMember
}
...
}
An attempt to encode or decode a vipMember
value will cause an error to be thrown, so make sure you catch an operation or make it optional.
Encode vipMember
.
let role = Role.vipMember(id: "1234", 5)
let jsonData = try! JSONEncoder().encode(role)
// Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.EncodingError.invalidValue(enum_codable.Role.vipMember(id: "1234", 5), Swift.EncodingError.Context(codingPath: [], debugDescription: "Case \'vipMember\' cannot be decoded because it is not defined in CodingKeys.", underlyingError: nil))
Decode to vipMember
.
let jsonString = """
{
"vipMember" : {
"id" : "1234",
"_1" : 5
}
}
"""
let role = try! JSONDecoder().decode(Role.self, from: jsonString.data(using: .utf8)!)
// Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(enum_codable.Role, Swift.DecodingError.Context(codingPath: [], debugDescription: "Invalid number of keys found, expected one.", underlyingError: nil))
Exclude values
You can control which associated value in an enum case should be codable by modifying corresponding CodingKeys
.
You can safely exclude any value if you only conform to Encodable
. The excluded value will not be encoded to a JSON.
Role
only conforms to Encodable
here.
extension Role: Encodable {
...
enum VipMemberCodingKeys: CodingKey {
case id
// case _1
}
}
Role.vipMember(id: "1234", 5)
will encode to.
{
"vipMember" : {
"id" : "1234"
}
}
Things get a little complicated with Decodable
. Excluding any value mean the system won't know how to decode JSON to that particular value. This results in a compile error.
Type 'Role' does not conform to protocol 'Decodable' error.
To fix the problem, values that are excluded must have a default value defined.
Add a default value to the excluded value (the second value) of vipMember
to satisfied Decodable
conformance and fix the error.
enum Role {
case admin
case guest(String?)
case member(id: String)
case vipMember(id: String, Int = 1)
}
extension Role: Codable {
enum VipMemberCodingKeys: CodingKey {
case id
// case _1
}
...
}
You may also like
Swift 5.5 extends the support for the automatic synthesis to enums with associated values. Learn what can be expected from the synthesis code.
Swift Enum CodableWhen working with an unstable, legacy, or third party API, you might get a malformed object in an array. Learn how to decode a JSON array with corrupted data in Codable safely.
Swift CodableRead more article about Swift, Enum, Codable,
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.
How to quickly test apps in other languages with an Xcode scheme
A tip for creating multiple schemes to quickly run your app in different languages.
How to test UI layout for different languages with Pseudolanguages
Each language has its own characteristic. Some are more verbose than others. Some have special characters that take up vertical spaces. Some even read and lay out from right to left. Let's see how to make sure your layout is ready for this.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK