A Swift macro library for automatic JSON encoding/decoding with safe defaults and immutable-first design.
Bourne uses Swift macros to automatically generate Codable implementations with sensible defaults. It's designed for immutable data models using struct and let, which helps avoid data race issues in multi-threaded environments.
- Automatic Codable Implementation: Generate
init(from:)andencode(to:)methods automatically - Safe Defaults: Missing JSON keys use default values instead of throwing errors
- Immutable-First Design: Designed for
struct+letpatterns with acopy()method for modifications - Empty Instance: Auto-generates a static
.emptyproperty for each model - Enum Support:
@BourneEnummacro for enums with raw values - Custom Property Configuration:
@JSONPropertyfor custom default values and key names - Thread-Safe:
Sendableconformance automatically added
- Swift 6.2+
- macOS 10.15+ / iOS 15+ / tvOS 15+ / watchOS 6+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/dalonng/Bourne.git", from: "0.4.0")
]Then add Bourne to your target dependencies:
.target(
name: "YourTarget",
dependencies: ["Bourne"]
)import Bourne
@Bourne
public struct Person {
public let name: String
public let age: Int
public var isChild: Bool
}This generates:
CodingKeysenuminit(from decoder: Decoder)with safe defaultsencode(to encoder: Encoder)static let empty- an empty instance with default valuesfunc copy(...)- create a modified copy
@BourneEnum
public enum Gender: String, Sendable {
case male
case female
}@Bourne
public struct User {
public let name: String
@JSONProperty(defaultValue: 18)
public let age: Int
@JSONProperty(name: "user_email")
public let email: String
@JSONProperty(defaultValue: Gender.male, name: "user_gender")
public let gender: Gender
}Parameters:
defaultValue: Custom default value when JSON key is missingname: Custom JSON key name (for snake_case to camelCase mapping)
@Bourne
public struct Address {
public let city: String
public let street: String
}
@Bourne
public struct PersonWithAddress {
public let name: String
public let address: Address // Uses Address.empty if missing
}Since models are immutable by default, use copy() to create modified instances:
let person = Person(name: "Alice", age: 25, isChild: false)
let updatedPerson = person.copy(age: 26) // Only changes ageWhen a JSON key is missing, Bourne uses these defaults:
| Type | Default Value |
|---|---|
String |
"" |
Int |
0 |
Bool |
false |
Float / Double / CGFloat |
0 |
UUID |
UUID() (new random UUID) |
Array |
[] |
Custom @Bourne types |
.empty |
Custom @BourneEnum types |
First case (or custom via @JSONProperty) |
For this struct:
@Bourne
public struct Person {
public let name: String
public let age: Int
public var isChild: Bool
}Bourne generates:
extension Person: Decodable, Encodable, Sendable {
enum CodingKeys: String, CodingKey {
case name
case age
case isChild
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
self.age = try container.decodeIfPresent(Int.self, forKey: .age) ?? 0
self.isChild = try container.decodeIfPresent(Bool.self, forKey: .isChild) ?? false
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(age, forKey: .age)
try container.encode(isChild, forKey: .isChild)
}
public static let empty = Person(
name: "",
age: 0,
isChild: false
)
public func copy(
name: String? = nil,
age: Int? = nil,
isChild: Bool? = nil
) -> Person {
Person(
name: name ?? self.name,
age: age ?? self.age,
isChild: isChild ?? self.isChild
)
}
}MIT License