Search code examples
swiftencoding

Swift encoding: multiple .encode strategeis?


I have a model TestModel that encodes data to JSON to send to an API. It looks like this:

// Called by: JSONEncoder().encode(testModelObject)

class TestModel {
   enum CodingKeys: String, CodingKey {
        case someKey = "some_key"
        case otherKey = "other_key"
        case thirdKey = "third_key"
        case apiID = "id"
        // ... lots of other keys
   }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(someKeyValue, forKey: .someKey)
        try container.encode(otherKeyValue, forKey: .otherKey)
        try container.encode(thirdKeyValue, forKey: .thirdKey)
        // ... lots of other encoded fields
    }
}

The above works fine, however sometimes I wish to send a request to a different endpoint that updates just a single attribute. The update is always going to be for the same attribute. At present I'm sending through all data in encode(), which equals a lot of wasted bandwidth.

I'm sure there's an easy way to do this, but docs/google/stackoverflow aren't proving helpful. So: any thoughts on how to create a second encoding strategy along the lines of the below and call it?

func encodeForUpdate(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(apiID, forKey: .apiID)
    try container.encode(justOneValueToUpdate, forKey: .someKey)
}

Solution

  • You need to have a single encode(to encoder: Encoder) function but you can solve this by using a specific CodingKey enum for the second strategy

    enum SimpleCodingKeys: String, CodingKey {
        case thirdKey = "third_key"
        case apiID = "id"
    }
    

    and then use the userInfo property of JSONEncoder to tell when you want to use this second enum. First we need a key to use

    extension TestModel {
        static var useSimpleCodingKeys: CodingUserInfoKey {
            return CodingUserInfoKey(rawValue: "useSimpleCodingKeys")!
        }
    }
    

    and then adjust the encoding function

    func encode(to encoder: Encoder) throws {
        let useSimple = encoder.userInfo[Self.useSimpleCodingKeys] as? Bool ?? false
        if useSimple {
            var container = encoder.container(keyedBy: SimpleCodingKeys.self)
            try container.encode(apiID, forKey: .apiID)
            try container.encode(thirdKeyValue, forKey: .thirdKey)
        } else {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(someKeyValue, forKey: .someKey)
            try container.encode(otherKeyValue, forKey: .otherKey)
            try container.encode(thirdKeyValue, forKey: .thirdKey)
            ...
        }
    }
    

    And of course set this value in the dictionary when encoding

    let encoder = JSONEncoder()
    encoder.userInfo[TestModel.useSimpleCodingKeys] = true