A declarative Swift package for parsing binary data using macros, built on top of Apple's swift-binary-parsing framework.
Important
Warning: This package is currently under active development and its APIs are subjected to drastic changes.
- Declarative syntax: Define binary structures using simple annotations
- Type-safe parsing: Leverages Swift's type system for safe binary data access
- Flexible endianness: Support for both big-endian and little-endian byte ordering
- Variable-length fields: Parse fields whose size depends on previously parsed values
- Skip functionality: Skip unwanted bytes with documentation
- Custom byte counts: Parse types with specific byte lengths
- Remaining data parsing: Parse all remaining bytes in a buffer
- Enum parsing: Parse enums with pattern matching and associated values
- Swift 6.2+
- Xcode 26.0+
- macOS 15.0+ / iOS 18.0+ / tvOS 18.0+ / watchOS 11.0+ / visionOS 2.0+
Add BinaryParseKit to your project using Swift Package Manager. In Xcode, go to File → Add Package Dependencies and enter:
https://github.com/FlickerSoul/BinaryParseKit
Or add it to your Package.swift:
dependencies: [
.package(url: "https://github.com/FlickerSoul/BinaryParseKit", branch: "main")
]Consider a binary packet with the following structure:
+----------------------+----------------------+-------------------------------+
| packetIndex (1 byte) | packetCount (1 byte) | SignalPacket (n bytes) |
+----------------------+----------------------+-------------------------------+
+------------------------------------------------------------------+
| Signal Packet |
+---------------------+------------------+-------------------------+
| level (1 bytes) | id (6 bytes) | messageSize (1 byte) |
+---------------------+------------------+-------------------------+
| message (variable, length = messageSize bytes) |
+------------------------------------------------------------------+
With BinaryParseKit, you can define and parse this structure declaratively:
import BinaryParseKit
import BinaryParsing
@ParseStruct
struct BluetoothPacket {
@parse
let packetIndex: UInt8
@parse
let packetCount: UInt8
@parse
let payload: SignalPacket
}
@ParseStruct
struct SignalPacket {
@parse(byteCount: 1, endianness: .big)
let level: UInt32
@parse(byteCount: 6, endianness: .little)
let id: UInt64
@skip(byteCount: 1, because: "padding byte")
@parse(endianness: .big)
let messageSize: UInt8
@parse(byteCountOf: \Self.messageSize)
let message: String
}
// Extend String to support sized parsing
extension String: SizedParsable {
public init(parsing input: inout BinaryParsing.ParserSpan, byteCount: Int) throws {
try self.init(parsingUTF8: &input, count: byteCount)
}
}Parse binary data in one line:
let data: [UInt8] = [
0x01, // packet index
0x01, // packet count
0xAA, // level
0xAB, 0xAD, 0xC0, 0xFF, 0xEE, 0x00, // id (little endian)
0x00, // padding byte (skipped)
0x0C, // message size
0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21 // "hello world!"
]
let packet = try BluetoothPacket(parsing: data)
print(packet.payload.message) // "hello world!"Mark your struct with @ParseStruct and annotate fields with parsing macros:
@ParseStruct
struct Header {
@parse(endianness: .big)
let magic: UInt32 // Uses default parsing
@parse(endianness: .big)
let version: UInt16 // Big-endian parsing
@parse(byteCount: 3, endianness: .little)
let customField: UInt32 // Parse only 3 bytes
}Available struct parsing macros:
@ParseStruct- Mark a struct for binary parsing@parse- Use the type's default parsing behavior@parse(endianness: .big/.little)- Parse with specific endianness@parse(byteCount: Int)- Parse a specific number of bytes@parse(byteCountOf: KeyPath)- Parse bytes based on another field's value@parseRest()- Parse all remaining bytes@skip(byteCount: Int, because: String)- Skip bytes with documentation
Parse fields whose length depends on previously parsed values:
@ParseStruct
struct VariableMessage {
@parse
let length: UInt8
@parse(byteCountOf: \Self.length)
let data: Data
}Combine multiple techniques for complex binary formats:
@ParseStruct
struct ComplexPacket {
@parse(byteCount: 4, endianness: .big)
let header: UInt32
@skip(byteCount: 2, because: "reserved field")
@parse(endianness: .little)
let payloadSize: UInt16
@parse(byteCountOf: \Self.payloadSize, endianness: .big)
let payload: Data
@parseRest()
let footer: Data
}Parse enums with pattern matching and associated values using the @ParseEnum macro:
Basic Enum Matching
@ParseEnum
enum MessageType {
@match(byte: 0x01)
case connect
@match(bytes: [0x02, 0x03])
case data
@match(byte: 0xFF)
case disconnect
}
let msgType = try MessageType(parsing: Data([0x01]))
// msgType == .connectEnums with Associated Values
Use @matchAndTake to consume the match pattern and @parse to parse associated values:
@ParseEnum
enum Command: Equatable {
@matchAndTake(byte: 0x01)
@parse(endianness: .big)
case setValue(UInt16)
@matchAndTake(byte: 0x02)
@parse(endianness: .big)
@parse(endianness: .big)
case setRange(start: UInt16, end: UInt16)
@matchAndTake(byte: 0xFF)
case reset
}
let cmd = try Command(parsing: Data([0x01, 0x12, 0x34]))
// cmd == .setValue(0x1234)Raw Representable Enums
For enums with raw values, conform to MatchableRawRepresentable (or Matchable):
@ParseEnum
enum StatusCode: UInt8, MatchableRawRepresentable {
@match
case success = 0x00
@match
case error
@match
case pending
}Default Cases
Use @matchDefault to handle unrecognized values:
@ParseEnum
enum PacketType {
@match(byte: 0x01)
case known
@matchDefault
case unknown
}Available enum parsing macros:
@ParseEnum- Mark an enum for binary parsing@match(byte: UInt8)- Match a single byte@match(bytes: [UInt8])- Match a sequence of bytes, but doesn't shrink the remaining buffer@matchAndTake(byte:)and@matchAndTake(bytes:)- Match and consume bytes in the remaining buffer@matchDefault- Default case for unrecognized patterns, which doesn't consume any bytes in the remaining buffer@parse- Parse associated values (same options as struct fields)@skip- Skip bytes (same options as struct fields)
BinaryParseKit defines four parsing protocols:
Parsable- Basic parsing without additional parametersEndianParsable- Parsing with endianness specificationSizedParsable- Parsing with byte count specificationEndianSizedParsable- Parsing with both endianness and byte count
Most built-in types already conform to these protocols. For custom types, implement the appropriate protocol(s).
In addition, as mentioned in the previous enum parsing section,
Matchable and MatchableRawRepresentable is introduced to allow each case to provide bytes for matching in the process.
BinaryParseKit is currently under active development and its API may face changes. Contributions, suggestions, and feedback are welcome!
If you encounter any issues, have feature requests, or want to suggest improvements, please:
- Check existing issues - Search through existing issues to see if your concern has already been reported
- Create a new issue - If you don't find a related issue, feel free to create a new one
- Provide details - Include as much relevant information as possible:
- Swift version and platform
- Code examples demonstrating the issue
- Expected vs actual behavior
- Use case or scenario description
Some areas we're considering for future development:
- More convenient APIs - Shorter syntax and better autocomplete support, such as merging
ParseStructandParseEnum - Advanced validation - Runtime validation of parsing constraints, such as require minimal byte size checking in front instead of at each parsing
- Performance optimizations - Further optimization of generated parsing code, such as linear time enum matching for constant bytes provided (
O( max(n, m) )instead ofO(n * m), wherenis the number of cases andmis the max number of bytes provided for each case) - Porting to prio iOS/macOS 26 - because
Spanis introduced only in iOS/macOS 26, port of usingwithUnsafePointercan be provided to prior versions of OSes for better compatibility
Your feedback on these directions and other ideas is highly appreciated!
To contribute to BinaryParseKit:
- Fork the repository
- Create a feature branch
- Make your changes with appropriate tests
- Submit a pull request
See the LICENSE file for more info.
- Built on top of Apple's
swift-binary-parsingframework