Releases: tomisacat/CodableDefault
CodableDefault 2.0.0
CodableDefault 2.0.0
Apple platform expansion, cleaner macro output, and an interactive iOS demo
Version 2.0.0 extends CodableDefault beyond iOS and macOS, hardens the test and CI pipeline for cross-platform builds, and improves generated code for @Default transforms. The public macro API is unchanged from 1.1.0 — existing call sites compile without modification.
This is a major release because of the swift-syntax 603.x dependency and updated macro expansion output for non-throwing transforms.
What's new
watchOS, tvOS, and visionOS
CodableDefault now declares first-class support for every major Apple platform:
| Platform | Minimum |
|---|---|
| iOS | 13+ |
| macOS | 10.15+ |
| watchOS | 6+ |
| tvOS | 13+ |
| visionOS | 1+ |
Link CodableDefault into watchOS, tvOS, and visionOS app targets the same way as iOS. Macro tooling still builds and runs on the macOS host during compilation.
iOS demo app
Examples/CodableDefaultDemo is a SwiftUI app with 14 interactive scenarios — defaults, null, required fields, custom keys, user CodingKeys, classes, transforms, validation errors, and encode round-trips.
cd Examples/CodableDefaultDemo
open CodableDefaultDemo.xcodeprojEach scenario shows sample JSON, a short explanation, and live decode output (success or expected error).
Improvements
Cleaner transform expansion
The macro now inspects transform closures at expansion time:
- Non-throwing closures — generated code omits unnecessary
try, eliminating "no calls to throwing functions occur within 'try' expression" warnings - Throwing closures — still wrapped in
try { … }()so errors propagate frominit(from:)
Runtime behavior is unchanged. Only the generated source differs for non-throwing transforms:
// Before (1.1.0) — warns when transform does not throw
self.retryCount = try {
let __codableDefault_retryCount = …
return try { min($0, 100) }(__codableDefault_retryCount)
}()
// After (2.0.0) — no warning
self.retryCount = {
let __codableDefault_retryCount = …
return { min($0, 100) }(__codableDefault_retryCount)
}()If you snapshot macro expansions in tests or tooling, re-record golden output after upgrading.
Reliable swift test
Test targets now declare explicit swift-syntax dependencies, fixing linker failures (Undefined symbols for SwiftSyntax…) when running swift test from the command line. This is a Swift Package Manager workaround for macro packages — no public API change.
CI and tooling
GitHub Actions now verifies the package across Apple platforms:
| Job | Coverage |
|---|---|
| test | swift build + swift test on macOS (Xcode 26.0 and latest stable) |
| platform | Cross-compiles CodableDefault-Package for iOS, tvOS, watchOS, and visionOS Simulator SDKs |
| demo | Builds CodableDefaultDemo for iOS Simulator |
Upgrade from 1.1.0
Requirements
Ensure your toolchain matches the updated dependency pin:
| Component | Version |
|---|---|
| Swift | 6.2+ |
| Xcode | 16+ (26+ recommended) |
| swift-syntax (transitive) | 603.x |
SPM
.package(url: "https://github.com/tomisacat/CodableDefault.git", from: "2.0.0")Or pin exactly:
.package(url: "https://github.com/tomisacat/CodableDefault.git", exact: "2.0.0")After upgrading:
- Resolve packages — File → Packages → Resolve Package Versions (Xcode) or
swift package resolve - Clean build — macro plugins must recompile on the Mac host
- Re-run macro expansion tests — if you depend on exact expansion snapshots
Breaking changes
| Change | Impact |
|---|---|
swift-syntax 602.x → 603.x |
Requires a Swift 6.2 toolchain that resolves swift-syntax 603. Consumers on older Xcode versions may need to upgrade. |
| Transform expansion output | Non-throwing @Default(…, transform:) properties emit code without try. Behavior is identical; expansion diffs only. |
| Platform manifest | Package now lists watchOS, tvOS, and visionOS minimums. Additive — no code changes required. |
No changes to @CodableDefault, @Default, @Default(_:codingKey:), or @Default(_:transform:) signatures.
Also in this release
Documentation
- README badges (CI, release, Swift, platforms, license)
- Platform requirements table updated for watchOS, tvOS, and visionOS
- Demo app screenshot and setup instructions
Compare: 1.1.0...2.0.0
CodableDefault 1.1.0
CodableDefault 1.1.0
Post-decode transforms for @Default
Version 1.1.0 adds optional (T) throws -> T transforms on defaulted properties. After CodableDefault resolves a value — from JSON or from the default fallback — you can clamp, normalize, or validate it before assignment. No manual init(from:), no property wrappers.
This is an additive minor release. Existing @Default and @Default(_:codingKey:) usage is unchanged.
What's new
@Default(_:transform:)
Apply a transform to any defaulted property:
import CodableDefault
@CodableDefault
struct Config: Codable {
@Default(10, transform: { min($0, 100) })
var retryCount: Int
@Default("guest", transform: { $0.trimmingCharacters(in: .whitespaces) })
var username: String
}@Default(_:codingKey:transform:)
Combine a custom JSON key with a transform:
@Default(0, codingKey: "limit", transform: { min($0, 100) })
var limit: IntHow it works
For each transformed property, @CodableDefault generates:
- Resolve —
decodeIfPresentwith fallback to the default (same as 1.0.0 for missing keys,null, and wrong types) - Transform — call your closure on the resolved value
- Assign — store the result on the property
self.retryCount = try {
let __codableDefault_retryCount =
(try? container.decodeIfPresent(Int.self, forKey: .retryCount))
?? 10
return try { min($0, 100) }(__codableDefault_retryCount)
}()| Situation | Resolved value | Transform runs? |
|---|---|---|
| Key absent | Default | Yes |
Value is null |
Default | Yes |
| Wrong JSON type | Default | Yes |
| Valid decoded value | Decoded value | Yes |
Throwing transforms propagate out of init(from:), so you can reject invalid values even after a successful decode:
@Default(0, transform: { value in
guard value >= 0 else {
throw DecodingError.dataCorrupted(
.init(codingPath: [], debugDescription: "negative value")
)
}
return value
})
var count: IntUse cases
- Clamping — cap numeric config from permissive APIs (
min($0, 100)) - Normalization — trim whitespace, lowercase enums, dedupe arrays
- Validation — reject out-of-range or invalid wire values with
throws - Coercion — adjust defaults before they are stored (e.g.
default + 1)
Transforms work with structs, classes, custom CodingKeys, and @Default(_:codingKey:) — the same combinations supported in 1.0.0.
Upgrade from 1.0.0
No breaking changes. No API removals or behavior changes for existing macros.
SPM — existing dependency rules continue to work:
.package(url: "https://github.com/tomisacat/CodableDefault.git", from: "1.0.0")Xcode — resolve packages and rebuild. Macro plugins must recompile on the Mac host after upgrading.
To adopt transforms, add transform: to any @Default property that needs post-decode shaping. Required (non-defaulted) properties are unchanged.
Also in this release
Documentation
- README section for
@Default(_:transform:)and@Default(_:codingKey:transform:) - Updated decode behavior table
- Demo scenarios in
CodableDefaultClient(swift run CodableDefaultClient)
Tests
- Macro expansion golden tests for transform and multi-line closures
- Runtime tests covering decoded values, defaults,
null, wrong types, throwing transforms, strings, classes, and user-definedCodingKeys
Internal
- Test suite migrated from XCTest to Swift Testing (no impact on the public API)
Requirements
| Component | Version |
|---|---|
| Swift | 6.2+ |
| Xcode | 16+ (recommended) |
| iOS | 13+ |
| macOS | 10.15+ (required to build macro tooling) |
Full changelog
See CHANGELOG.md.
Compare: 1.0.0...1.1.0
CodableDefault 1.0.0
CodableDefault 1.0.0
First public release of CodableDefault — Swift macros that make Codable decoding tolerant of missing or null JSON fields, while keeping required properties strict.
Highlights
@CodableDefault— Attach to astructorclassto generateinit(from:)(andCodingKeyswhen you don’t define your own).@Default(_:)— Mark properties with a compile-time default used when the JSON key is missing or the value isnull.@Default(_:codingKey:)— Same behavior with a custom JSON key name.- Custom
CodingKeys— Provide your own enum for full control over key mapping; the macro generates onlyinit(from:). - Strict required fields — Properties without
@Defaultstill throw on missing or invalid values. - Declarative models — No manual
init(from:), property wrappers, or post-decode merging for common API response shapes.
Example
import CodableDefault
@CodableDefault
struct Settings: Codable {
var name: String
@Default(false)
var isEnabled: Bool
@Default("guest", codingKey: "user_name")
var username: String
}
let json = #"{"name":"App"}"#.data(using: .utf8)!
let settings = try JSONDecoder().decode(Settings.self, from: json)
// name == "App", isEnabled == false, username == "guest"Requirements
| Component | Version |
|---|---|
| Swift | 6.2+ |
| Xcode | 16+ (recommended) |
| iOS | 13+ |
| macOS | 10.15+ |
Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/tomisacat/CodableDefault.git", from: "1.0.0"),
]
Add the CodableDefault library product to your target. See the README for Xcode setup and troubleshooting.
Notes
- Macros customize decoding only; Swift can still synthesize encode(to:) for Codable structs.
- Supported on struct and class types with stored properties only.
- Released under the MIT License.