swift-data-parsing

0.1.2

Binary data parsing abstractions and numeric conversions for Swift.
orchetect/swift-data-parsing

What's New

0.1.2

2026-04-06T03:01:29Z

Maintenance

  • Removed .swift-version file which was causing build failures when using the swiftly toolchain manager

swift-data-parsing

Xcode 16 License: MIT

Multi-platform binary data parsing abstractions for Swift, with integer and floating-point conversions to/from binary data with control over byte order (endianness).

Data Parsing

Simple and performant binary data parsing abstractions can be used to sequentially parse arbitrary binary data.

Read methods advance the offset position by default, but can be performed as read-ahead operations by supplying false to the advance parameter.

Read methods throwing if not enough byte are available:

let data = Data([0x01, 0x02, 0x03, 0x04, 0x05])

try data.withDataParser { parser in
    let a = try parser.readByte(advance: false) // 0x01 as UInt8
    let b = try parser.read(bytes: 2, advance: false) // [0x01, 0x02] as UInt8 buffer ptr
    let c = try parser.read(advance: false) // [0x01, 0x02, 0x03, 0x04, 0x05] as UInt8 buffer ptr
}

Methods to read bytes and advance the offset position, throwing if not enough byte are available:

let data = Data([0x01, 0x02, 0x03, 0x04, 0x05])

try data.withDataParser { parser in
    let a = try parser.readByte() // 0x01 as UInt8
    let b = try parser.read(bytes: 2) // [0x02, 0x03] as UInt8 buffer ptr
    let c = try parser.read(bytes: 2) // [0x04, 0x05] as UInt8 buffer ptr
    let d = try parser.readByte() // throws error; not enough bytes
}

try data.withDataParser { parser in
    let a = try parser.readByte() // 0x01 as UInt8
    let b = try parser.read() // [0x02, 0x03, 0x04, 0x05] as UInt8 buffer ptr
	let c = try parser.readByte() // throws error; not enough bytes
}

try data.withDataParser { parser in
    try parser.seek(by: 4) // advance offset without reading the bytes
    try parser.seek(by: -3) // recede offset without reading the bytes
    let a = try parser.readByte() // 0x02 as UInt8
}

try data.withDataParser { parser in
    try parser.seek(to: 3) // set the absolute read offset
    let a = try parser.readByte() // 0x04 as UInt8
    
    parser.seek(unsafeTo: 100) // set the absolute read offset without bounds checking
    let b = try parser.readByte() // throws error; past end of data
}

try data.withDataParser { parser in
    _ = parser.readOffset // 0
    _ = parser.remainingByteCount // 5
}

Byte ranges can be subscripted, compared, and more:

let data = Data([0x01, 0x02, 0x03, 0x04, 0x05])

try data.withDataParser { parser in
    let byte = try parser.readByte() // 0x01 as UInt8
    let bytes = try parser.read(bytes: 2) // [0x02, 0x03] as UInt8 buffer ptr
    
    // byte ranges can be subscripted using indexes rebased to 0
    let byte1 = bytes[0] // 0x02 as UInt8
    let byte2 = bytes[1] // 0x03 as UInt8
    
    // byte ranges can be tested for equality against other data
    // (Data, UInt8 array, UInt8 ptr, etc.)
    if bytes == [0x02, 0x03] { }
    if bytes == Data([0x02, 0x03]) { }
}

The parser can return a value:

let data = Data([0x04, 0x44, 0x41, 0x54, 0x41])

let value: String? = try data.withDataParser { parser in
    let length = try parser.readByte() // 0x04 as UInt8
    let chars = try parser.read(bytes: Int(length)) // 4 bytes as UInt8 buffer ptr
    let string = String(bytes: chars, encoding: .utf8)
    return string
}

print(value) // Optional("DATA")

Numeric Conversion

Numeric types (integers and floating-point) can be easily converted from binary bytes.

