Default is a Swift package for defining fallback values on Decodable properties.
You write @Default on each property and @DefaultDecodable on the struct. The macros generate a private DecodeDefaultValue type per property, attach @DefaultProperty for lenient decoding, and synthesize a memberwise init with default values for @Default properties. @DefaultDecodable does not generate CodingKeys or init(from:).
- Swift 6.0+
Add the package to your Package.swift:
dependencies: [
.package(url: "https://github.com/your-org/Default.git", from: "1.0.0"),
]import Default
@DefaultDecodable
struct User: Codable, Equatable {
@Default("default name")
var name: String
@Default(10)
var age: Int
}
let user = User(name: "Alice", age: 25)
let decoded = try JSONDecoder().decode(User.self, from: jsonData)Three pieces work together:
| Piece | Role |
|---|---|
@Default |
Marker only. Generates a private DecodeDefaultValue type for the property. |
@DefaultProperty |
Property wrapper that decodes leniently when a JSON key is present. |
@DefaultDecodable |
Attaches @DefaultProperty to each @Default property and generates a memberwise init with default values. Does not generate CodingKeys or init(from:). |
Given this source:
@DefaultDecodable
struct User: Codable, Equatable {
@Default("default name")
var name: String
@Default(10)
var age: Int
}the macros expand it to something like:
@DefaultDecodable
struct User: Codable, Equatable {
enum __DefaultValue_name: DecodeDefaultValue {
typealias T = String
static var defaultValue: String { "default name" }
}
enum __DefaultValue_age: DecodeDefaultValue {
typealias T = Int
static var defaultValue: Int { 10 }
}
@Default("default name")
@DefaultProperty<__DefaultValue_name>
var name: String
@Default(10)
@DefaultProperty<__DefaultValue_age>
var age: Int
init(
name: String = "default name",
age: Int = 10
) {
self.name = name
self.age = age
}
}DecodeDefaultValue holds the fallback value. DefaultProperty uses that type when decoding from a single-value container. The generated init applies the same defaults for ordinary initialization.
@Default properties get default arguments in the synthesized initializer. Properties without @Default remain required:
@DefaultDecodable
struct User: Codable, Equatable {
@Default("default name")
var name: String
@Default(10)
var age: Int
var isAdult: Bool
}
let user = User(isAdult: false)
// user.name == "default name"
// user.age == 10
// user.isAdult == falseIf you define your own non-init(from:) initializer, the macro skips generating one.
When a JSON key is present, DefaultProperty handles decoding:
| Input | Result |
|---|---|
| Valid value | Decodes normally |
null |
Falls back to default |
| Wrong type | Falls back to default |
When a JSON key is missing, Swift's synthesized Decodable implementation does not apply property-wrapper defaults automatically. Provide your own init(from:) if you need fallback behavior for missing keys (see below).
@Default accepts Int, Bool, String, Double, and Float literals, as well as expressions that resolve to those types:
private enum Defaults {
static let name = "guest"
}
@DefaultDecodable
struct Settings: Codable {
@Default(0) var count: Int
@Default(true) var isEnabled: Bool
@Default("guest") var nickname: String
@Default(Defaults.name) var name: String
}Each property needs an explicit type annotation (for example var name: String, not var name).
When a property is public, its generated __DefaultValue_* type is also public:
@Default("default name")
public internal(set) var name: String
// expands to:
public enum __DefaultValue_name: DecodeDefaultValue {
public typealias T = String
public static var defaultValue: String {
"default name"
}
}@DefaultDecodable does not generate CodingKeys. You can define your own:
@DefaultDecodable
struct Config: Decodable {
@Default("production")
var environment: String
enum CodingKeys: String, CodingKey {
case environment = "env"
}
}Swift synthesizes init(from:) from your CodingKeys. @DefaultProperty still handles null and type mismatches when the key is present.
When you need defaults for missing keys, write your own initializer and use the generated __DefaultValue_* type:
@DefaultDecodable
struct Settings: Decodable {
@Default("guest")
var nickname: String
enum CodingKeys: String, CodingKey {
case nickname
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_nickname = try container.decodeIfPresent(
DefaultProperty<__DefaultValue_nickname>.self,
forKey: .nickname
) ?? DefaultProperty(wrappedValue: __DefaultValue_nickname.defaultValue)
}
}Because @DefaultDecodable never generates init(from:), this does not conflict with the macros.