Skip to content

Releases: tomisacat/CodableDefault

CodableDefault 2.0.0

14 Jun 13:25

Choose a tag to compare

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

Each 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 from init(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:

  1. Resolve packages — File → Packages → Resolve Package Versions (Xcode) or swift package resolve
  2. Clean build — macro plugins must recompile on the Mac host
  3. 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

11 Jun 12:44

Choose a tag to compare

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: Int

How it works

For each transformed property, @CodableDefault generates:

  1. ResolvedecodeIfPresent with fallback to the default (same as 1.0.0 for missing keys, null, and wrong types)
  2. Transform — call your closure on the resolved value
  3. 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: Int

Use 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-defined CodingKeys

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

31 May 04:04

Choose a tag to compare

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 a struct or class to generate init(from:) (and CodingKeys when you don’t define your own).
  • @Default(_:) — Mark properties with a compile-time default used when the JSON key is missing or the value is null.
  • @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 only init(from:).
  • Strict required fields — Properties without @Default still 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.