// Data -> Int
Data([0x01]).toInt8() // 1 as Int8?
Data([0x01, 0x00]).toInt16() // 1 as Int16?
Data([0x01, 0x00, 0x00, 0x00]).toInt32() // 1 as Int32?
Data([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toInt64() // 1 as Int64?
Data([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toInt() // 1 as Int?

// Data -> UInt
Data([0x01]).toUInt8() // 1 as UInt8?
Data([0x01, 0x00]).toUInt16() // 1 as UInt16?
Data([0x01, 0x00, 0x00, 0x00]).toUInt32() // 1 as UInt32?
Data([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toUInt64() // 1 as UInt64?
Data([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]).toUInt() // 1 as UInt?

// Data -> Floating-Point
Data([0x00, 0x00, 0x80, 0x3F]).toFloat32() // 1.0 as Float32?
Data([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F]).toDouble() // 1.0 as Double?

Conversely, the same numeric types can be converted to binary bytes.

Int16(1).toData() // Data([0x01, 0x00])?
// etc.

Byte Ordering

By default, platform-default byte order is assumed, but source byte order can be specified when converting to/from binary data.

Data([0x01, 0x00]).toUInt16() // 1 as UInt16? // (Apple platforms use litte-endian)
Data([0x01, 0x00]).toUInt16(from: .littleEndian) // 1 as UInt16?
Data([0x00, 0x01]).toUInt16(from: .bigEndian) // 1 as UInt16?

UInt16(1).toData() // Data([0x01, 0x00])? // (Apple platforms use litte-endian)
UInt16(1).toData(.littleEndian) // Data([0x01, 0x00])?
UInt16(1).toData(.bigEndian) // Data([0x00, 0x01])?

Signed Integer Encoding

By default, default encoding of signed integers uses signed bit encoding when stored as bytes.

Alternatively, one's complement and two's complement encodings are available when converting to/from data.

// from Data
Data([...]).toInt8(as: .signedBit) // Default, same as .toInt8()
Data([...]).toInt8(as: .onesComplement)
Data([...]).toInt8(as: .twosComplement)

// to Data
Int8(...).toData(as: ...)

// to/from unsigned integer with same bit width
Int8(...).toUInt8(as: ...)
UInt8(...).toInt8(as: ...)

Data Conversion

Various types of binary data can be converted to one another using extension methods.

// Data -> [UInt8]
let data = Data([0x01, 0x02])
let bytes = data.toUInt8Bytes() // as [UInt8]

// [UInt8] -> Data
let bytes: [UInt8] = [0x01, 0x02]
let data = bytes.toData() // as Data

Accessing Bits

Return the bit at the specified bit position from least significant bit of any unsigned integer:

UInt8(0b1000_0000).bit(0) // 0
UInt8(0b1000_0000).bit(7) // 1

Installation: Swift Package Manager (SPM)

Dependency within an Application Project

  1. Add the package to your Xcode project using Swift Package Manager using https://github.com/orchetect/swift-data-parsing as the URL.

  2. Import the module where needed.

    import SwiftDataParsing

Dependency within a Swift Package

In your Package.swift file:

let package = Package(
    dependencies: [
        .package(url: "https://github.com/orchetect/swift-data-parsing", from: "0.1.2")
    ],
    targets: [
        .target(
            dependencies: [
                .product(name: "SwiftDataParsing", package: "swift-data-parsing")
            ]
        )
    ]
)

Extending the Library

In addition to the data parsers provided by the library, it is possible to define custom parsing implementation by adopting the DataParserProtocol protocol.

This is a basic template to get started:

Show Code
public struct CustomDataParser<DataType: DataProtocol & Sendable>: DataParserProtocol {
    public typealias DataElement = DataType.Element
    public typealias DataRange = DataType.SubSequence
    
    let data: DataType
    
    public internal(set) var readOffset: Int = 0
    
    init(data: DataType) {
        self.data = data
        count = data.count
    }
    
    @inline(__always)
    public let count: Int
    
    @inline(__always)
    public mutating func seek(unsafeTo offset: Int) {
        readOffset = offset
    }
    
    public mutating func readByte(advance: Bool) throws(DataParserError) -> DataElement {
        guard remainingByteCount > 0 else { throw .pastEndOfStream }
        let byte = data[data.index(data.startIndex, offsetBy: readOffset)]
        if advance { readOffset += 1 }
        return byte
    }
    
    public mutating func read(bytes count: Int?, advance: Bool) throws(DataParserError) -> DataRange {
        let count = count ?? remainingByteCount
        guard count > -1 else { throw .invalidByteCount }
        guard count <= remainingByteCount else { throw .pastEndOfStream }
        let startIndex = data.index(data.startIndex, offsetBy: readOffset)
        let endIndex = data.index(startIndex, offsetBy: count)
        if advance { readOffset += count }
        let data = data[startIndex ..< endIndex]
        return data
    }
}

extension DataProtocol {
    @discardableResult
    public func withCustomDataParser<T, E>(
        _ block: (_ parser: inout CustomDataParser<Self>) throws(E) -> T
    ) throws(E) -> T {
        var parser = CustomDataParser(data: self)
        return try block(&parser)
    }
}

Documentation

No separate documentation is provided at this time. This README serves as a getting started guide, covering most of the core features of the library.

Author

Coded by a bunch of 🐹 hamsters in a trenchcoat that calls itself @orchetect.

License

Licensed under the MIT license. See LICENSE for details.

Sponsoring

If you enjoy using swift-data-parsing and want to contribute to open-source financially, GitHub sponsorship is much appreciated. Feedback and code contributions are also welcome.

Community & Support

Please do not email maintainers for technical support. Several options are available for issues and questions:

  • Questions and feature ideas can be posted to Discussions.
  • If an issue is a verifiable bug with reproducible steps it may be posted in Issues.

Contributions

Contributions are welcome. Posting in Discussions first prior to new submitting PRs for features or modifications is encouraged.

Legacy

This repository was extracted from swift-extensions 2.0.0 into its own repository in March of 2026.

Resources

  • If your needs exceed the features provided by list library, Apple's swift-binary-parsing may provide more comprehensive binary parsing capabilities

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

  • None
Last updated: Wed Apr 08 2026 18:56:56 GMT-0900 (Hawaii-Aleutian Daylight Time)