We're using Swift Protobuf in a project that generates the message struct
code (i.e. DTOs). It doesn't implement Codable
and I'm trying to add Codable
through an extension
so that I can serialize them to disk when used inside a non-protobuf that conforms to Codable
. However, it's quite boilerplate to do this for every struct
.
Any recommendations on how to minimize the boiler plate code, other than using a static, generic method? Is there a way to do this using generics in the extension definition? Ideally, I'd like to replace Message1
and Message2
with a generic extension.
With Swift Protobuf all message structs have a Message
extension applied, but they do not have a common protocol I can use in a where
clause for an extension.
extension Message1: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
self = try Message1(serializedData: data)
}
public func encode(to encoder: Encoder) throws {
let data = try self.serializedData()
var container = encoder.singleValueContainer()
try container.encode(data)
}
}
extension Message2: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
self = try Message2(serializedData: data)
}
public func encode(to encoder: Encoder) throws {
let data = try self.serializedData()
var container = encoder.singleValueContainer()
try container.encode(data)
}
}
First, declare a protocol CodableMessage
that refines both SwiftProtobuf.Message
and Codable
. Use an extension to provide default implementations of init(from:)
and encode(to:)
.
// Use this narrow import to avoid importing SwiftProtobuf.Decoder, since
// that will conflict with Swift.Decoder and generally be annoying.
import protocol SwiftProtobuf.Message
public protocol CodableMessage: SwiftProtobuf.Message, Codable { }
extension CodableMessage {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let data = try container.decode(Data.self)
self = try Self(serializedData: data)
}
public func encode(to encoder: Encoder) throws {
let data = try self.serializedData()
var container = encoder.singleValueContainer()
try container.encode(data)
}
}
Then, extend each of your generated message types to conform to your new protocol:
extension Message1: CodableMessage { }
extension Message2: CodableMessage { }