How to exclude properties from Swift Codable?

48,195

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
  }
}
Share:
48,195
RamwiseMatt
Author by

RamwiseMatt

Updated on February 12, 2022

Comments

  • RamwiseMatt
    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
    Itai Ferber about 7 years
    You need to give nickname a default value for this to work. Otherwise, there's no value that can be assigned to the property on init(from:).
  • Code Different
    Code Different about 7 years
    @ItaiFerber I switched it to an optional, which was in my Xcode originally
  • Mark A. Donohoe
    Mark A. Donohoe over 6 years
    Are 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 the decode since that's where the asymmetry is coming from.
  • Code Different
    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
    ChrisH almost 4 years
    This was the clue for me - using a lazy var effectively making it a runtime property excluded it from Codable.
  • Leo
    Leo over 3 years
    Just 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
    Citizen_5 over 3 years
    Does the decoder automatically choose the right container when performing decoding or should i define a condition logic for that?
  • Rukshan
    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
    Bruno Muniz over 2 years
    Computed properties can't be lazy, can you expand your line of thought? @ChrisH
  • 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
    jguillen over 2 years
    I 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.