Skip to content

Protobuf and SwiftData with full exposure of the Proto object graph for queries #1780

@Skoti

Description

@Skoti

To use SwiftProtobuf types in SwiftData models, they must conform to Codable.
Suppose you have:

@Model
final class Foo {
    var bar: Bar?
    
    init(bar: Bar? = nil) {
        self.bar = bar
    }
}

where Bar is a protobuf message type.

We cannot simply do:

extension Bar: Codable {}

public extension Message {
    init(from decoder: Swift.Decoder) throws {
        let container = try decoder.singleValueContainer()
        let data = try container.decode(Data.self)
        self = try Self(serializedBytes: data)
    }

    func encode(to encoder: Swift.Encoder) throws {
        let data = try serializedData()
        var container = encoder.singleValueContainer()
        try container.encode(data)
    }
}

because it will crash at runtime as SwiftData expects things to use a keyed container, and not a single-value one.

Ofc one can implement a custom init(from:) and encode(to:) per each message, but this is cumbersome.
It would be possible to write the implementation in one place (like above), if there were access to field names that SwiftProtobuf already generates, yet all related APIs are internal only (like visitors or _NampingMap).

So there are two possibilities:

  • make Protobuf types Codable (the default synthesized behavior already relies on keyed containers) - this has nothing to do with how Protobuf encodes things (so might be confusing for some people)

OR (probably preferable)

  • expose a way (like a custom Decoder and Visitor) to traverse each field and decode/encode into a keyed container, so folks wishing to opt into Codable can easily do this with just an extension Bar: Codable

Something like:

public extension Message {
    init(from decoder: Swift.Decoder) throws {
        init()
        try decodeMessage(decoder: CodableDecoder(decoder))
    }

    func encode(to encoder: Swift.Encoder) throws {
        try CodableEncoder(encoder).encode(self)
    }
}

Where CodableDecoder and CodableEncoder are provided by SwiftProtobuf.
They could even check for a custom userInfo key in the provided decoder/encoder to decide whether to use a single container (from 1st snippet) or a keyed container - so their use case is more flexible.

Would any route be of interest to implement?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions