How to exclude properties from Swift Codable?
Solution 1
The list of keys to encode/decode is controlled by a type called CodingKeys
(note the s
at the end). The compiler can synthesize this for you but can always override that.
Let's say you want to exclude the property nickname
from both encoding and decoding:
struct Person: Codable {
var firstName: String
var lastName: String
var nickname: String?
private enum CodingKeys: String, CodingKey {
case firstName, lastName
}
}
If you want it to be asymmetric (i.e. encode but not decode or vice versa), you have to provide your own implementations of encode(with encoder: )
and init(from decoder: )
:
struct Person: Codable {
var firstName: String
var lastName: String
// Since fullName is a computed property, it's excluded by default
var fullName: String {
return firstName + " " + lastName
}
private enum CodingKeys: String, CodingKey {
case firstName, lastName, fullName
}
// We don't want to decode `fullName` from the JSON
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
}
// But we want to store `fullName` in the JSON anyhow
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
try container.encode(fullName, forKey: .fullName)
}
}
Solution 2
Solution with custom property wrapper
struct Person: Codable {
var firstName: String
var lastName: String
@CodableIgnored
var nickname: String?
}
Where CodableIgnored
is
@propertyWrapper
public struct CodableIgnored<T>: Codable {
public var wrappedValue: T?
public init(wrappedValue: T?) {
self.wrappedValue = wrappedValue
}
public init(from decoder: Decoder) throws {
self.wrappedValue = nil
}
public func encode(to encoder: Encoder) throws {
// Do nothing
}
}
extension KeyedDecodingContainer {
public func decode<T>(
_ type: CodableIgnored<T>.Type,
forKey key: Self.Key) throws -> CodableIgnored<T>
{
return CodableIgnored(wrappedValue: nil)
}
}
extension KeyedEncodingContainer {
public mutating func encode<T>(
_ value: CodableIgnored<T>,
forKey key: KeyedEncodingContainer<K>.Key) throws
{
// Do nothing
}
}
Solution 3
Another way to exclude some properties from encoder, separate coding container can be used
struct Person: Codable {
let firstName: String
let lastName: String
let excludedFromEncoder: String
private enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
private enum AdditionalCodingKeys: String, CodingKey {
case excludedFromEncoder
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let anotherContainer = try decoder.container(keyedBy: AdditionalCodingKeys.self)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
excludedFromEncoder = try anotherContainer(String.self, forKey: . excludedFromEncoder)
}
// it is not necessary to implement custom encoding
// func encode(to encoder: Encoder) throws
// let person = Person(firstName: "fname", lastName: "lname", excludedFromEncoder: "only for decoding")
// let jsonData = try JSONEncoder().encode(person)
// let jsonString = String(data: jsonData, encoding: .utf8)
// jsonString --> {"firstName": "fname", "lastName": "lname"}
}
same approach can be used for decoder
Solution 4
If we need to exclude decoding of a couple of properties from a large set of properties in the structure, declare them as optional properties. Code to unwrapping optionals is less than writing a lot of keys under CodingKey enum.
I would recommend using extensions to add computed instance properties and computed type properties. It separates codable comforming properties from other logic hence provides better readability.
Solution 5
You can use computed properties:
struct Person: Codable {
var firstName: String
var lastName: String
var nickname: String?
var nick: String {
get {
nickname ?? ""
}
}
private enum CodingKeys: String, CodingKey {
case firstName, lastName
}
}
RamwiseMatt
Updated on February 12, 2022Comments
-
RamwiseMatt over 2 years
Swift's
Encodable
/Decodable
protocols, released with Swift 4, make JSON (de)serialization quite pleasant. However, I have not yet found a way to have fine-grained control over which properties should be encoded and which should get decoded.I have noticed that excluding the property from the accompanying
CodingKeys
enum excludes the property from the process altogether, but is there a way to have more fine-grained control? -
Itai Ferber about 7 yearsYou need to give
nickname
a default value for this to work. Otherwise, there's no value that can be assigned to the property oninit(from:)
. -
Code Different about 7 years@ItaiFerber I switched it to an optional, which was in my Xcode originally
-
Mark A. Donohoe over 6 yearsAre you sure you have to provide the
encode
in the asymmetric example? Since that is still the standard behavior, I didn't think it was needed. Just thedecode
since that's where the asymmetry is coming from. -
Code Different over 6 years@MarqueIV Yes, you have to. Since
fullName
cannot be mapped to a stored property, you must provide a custom encoder and decoder. -
ChrisH almost 4 yearsThis was the clue for me - using a
lazy var
effectively making it a runtime property excluded it from Codable. -
Leo over 3 yearsJust tested this in Swift 5. You should only need to define a constant for the property you which not to decode. You do not need to explicitly add the keys to
CodingKeys
. So,var nickname: String { get { "name" } }
should suffice. -
Citizen_5 over 3 yearsDoes the decoder automatically choose the right container when performing decoding or should i define a condition logic for that?
-
Rukshan almost 3 years@Citizen_5 decoder has logic to use both codingkey enums (regular CodingKeys and custom one), but encoder only knows about CodingKeys, that's why it works this way.
-
Bruno Muniz over 2 yearsComputed properties can't be lazy, can you expand your line of thought? @ChrisH
-
ChrisH over 2 years@BrunoMuniz honestly cannot remember why I would have made this comment, but I would most likely have been referring to something like
lazy var x:String = { //Computed string }()
which I used instead of a computed property although I can't really imagine why. -
jguillen over 2 yearsI works fine but I got into a trouble for the private access modifier, so I just removed it. Just in case anybody runs into this problem too.