Skip to content
TStrawberryPublic

About

Default value for failed decoding with Swift's Codable

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

5 Commits

Folders and files

Repository files navigation

Default

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:).

Requirements

  • Swift 6.0+

Installation

Add the package to your Package.swift:

dependencies: [
    .package(url: "https://github.com/your-org/Default.git", from: "1.0.0"),
]

Quick start

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)

How it works

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.

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 == false

If you define your own non-init(from:) initializer, the macro skips generating one.

Decoding behavior

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).

Supported default expressions

@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"
    }
}

Custom CodingKeys

@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.

Custom init(from:) for missing keys

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.

About

Default value for failed decoding with Swift's Codable

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages