What you can do is define a custom CodingKey
that essentially combines both your model's coding keys and any other coding keys you want, like so (warning - there's a lot of code here, but I made it so it's generic enough to be reusable for any model in any project):
protocol CompoundableCodingKey: CodingKey {
associatedtype OtherCodingKeys1
associatedtype OtherCodingKeys2
init(otherCodingKeys1: OtherCodingKeys1)
init(otherCodingKeys2: OtherCodingKeys2)
init(intValue: Int)
init(stringValue: String)
}
struct CompoundCodingKeys<OtherCodingKeys1: CodingKey, OtherCodingKeys2: CodingKey>: CompoundableCodingKey {
private let otherCodingKeys1: OtherCodingKeys1?
private let otherCodingKeys2: OtherCodingKeys2?
private let internalIntValue: Int?
private let internalStringValue: String
var intValue: Int? {
if let otherCodingKeys1 = otherCodingKeys1 {
return otherCodingKeys1.intValue
} else if let otherCodingKeys2 = otherCodingKeys2 {
return otherCodingKeys2.intValue
}
return internalIntValue
}
var stringValue: String {
if let otherCodingKeys1 = otherCodingKeys1 {
return otherCodingKeys1.stringValue
} else if let otherCodingKeys2 = otherCodingKeys2 {
return otherCodingKeys2.stringValue
}
return internalStringValue
}
init(intValue: Int) {
if let otherCodingKeys1 = OtherCodingKeys1(intValue: intValue) {
self.otherCodingKeys1 = otherCodingKeys1
otherCodingKeys2 = nil
internalIntValue = nil
internalStringValue = otherCodingKeys1.stringValue
} else if let otherCodingKeys2 = OtherCodingKeys2(intValue: intValue) {
otherCodingKeys1 = nil
self.otherCodingKeys2 = otherCodingKeys2
internalIntValue = nil
internalStringValue = otherCodingKeys2.stringValue
} else {
otherCodingKeys1 = nil
otherCodingKeys2 = nil
internalIntValue = intValue
internalStringValue = intValue.description
}
}
init(stringValue: String) {
if let otherCodingKeys1 = OtherCodingKeys1(stringValue: stringValue) {
self.otherCodingKeys1 = otherCodingKeys1
otherCodingKeys2 = nil
internalIntValue = nil
internalStringValue = otherCodingKeys1.stringValue
} else if let otherCodingKeys2 = OtherCodingKeys2(stringValue: stringValue) {
otherCodingKeys1 = nil
self.otherCodingKeys2 = otherCodingKeys2
internalIntValue = nil
internalStringValue = otherCodingKeys2.stringValue
} else {
otherCodingKeys1 = nil
otherCodingKeys2 = nil
internalIntValue = nil
internalStringValue = stringValue
}
}
init(otherCodingKeys1: OtherCodingKeys1) {
self.init(stringValue: otherCodingKeys1.stringValue)
}
init(otherCodingKeys2: OtherCodingKeys2) {
self.init(stringValue: otherCodingKeys2.stringValue)
}
}
extension KeyedEncodingContainerProtocol where Key: CompoundableCodingKey {
mutating func encode<T>(_ value: T, forKey key: Key.OtherCodingKeys1) throws where T: Encodable {
try encode(value, forKey: Key(otherCodingKeys1: key))
}
mutating func encode<T>(_ value: T, forKey key: Key.OtherCodingKeys2) throws where T: Encodable {
try encode(value, forKey: Key(otherCodingKeys2: key))
}
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key.OtherCodingKeys1) throws where T: Encodable {
try encodeIfPresent(value, forKey: Key(otherCodingKeys1: key))
}
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key.OtherCodingKeys2) throws where T: Encodable {
try encodeIfPresent(value, forKey: Key(otherCodingKeys2: key))
}
mutating func encodeConditional<T>(_ object: T, forKey key: Key.OtherCodingKeys1) throws where T: AnyObject, T: Encodable {
try encodeConditional(object, forKey: Key(otherCodingKeys1: key))
}
mutating func encodeConditional<T>(_ object: T, forKey key: Key.OtherCodingKeys2) throws where T: AnyObject, T: Encodable {
try encodeConditional(object, forKey: Key(otherCodingKeys2: key))
}
}
extension KeyedEncodingContainerProtocol where Key: CompoundableCodingKey, Key.OtherCodingKeys1: CompoundableCodingKey {
mutating func encode<T>(_ value: T, forKey key: Key.OtherCodingKeys1.OtherCodingKeys1) throws where T: Encodable {
try encode(value, forKey: Key.OtherCodingKeys1(otherCodingKeys1: key))
}
mutating func encode<T>(_ value: T, forKey key: Key.OtherCodingKeys1.OtherCodingKeys2) throws where T: Encodable {
try encode(value, forKey: Key.OtherCodingKeys1(otherCodingKeys2: key))
}
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key.OtherCodingKeys1.OtherCodingKeys1) throws where T: Encodable {
try encodeIfPresent(value, forKey: Key.OtherCodingKeys1(otherCodingKeys1: key))
}
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key.OtherCodingKeys1.OtherCodingKeys2) throws where T: Encodable {
try encodeIfPresent(value, forKey: Key.OtherCodingKeys1(otherCodingKeys2: key))
}
mutating func encodeConditional<T>(_ object: T, forKey key: Key.OtherCodingKeys1.OtherCodingKeys1) throws where T: AnyObject, T: Encodable {
try encodeConditional(object, forKey: Key.OtherCodingKeys1(otherCodingKeys1: key))
}
mutating func encodeConditional<T>(_ object: T, forKey key: Key.OtherCodingKeys1.OtherCodingKeys2) throws where T: AnyObject, T: Encodable {
try encodeConditional(object, forKey: Key.OtherCodingKeys1(otherCodingKeys2: key))
}
}
extension KeyedEncodingContainerProtocol where Key: CompoundableCodingKey, Key.OtherCodingKeys2: CompoundableCodingKey {
mutating func encode<T>(_ value: T, forKey key: Key.OtherCodingKeys2.OtherCodingKeys1) throws where T: Encodable {
try encode(value, forKey: Key.OtherCodingKeys2(otherCodingKeys1: key))
}
mutating func encode<T>(_ value: T, forKey key: Key.OtherCodingKeys2.OtherCodingKeys2) throws where T: Encodable {
try encode(value, forKey: Key.OtherCodingKeys2(otherCodingKeys2: key))
}
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key.OtherCodingKeys2.OtherCodingKeys1) throws where T: Encodable {
try encodeIfPresent(value, forKey: Key.OtherCodingKeys2(otherCodingKeys1: key))
}
mutating func encodeIfPresent<T>(_ value: T?, forKey key: Key.OtherCodingKeys2.OtherCodingKeys2) throws where T: Encodable {
try encodeIfPresent(value, forKey: Key.OtherCodingKeys2(otherCodingKeys2: key))
}
mutating func encodeConditional<T>(_ object: T, forKey key: Key.OtherCodingKeys2.OtherCodingKeys1) throws where T: AnyObject, T: Encodable {
try encodeConditional(object, forKey: Key.OtherCodingKeys2(otherCodingKeys1: key))
}
mutating func encodeConditional<T>(_ object: T, forKey key: Key.OtherCodingKeys2.OtherCodingKeys2) throws where T: AnyObject, T: Encodable {
try encodeConditional(object, forKey: Key.OtherCodingKeys2(otherCodingKeys2: key))
}
}
extension KeyedDecodingContainerProtocol where Key: CompoundableCodingKey {
func decode<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys1) throws -> T where T: Decodable {
try decode(type, forKey: Key(otherCodingKeys1: key))
}
func decode<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys2) throws -> T where T: Decodable {
try decode(type, forKey: Key(otherCodingKeys2: key))
}
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys1) throws -> T? where T: Decodable {
try decodeIfPresent(type, forKey: Key(otherCodingKeys1: key))
}
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys2) throws -> T? where T: Decodable {
try decodeIfPresent(type, forKey: Key(otherCodingKeys2: key))
}
}
extension KeyedDecodingContainerProtocol where Key: CompoundableCodingKey, Key.OtherCodingKeys1: CompoundableCodingKey {
func decode<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys1.OtherCodingKeys1) throws -> T where T: Decodable {
try decode(type, forKey: Key.OtherCodingKeys1(otherCodingKeys1: key))
}
func decode<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys1.OtherCodingKeys2) throws -> T where T: Decodable {
try decode(type, forKey: Key.OtherCodingKeys1(otherCodingKeys2: key))
}
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys1.OtherCodingKeys1) throws -> T? where T: Decodable {
try decodeIfPresent(type, forKey: Key.OtherCodingKeys1(otherCodingKeys1: key))
}
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys1.OtherCodingKeys2) throws -> T? where T: Decodable {
try decodeIfPresent(type, forKey: Key.OtherCodingKeys1(otherCodingKeys2: key))
}
}
extension KeyedDecodingContainerProtocol where Key: CompoundableCodingKey, Key.OtherCodingKeys2: CompoundableCodingKey {
func decode<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys2.OtherCodingKeys1) throws -> T where T: Decodable {
try decode(type, forKey: Key.OtherCodingKeys2(otherCodingKeys1: key))
}
func decode<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys2.OtherCodingKeys2) throws -> T where T: Decodable {
try decode(type, forKey: Key.OtherCodingKeys2(otherCodingKeys2: key))
}
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys2.OtherCodingKeys1) throws -> T? where T: Decodable {
try decodeIfPresent(type, forKey: Key.OtherCodingKeys2(otherCodingKeys1: key))
}
func decodeIfPresent<T>(_ type: T.Type, forKey key: Key.OtherCodingKeys2.OtherCodingKeys2) throws -> T? where T: Decodable {
try decodeIfPresent(type, forKey: Key.OtherCodingKeys2(otherCodingKeys2: key))
}
}
Then, if we change SharedCodingKeyValues
into a CodingKey
enum, like so:
enum SharedCodingKeys: String, CodingKey {
case version
case serializer
}
...we can then get a container keyed by the type CompoundCodingKeys<CodingKeys, SharedCodingKeys>
, which lets you specify either Person.CodingKeys
(which does not need to be manually defined), or SharedCodingKeys
, like so:
// MARK: Encodable
extension Person {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CompoundCodingKeys<CodingKeys, SharedCodingKeys>.self)
// From `Person.CodingKeys`
try container.encode(name, forKey: .name)
// From `SharedCodingKeys`
try container.encode(version, forKey: .version)
try container.encode(serializerName, forKey: .serializer)
}
}
Since CompoundCodingKeys
itself conforms to CodingKey
, you can even take this one step further and combine as many coding keys as you want into one, like CompoundCodingKeys<CodingKeys, CompoundCodingKeys<SomeOtherCodingKeys, SharedCodingKeys>>
:
enum SomeOtherCodingKeys: String, CodingKey {
case someOtherKey
}
// MARK: Encodable
extension Person {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CompoundCodingKeys<CodingKeys, CompoundCodingKeys<SomeOtherCodingKeys, SharedCodingKeys>>.self)
// From `Person.CodingKeys`
try container.encode(name, forKey: .name)
// From `SharedCodingKeys`
try container.encode(version, forKey: .version)
try container.encode(serializerName, forKey: .serializer)
// From `SomeOtherCodingKeys`
try container.encode(serializerName, forKey: .someOtherKey)
}
}