0

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)
    }    
}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
Gary Rudolph
  • 1,062
  • 9
  • 21
  • A Swift `struct` can conform to `Codable` without any code if all members of the struct conform to `Codable`. Do both `Message1` and `Message2` only have a single property of type `Data`? If so, simply declare an empty extension as `extension Message1: Codable {}`, for example, and you are done. – HangarRash Apr 11 '23 at 05:16
  • @HangarRash Unfortunately, `Message1` and `Message2` have multiple, complex, non-`Codable` properties. I'm serializing the whole object using protobuf's serialization to a `Data`, thus why I used `singleValueContainer`. I'm actually hoping to achieve what you mentioned which would be a generic extension that I can then have a. one-liner to extend each concrete struct. – Gary Rudolph Apr 11 '23 at 09:42

1 Answers1

1

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 { }
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks, finally got back to where I could give this a try. This might have worked in the same source, but since I'm including the source from another library I'm receiving a "Extension outside of file declaring struct 'Message1' prevents automatic synthesis of 'init(from:)' for protocol 'Decodable'". My understanding is the compiler when it compiles Message1 has no way to know there's another init:from implementation as it's in a different file? – Gary Rudolph May 12 '23 at 22:03
  • Are you declaring `extension Message1: CodableMessage`? Just `extension Message1: Codable` will not work for the reason you gave. – rob mayoff May 12 '23 at 23:39
  • Ahh, that worked, at least I only have to add it for every Message, vs. duplicate the Codable code. – Gary Rudolph Jun 06 '23 at 00:12