truewebber/swift-protoreflect
A Swift library for dynamic Protocol Buffers message manipulation without pre-compiled schemas.
Overview
SwiftProtoReflect enables runtime manipulation of Protocol Buffers messages without requiring code generation from .proto files. This is useful for building generic tools, API gateways, data processors, and other applications that need to work with protobuf schemas dynamically.
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/truewebber/swift-protoreflect.git", from: "6.0.0")
]Note: If you are upgrading from 4.x, see the Migration Guide for details on breaking changes.
Basic Usage
Creating Messages Dynamically
import SwiftProtoReflect
// Define a message schema at runtime
var personSchema = MessageDescriptor(name: "Person", fullName: "Person")
personSchema.addField(FieldDescriptor(name: "name", number: 1, type: .string))
personSchema.addField(FieldDescriptor(name: "age", number: 2, type: .int32))
personSchema.addField(FieldDescriptor(name: "emails", number: 3, type: .string, isRepeated: true))
// Create and populate a message
var message = MessageFactory().createMessage(from: personSchema)
try message.set("Alice", forField: "name")
try message.set(Int32(25), forField: "age")
try message.set(["alice@example.com"], forField: "emails")
// Serialize to binary or JSON
// TypeRegistry is required for JSONSerializer and deserializers; empty registry is fine for scalar-only messages.
let registry = TypeRegistry()
let binaryData = try BinarySerializer().serialize(message)
let jsonData = try await JSONSerializer(options: .init(typeRegistry: registry)).serialize(message)Working with Well-Known Types
// Timestamps
let timestampMessage = try DynamicMessage.timestampMessage(from: Date())
let backToDate = try timestampMessage.toDate()
// JSON-like structures
let data: [String: Any] = ["user": "john", "active": true]
let structMessage = try DynamicMessage.structMessage(from: data)
// Type erasure
let anyMessage = try message.packIntoAny()
let unpackedMessage = try await anyMessage.unpackFromAny(to: personSchema)Features
- Dynamic Message Creation: Create and manipulate protobuf messages at runtime
- Schema Definition: Build message descriptors programmatically
- Proto3 Compliance: Syntax tracking, zero defaults, optional presence, enum validation, unknown fields preservation
- Canonical JSON: int64/uint64 as strings, bytes as base64, enums as names,
includeDefaultValues - Nested Messages: Recursive binary serialization/deserialization with type resolution
- Schema Evolution: Safe field addition/removal/renaming with unknown field round-trip
- Typed Options:
DescriptorOptionenum for type-safe,Sendabledescriptor options - Oneof Support: First-class
OneofDescriptorwith full bridge round-trip - Serialization: Binary and JSON serialization/deserialization
- Well-Known Types: 18 types — Timestamp, Duration, Empty, FieldMask, Struct, Value, Any, ListValue, NullValue, and 9 wrapper types
- Swift Protobuf Compatibility: Convert between static and dynamic messages, Visitor-based descriptor extraction
- Type Registry: Centralized type management and lookup
- Swift 6 Ready: All public types conform to
Sendable
Examples
The library includes 47 working examples demonstrating various use cases:
git clone https://github.com/truewebber/swift-protoreflect.git
cd swift-protoreflect/examples
# Basic examples
swift run HelloWorld
swift run FieldTypes
swift run TimestampDemo
# Proto3 compliance
swift run SyntaxAndDefaults
swift run OptionalPresence
swift run UnknownFields
swift run JsonCanonical
# Advanced examples
swift run ApiGateway
swift run MessageTransformExamples are organized by topic:
- Basic Usage (4): Getting started
- Dynamic Messages (7): Message manipulation
- Serialization (5): Binary and JSON formats
- Registry (4): Type management
- Well-Known Types (10): Google standard types, wrapper types, ListValue
- Advanced (6): Complex patterns
- Real-World (5): Production scenarios
- Proto3 Compliance (6): Syntax, optional presence, unknown fields, canonical JSON, schema evolution, nested messages
Requirements
- Swift 5.9+
- macOS 12.0+ / iOS 15.0+
- Recommended: SwiftProtoReflect 6.0.0+
Dependencies
- SwiftProtobuf 1.29.0+
Documentation
- Architecture Guide: Technical implementation details
- Migration Guide: Migrating from static Swift Protobuf
Use Cases
- Generic protobuf tools (viewers, debuggers, converters)
- API gateways with dynamic message routing
- Data processing pipelines with runtime schema handling
- Testing tools that generate data for arbitrary schemas
- Configuration systems using protobuf schemas
Integration with Swift Protobuf
SwiftProtoReflect works alongside existing Swift Protobuf code:
// Convert static to dynamic
let staticMessage = Person.with { /* ... */ }
let dynamicMessage = try staticMessage.toDynamicMessage()
// Convert dynamic to static
let backToStatic: Person = try dynamicMessage.toStaticMessage(as: Person.self)Testing
The library has comprehensive test coverage covering all functionality and edge cases.
Quality metrics
Code coverage is measured with LLVM (make coverage after make test) over Sources/SwiftProtoReflect/:
| Metric | Coverage | |--------|----------| | Lines | 95.60% | | Functions | 95.79% | | Regions | 93.95% |
Figures reflect the current test suite; re-run make test and make coverage locally for up-to-date numbers.
License
MIT License. See LICENSE for details.
Package Metadata
Repository: truewebber/swift-protoreflect
Default branch: master
README: README.md