CBOR is a lightweight implementation of the CBOR (Concise Binary Object Representation) format in Swift. It allows you to encode and decode data to and from the CBOR format, work directly with the CBOR data model, and integrate with Swift's Codable protocol.
Direct CBOR Data Model:
Represent CBOR values using an enum with cases for unsigned/negative integers, byte strings, text strings, arrays, maps (ordered key/value pairs), tagged values, simple values, booleans, null, undefined, and floats.Memory-Optimized for Embedded Swift:
UsesArraySlice<UInt8>internally to avoid heap allocations by referencing original data instead of copying. Includes zero-copy access methods and memory-efficient iterators for arrays and maps.Encoding & Decoding:
Easily convert between CBOR values and byte arrays.Full Codable Support:
UseCBOREncoderandCBORDecoderfor complete support of Swift'sCodableprotocol, including:- Single value encoding/decoding
- Keyed containers (for dictionaries and objects)
- Unkeyed containers (for arrays)
- Nested containers
- Custom encoding/decoding
- Sets and other collection types
- Optionals and deeply nested optionals
- Non-String dictionary keys
- Cross-platform date handling (ISO8601 format on Apple platforms)
Error Handling:
Detailed error types (CBORError) to help you diagnose encoding/decoding issues.
Comprehensive documentation is available via DocC:
- Online Documentation
- Generate locally with:
swift package --allow-writing-to-directory ./docs generate-documentation --target CBOR
Add the CBOR package to your Swift package dependencies:
// swift-tools-version:6.0
import PackageDescription
letpackage=Package(
name:"YourProject",
dependencies:[.package(url:"https://github.com/edgeengineer/cbor", from:"0.0.4")],
targets:[.target(
name:"YourTarget",
dependencies:[.product(name:"CBOR",package:"cbor")])])import CBOR
// Create a CBOR value (an unsigned integer)
letcborValue:CBOR=.unsignedInt(42)
// Encode the CBOR value to a byte array
letencodedBytes= cborValue.encode()print("Encoded bytes:", encodedBytes)
// Decode the bytes back into a CBOR value
do{letdecodedValue=tryCBOR.decode(encodedBytes)print("Decoded CBOR value:", decodedValue)}catch{print("Decoding error:", error)}import CBOR
// Define your data structures
structPerson:Codable{letname:Stringletage:Intletaddresses:[Address]letmetadata:[String:String]}structAddress:Codable{letstreet:Stringletcity:String}
// Create an instance
letperson=Person(
name:"Alice",
age:30,
addresses:[Address(street:"123 Main St", city:"Wonderland"),Address(street:"456 Side Ave", city:"Fantasialand")],
metadata:["occupation":"Engineer","department":"R&D"])
// Encode to CBOR
do{letencoder=CBOREncoder()letcborData=try encoder.encode(person)print("Encoded CBOR Data:", cborData asNSData)
// Decode back from CBOR
letdecoder=CBORDecoder()letdecodedPerson=try decoder.decode(Person.self, from: cborData)print("Decoded Person:", decodedPerson)}catch{print("Error:", error)}// Create an array of CBOR values
letarrayCBOR:CBOR=.array([.unsignedInt(1),.textString("hello"),.bool(true)])
// Create a map (ordered key/value pairs)
letmapCBOR:CBOR=.map([CBORMapPair(key:.textString("name"), value:.textString("SwiftCBOR")),CBORMapPair(key:.textString("version"), value:.unsignedInt(1))])
// Combine them into a nested structure
letnestedCBOR:CBOR=.map([CBORMapPair(key:.textString("data"), value: arrayCBOR),CBORMapPair(key:.textString("info"), value: mapCBOR)])do{letcbor=tryCBOR.decode([0xff,0x00]) // Example invalid CBOR data
}catchlet error as CBORError{switch error {case.invalidCBOR:print("Invalid CBOR data")case.typeMismatch(let expected,let actual):print("Type mismatch: expected \(expected), found \(actual)")case.prematureEnd:print("Unexpected end of data")default:print("Other CBOR error:", error.description)}}catch{print("Unexpected error:", error)}// Example of nested containers and arrays
structTeam:Codable{letname:Stringletmembers:[Member]letstats:Statisticslettags:Set<String>}structMember:Codable{letid:Intletname:Stringletroles:[Role]enumRole:String,Codable{case developer
case designer
case manager
}}structStatistics:Codable{letprojectsCompleted:IntletaverageRating:DoubleletactiveYears:[Int]}
// Create and encode a team
letteam=Team(
name:"Dream Team",
members:[Member(id:1, name:"Alice", roles:[.developer,.manager]),Member(id:2, name:"Bob", roles:[.designer])],
stats:Statistics(
projectsCompleted:12,
averageRating:4.8,
activeYears:[2020,2021,2022]),
tags:["innovative","agile","productive"])letencoder=CBOREncoder()letcborData=try encoder.encode(team)import CBOR
// Define a struct with Set properties
structSetContainer:Codable,Equatable{letstringSet:Set<String>letintSet:Set<Int>}
// Create an instance with sets
letsetExample=SetContainer(
stringSet:Set(["apple","banana","cherry"]),
intSet:Set([1,2,3,4,5]))
// Encode to CBOR
letencoder=CBOREncoder()letencoded=try encoder.encode(setExample)
// Decode from CBOR
letdecoder=CBORDecoder()letdecoded=try decoder.decode(SetContainer.self, from: encoded)
// Verify sets are preserved
assert(decoded.stringSet.contains("apple"))assert(decoded.intSet.contains(3))import CBOR
// Define a struct with optional and nested optional properties
structOptionalExample:Codable,Equatable{letsimpleOptional:String?letnestedOptional:Int??letoptionalArray:[Double?]?letoptionalDict:[String:Bool?]?}
// Create an instance with various optional values
letoptionalExample=OptionalExample(
simpleOptional:"present",
nestedOptional:nil,
optionalArray:[1.0,nil,3.0],
optionalDict:["yes":true,"no":false,"maybe":nil])
// Encode to CBOR
letencoder=CBOREncoder()letencoded=try encoder.encode(optionalExample)
// Decode from CBOR
letdecoder=CBORDecoder()letdecoded=try decoder.decode(OptionalExample.self, from: encoded)
// Verify optionals are preserved
assert(decoded.simpleOptional =="present")assert(decoded.nestedOptional ==nil)assert(decoded.optionalArray?[1]==nil)assert(decoded.optionalDict?["maybe"]==nil)import CBOR
// Define an enum to use as dictionary keys
enumColor:String,Codable,Hashable{case red
case green
case blue
}structEnumKeyDict:Codable,Equatable{letcolorValues:[Color:Int]}
// Create an instance with enum keys
letcolorDict=EnumKeyDict(colorValues:[.red:1,.green:2,.blue:3])
// Encode to CBOR
letencoder=CBOREncoder()letencoded=try encoder.encode(colorDict)
// Decode from CBOR
letdecoder=CBORDecoder()letdecoded=try decoder.decode(EnumKeyDict.self, from: encoded)
// Verify dictionary with enum keys is preserved
assert(decoded.colorValues[.red]==1)assert(decoded.colorValues[.green]==2)assert(decoded.colorValues[.blue]==3)This CBOR library is optimized for memory-constrained environments like Embedded Swift. It uses ArraySlice<UInt8> internally to avoid unnecessary heap allocations by referencing original data instead of copying it.
import CBOR
// Create a byte string from raw data
letrawData:[UInt8]=[0x01,0x02,0x03,0x04,0x05]letcbor=CBOR.byteString(ArraySlice(rawData))
// Zero-copy access (recommended for Embedded Swift)
iflet slice = cbor.byteStringSlice(){print("Length: \(slice.count)")print("First byte: 0x\(String(slice.first!, radix:16))")
// Process bytes without copying
forbytein slice {print("Byte: 0x\(String(byte, radix:16))")}}
// Copy to Array only when needed (allocates memory)
iflet bytes = cbor.byteStringValue(){lethexString= bytes.map{String(format:"%02x", $0)}.joined()print("Hex: \(hexString)")}import CBOR
// Create a text string with Unicode content
lettext="Hello, 世界! 🌍"letcbor=CBOR.textString(ArraySlice(text.utf8))
// Zero-copy access to UTF-8 bytes
iflet slice = cbor.textStringSlice(){print("UTF-8 byte count: \(slice.count)")
// Convert to String without intermediate allocation
iflet string =String(bytes: slice, encoding:.utf8){print("Text: \(string)")}
// Or examine raw UTF-8 bytes
forbytein slice {print("UTF-8 byte: 0x\(String(byte, radix:16))")}}
// Convenience method for direct String conversion
iflet text = cbor.stringValue {print("Decoded text: \(text)")}import CBOR
// Decode CBOR data containing an array
letencodedArray:[UInt8]=[0x83,0x01,0x62,0x68,0x69,0xf5] // [1, "hi", true]
letcbor=tryCBOR.decode(encodedArray)
// Use iterator to avoid loading entire array into memory
iflet iterator =try cbor.arrayIterator(){variterator= iterator // Make mutable
varindex=0whilelet element = iterator.next(){print("Element \(index):")switch element {case.unsignedInt(let value):print(" Integer: \(value)")case.textString:
// Use zero-copy access for strings
iflet text = element.stringValue {print(" Text: \(text)")}case.bool(let flag):print(" Boolean: \(flag)")default:print(" Other: \(element)")}
index +=1}}
// Compare with traditional approach (allocates full array)
iflet elements =try cbor.arrayValue(){print("Traditional approach loaded \(elements.count) elements into memory")}import CBOR
// Decode CBOR data containing a map
letencodedMap:[UInt8]=[0xa2,0x64,0x6e,0x61,0x6d,0x65,0x64,0x4a,0x6f,0x68,0x6e,0x63,0x61,0x67,0x65,0x18,0x1e]
// {"name": "John", "age": 30}
letcbor=tryCBOR.decode(encodedMap)
// Use iterator to process key-value pairs without loading entire map
iflet iterator =try cbor.mapIterator(){variterator= iterator // Make mutable
whilelet pair = iterator.next(){print("Processing key-value pair:")
// Handle the key (zero-copy for strings)
iflet keyText = pair.key.stringValue {print(" Key: \(keyText)")}
// Handle the value
switch pair.value {case.unsignedInt(let value):print(" Value: \(value)")case.textString:iflet valueText = pair.value.stringValue {print(" Value: \(valueText)")}default:print(" Value: \(pair.value)")}}}
// Compare with traditional approach (allocates full map)
iflet pairs =try cbor.mapValue(){print("Traditional approach loaded \(pairs.count) pairs into memory")}import CBOR
// Create a large byte string
letlargeData=[UInt8](repeating:0xFF, count:10000)letcbor=CBOR.byteString(ArraySlice(largeData))
// ✅ Memory-efficient: Zero-copy access
iflet slice = cbor.byteStringSlice(){
// No memory allocation - just references original data
letsum= slice.reduce(0,+)print("Sum using slice: \(sum)")}
// ⚠️ Memory-intensive: Copies data
iflet bytes = cbor.byteStringValue(){
// Allocates 10KB of memory for the copy
letsum= bytes.reduce(0,+)print("Sum using copy: \(sum)")}import CBOR
// When you decode CBOR from external data
letnetworkData:[UInt8]=[0x65,0x48,0x65,0x6c,0x6c,0x6f] // "Hello"
letcbor=tryCBOR.decode(networkData)
// The decoded CBOR references the original networkData
iflet slice = cbor.textStringSlice(){
// slice points into networkData - no copying!
print("Text length: \(slice.count)")
// As long as networkData stays alive, slice is valid
iflet text =String(bytes: slice, encoding:.utf8){print("Decoded: \(text)")}}- Prefer slice methods (
byteStringSlice(),textStringSlice()) over value methods for better memory efficiency - Use iterators (
arrayIterator(),mapIterator()) for large collections to avoid loading everything into memory - Keep original data alive when using slices, as they reference the original data
- Use
stringValueconvenience property for direct String conversion without intermediate allocations
This CBOR library is designed to work across all Swift-supported platforms:
- Apple platforms (macOS, iOS, tvOS, watchOS, visionOS): Full feature support including ISO8601 date formatting
- Linux: Full feature support except ISO8601 date formatting (dates are still supported through other formats)
- Windows: Full feature support except ISO8601 date formatting (dates are still supported through other formats)
- Android: Cross-platform compatibility maintained
The library provides automatic date encoding/decoding support through the Codable interface:
- On Apple platforms: Dates are automatically formatted using
ISO8601DateFormatterwhen encoded as text strings - On Linux/Windows: Date text string formatting is not available, but dates can still be encoded/decoded using other CBOR representations (tagged values, numeric timestamps, etc.)
This ensures your code remains fully functional across all platforms while taking advantage of platform-specific optimizations where available.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.