Skip to content

Kayllawen/AutoCodablePackage

Repository files navigation

AutoCodable Package

一個強大的 Swift Macros 套件,為 JSON 解碼提供自動預設值支援,徹底解決了 Property Wrapper + Codable 的技術限制。 使用 Swift 5.9 引入的 Macro 來解決 Codable 中一個常見的痛點:後端回傳的資料型別不穩定或是有缺漏時,容易導致解碼失敗。

A powerful Swift Macros suite that provides automatic default value support for JSON decoding, completely solving the technical limitations of Property Wrapper + Codable. It leverages Swift 5.9's Macro feature to solve a common pain point in Codable: when backend data types are unstable or missing, it often leads to decoding failures.

在真實世界的開發場景中,API 回傳的 JSON 品質參差不齊,AutoCodable 透過提供預設值和型別轉換,大幅提高了 JSON 解碼的穩定性與容錯能力,避免了因為單一欄位解碼失敗而導致整個物件解碼失敗的問題。且語法簡潔且直觀:透過 @AutoCodable 和 @DecodeDefault 這兩個 attached Macro,開發者可以很輕易地為特定屬性加上解碼保護,而不需要手動撰寫大量的 init(from:) 和 CodingKeys,讓 Model 的宣告變得非常乾淨。透過 Swift Macro,AutoCodable 在編譯時期自動生成 Codable,行時期的效能與手寫 Codable 幾乎沒有差異,且可以在編譯時期就能發現錯誤,而不是在執行時期才 Crash。可以透過 Xcode 的 "Expand Macro" 功能,清楚地看到 Macro 生成的程式碼,方便除錯。

In real-world development scenarios, the quality of JSON returned by APIs is inconsistent. AutoCodable greatly improves the stability and fault tolerance of JSON decoding by providing default values and type conversion, avoiding the problem of the entire object failing to decode due to a single field decoding failure. The syntax is concise and intuitive: through the @AutoCodable and @DecodeDefault attached macros, developers can easily add decoding protection to specific properties without having to manually write a large amount of init(from:) and CodingKeys, making the Model declaration very clean. Through Swift Macro, AutoCodable automatically generates Codable at compile time, the runtime performance is almost the same as handwritten Codable, and errors can be found at compile time instead of crashing at runtime. You can clearly see the code generated by the Macro through Xcode's "Expand Macro" function, which is convenient for debugging.

🎯 核心功能 / Core Features

✅ 完美的語法支援 / Perfect Syntax Support

@AutoCodable
struct Model: Codable {
    @DecodeDefault("") var name: String           // Use "" when decoding fails
    @DecodeDefault("Tom") var nickname: String    // Use "Tom" when decoding fails
    @DecodeDefault(100) var score: Int            // Use 100 when decoding fails
    @DecodeDefault(.daily) var type: TypeEnum     // Use .daily when decoding fails
}

✅ 解決所有技術問題 / Solve All Technical Issues

  • 無全域變數 / No Global Variables:沒有線程安全問題 / No thread safety issues
  • 無記憶體洩漏 / No Memory Leaks:編譯時生成,無靜態存儲 / Compile-time generation, no static storage
  • 無運行時開銷 / No Runtime Overhead:完全在編譯時處理 / Completely handled at compile time
  • 完全類型安全 / Completely Type Safe:編譯時檢查所有類型 / All types checked at compile time
  • 支援所有 Codable 類型 / Support All Codable Types:包括自定義 enum / Including custom enums

🚀 開始方式 / Usage

1. 基本使用 / Basic Usage

@AutoCodable
struct User: Codable {
    @DecodeDefault("Unknown") var name: String
    @DecodeDefault(0) var age: Int
    @DecodeDefault(true) var isActive: Bool
    
    // Required properties (must exist in JSON)
    var email: String
    var userId: String
}

2. JSON 解碼測試 / JSON Decoding Test

let json = """
{
    "email": "test@example.com",
    "userId": "12345"
    // Note: Missing name, age, isActive intentionally
}
"""

let user = try JSONDecoder().decode(User.self, from: json.data(using: .utf8)!)

print(user.name)     // "Unknown" (using default value)
print(user.age)      // 0 (using default value)
print(user.isActive) // true (using default value)
print(user.email)    // "test@example.com" (from JSON)

🏗️ 技術架構 / Technical Architecture

生成的代碼範例 / Generated Code Example

/* Your code */
@AutoCodable
struct Model: Codable {
    @DecodeDefault("Tom") var name: String
    @DecodeDefault(100) var score: Int
}

/* Expanded Source */
struct Model: Codable {
    var name: String
    var score: Int

    // Following auto-generated code by Macro
    enum CodingKeys: String, CodingKey {
        case name = "name"
        case score = "score"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = (try? container.decode(String.self, forKey: .name)) ?? "Tom"
        self.score = (try? container.decode(Int.self, forKey: .score)) ?? 100
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(score, forKey: .score)
    }
}

📝 詳細使用指南 / Detailed Usage Guide

支援的類型 / Supported Types

基本類型 / Basic Types

@AutoCodable
struct BasicTypes: Codable {
    @DecodeDefault() var optional: String?
    @DecodeDefault("") var text: String
    @DecodeDefault(0) var number: Int
    @DecodeDefault(0.0) var decimal: Double
    @DecodeDefault(false) var flag: Bool
}

集合類型 / Collection Types

@AutoCodable
struct Collections: Codable {
    @DecodeDefault([]) var items: [String]
    @DecodeDefault([:]) var metadata: [String: String]
    @DecodeDefault(Set<String>()) var tags: Set<String>
}

自定義 Enum / Custom Enums

enum Status: String, Codable {
    case active, inactive, pending
}

@AutoCodable
struct WithEnum: Codable {
    @DecodeDefault(Status.pending) var status: Status
}

嵌套結構 / Nested Structures

@AutoCodable
@GenerateEmptyInit
struct Address: Codable {
    @DecodeDefault("") var street: String
    @DecodeDefault("") var city: String
}

extension Address {
    static let `default` = Address()
}

@AutoCodable
struct User: Codable {
    @DecodeDefault("") var name: String
    @DecodeDefault(Address.default) var address: Address
}

初始化器生成宏使用 / Initializer Generation Macros Usage

@GenerateEmptyInit - 空初始化器 / Empty Initializer
@AutoCodable
@GenerateEmptyInit
struct Settings: Codable {
    @DecodeDefault(true) var notifications: Bool
    @DecodeDefault("light") var theme: String
    @DecodeDefault(60) var timeout: Int
    @DecodeIgnore var sessionId: String? = nil
}

// Create instance with default values
let defaultSettings = Settings()
print(defaultSettings.notifications) // true
print(defaultSettings.theme)        // "light"
print(defaultSettings.timeout)      // 60
@GenerateMemberwiseInit - 成員初始化器 / Memberwise Initializer
@AutoCodable
@GenerateMemberwiseInit
struct Product: Codable {
    @DecodeDefault("") var name: String
    @DecodeDefault(0.0) var price: Double
    @DecodeDefault(true) var isAvailable: Bool
    @DecodeIgnore var createdAt: Date = Date()
}

// Create instance with custom values
let product = Product(
    name: "MacBook Pro",
    price: 2999.99,
    isAvailable: false
)
print(product.name)        // "MacBook Pro"
print(product.price)       // 2999.99
print(product.isAvailable) // false
組合使用 / Combined Usage
@AutoCodable
@GenerateEmptyInit          // Company()
@GenerateMemberwiseInit     // Company(name:industry:location:)
struct Company: Codable {
    @DecodeDefault("") var name: String
    @DecodeDefault("") var industry: String
    @DecodeDefault("TW") var location: String
}

// Both initialization methods available
let defaultCompany = Company()                    // Use default
let customCompany = Company(                      // Use custom values
    name: "Apple",
    industry: "Technology", 
    location: "US"
)

// Usage in extensions
extension Company {
    static let `default` = Company()              // Use default init
    static let tech = Company(                    // Use All parameters init
        name: "Tech Corp",
        industry: "Technology",
        location: "TW"
    )
}
複雜嵌套範例 / Complex Nested Example
@AutoCodable
@GenerateEmptyInit
@GenerateMemberwiseInit
struct DatabaseConfig: Codable {
    @DecodeDefault("localhost") var host: String
    @DecodeDefault(5432) var port: Int
    @DecodeDefault("myapp") var database: String
}

extension DatabaseConfig {
    static let `default` = DatabaseConfig()
}

@AutoCodable
@GenerateEmptyInit
@GenerateMemberwiseInit
struct AppConfig: Codable {
    @DecodeDefault("MyApp") var appName: String
    @DecodeDefault("1.0.0") var version: String
    @DecodeDefault(DatabaseConfig.default) var database: DatabaseConfig
    @DecodeIgnore var buildTimestamp: Date = Date()
}

// Flexible creation methods
let config1 = AppConfig()                                      // Default
let config2 = AppConfig(                                       // Custom top-level properties
    appName: "MyCustomApp",
    version: "2.0.0", 
    database: DatabaseConfig()
)
let config3 = AppConfig(                                       // Fully custom
    appName: "ProductionApp",
    version: "1.5.0",
    database: DatabaseConfig(
        host: "prod-db.company.com",
        port: 5432,
        database: "production"
    )
)

屬性驗證規則 / Property Validation Rules

使用 @GenerateEmptyInit@GenerateMemberwiseInit 時,必須遵循以下規則:

When using @GenerateEmptyInit or @GenerateMemberwiseInit, you must follow these rules:

@AutoCodable
@GenerateEmptyInit
@GenerateMemberwiseInit
struct ValidModel: Codable {
    @DecodeDefault("") var name: String           // ✅ Marked @DecodeDefault
    @DecodeDefault(0) var age: Int               // ✅ Marked @DecodeDefault
    @DecodeIgnore var cache: String? = nil // ✅ Marked @DecodeIgnore
}

@AutoCodable
@GenerateMemberwiseInit
struct InvalidModel: Codable {
    @DecodeDefault("") var name: String
    var unmarkedProperty: String  // ❌ Unmarked property: unmarkedProperty
}

類型轉換 / Type Conversion

@AutoCodable
struct TypeConversion: Codable {
    // String to Int conversion
    @DecodeDefault(0, from: String.self) var stringToInt: Int
    
    // String to Bool conversion  
    @DecodeDefault(false, from: String.self) var stringToBool: Bool
    
    // Handle multiple source types
    @DecodeDefault(0, from: [Int.self, String.self]) var flexibleInt: Int
}

自定義 JSON Key / Custom JSON Keys

@AutoCodable
struct CustomKeys: Codable {
    @DecodeDefault("", keyName: "user_name") var userName: String
    @DecodeDefault(0, keyName: "user_id") var userId: Int
    @DecodeDefault(false, keyName: "is_verified") var isVerified: Bool
}

忽略屬性 / Ignore Properties

@AutoCodable
struct WithIgnoredProperties: Codable {
    @DecodeDefault("") var name: String
    @DecodeDefault(0) var age: Int
    @DecodeIgnore var computedValue: String = "calculated"
    @DecodeIgnore var timestamp: Date = Date()
}

可選類型預設值 / Optional Type Defaults

@AutoCodable
struct OptionalDefaults: Codable {
    @DecodeDefault() var optionalName: String?        // Defaults to nil
    @DecodeDefault() var optionalAge: Int?            // Defaults to nil  
    @DecodeDefault("Guest") var nameWithDefault: String  // Defaults to "Guest"
}

混合使用範例 / Mixed Usage Example

@AutoCodable
struct Product: Codable {
    // Properties with default values
    @DecodeDefault("") var name: String
    @DecodeDefault(0.0) var price: Double
    @DecodeDefault(true) var isAvailable: Bool
    @DecodeDefault([]) var tags: [String]
    
    // Required properties (must exist in JSON)
    var productId: String
    var createdAt: Date
    
    // Optional properties
    var description: String?
    var imageUrl: String?
}

邊緣情況處理 / Edge Case Handling

@AutoCodable
struct EdgeCases: Codable {
    // Handle whitespace in string-to-number conversion
    @DecodeDefault(0, from: String.self) var numberFromString: Int
    
    // Handle NaN for floating point
    @DecodeDefault(0.0, from: String.self) var doubleFromString: Double
    
    // Handle mixed character strings (will use default)
    @DecodeDefault(0, from: String.self) var intFromMixedString: Int
}

// JSON Test Data
let json = """
{
    "numberFromString": "  123  ",      // -> 123 (trimmed)
    "doubleFromString": "NaN",          // -> Double.nan
    "intFromMixedString": "123有中文"    // -> 0 (default, conversion fails)
}
"""

🧪 測試和驗證 / Testing and Validation

測試覆蓋 / Test Coverage

  • ✅ 基本 Property Wrapper 功能 / Basic Property Wrapper functionality
  • ✅ Macro 展開正確性 / Macro expansion correctness
  • ✅ 錯誤情況處理 / Error condition handling
  • ✅ 複雜類型支援 / Complex type support
  • ✅ 實際 JSON 解碼場景 / Real JSON decoding scenarios
  • ✅ 類型轉換測試 / Type conversion testing
  • ✅ 邊緣情況測試 / Edge case testing

在 Xcode 中查看 Macro 展開 / View Macro Expansion in Xcode

  1. 右鍵點擊 @AutoCodable / Right-click on @AutoCodable
  2. 選擇 "Expand Macro" / Select "Expand Macro"
  3. 查看生成的完整代碼 / View the complete generated code

🔍 技術細節 / Technical Details

為什麼選擇 Swift Macros? / Why Choose Swift Macros?

問題分析 / Problem Analysis

傳統的 Property Wrapper + Codable 組合有根本性的技術問題:

Traditional Property Wrapper + Codable combination has fundamental technical issues:

@propertyWrapper
struct DecodeDefault<T: Codable>: Codable {
    init(_ defaultValue: T) { /* User-specified default value */ }
    
    init(from decoder: Decoder) throws {
        // ⚠️ Problem: Cannot access the default value passed by user during initialization!
    }
}
protocol DefaultValueProvider {
    associatedtype Value: Codable
    static var defaultValue: Value { get }
}

@propertyWrapper
struct Default<Provider: DefaultValueProvider>: Codable {
    var wrappedValue: Provider.Value = Provider.defaultValue
    // ... decoder implement ...
}

// 
struct One: DefaultValueProvider { static var defaultValue = 1 }
struct Zero: DefaultValueProvider { static var defaultValue = 0 }
struct True: DefaultValueProvider { static var defaultValue = true }

// ⚠️ If you need a different default value (e.g., 100), you would have to define a new struct for each value.
struct OneHundred: DefaultValueProvider { static var defaultValue = 100 }

Swift Macros 解決方案 / Swift Macros Solution

Swift Macros 在編譯時運行,能夠:

Swift Macros run at compile time and can:

  • 📖 編譯時分析 / Compile-time Analysis:Macro 讀取你的源碼並分析 @DecodeDefault 屬性 / Macro reads your source code and analyzes @DecodeDefault attributes
  • 🏗️ 代碼生成 / Code Generation:自動生成完整的 Codable 實現 / Automatically generates complete Codable implementation
  • 🔒 類型安全 / Type Safety:所有類型檢查在編譯時完成 / All type checking completed at compile time

性能影響 / Performance Impact

✅ 編譯時間 / Compile Time

  • 初次編譯可能增加 2-5 秒 / Initial compilation may increase by 2-5 seconds
  • 增量編譯影響極小 / Minimal impact on incremental compilation

✅ App Size

  • 生成的代碼量與手寫代碼相當 / Generated code amount equivalent to hand-written code
  • 影響 < 0.1%,可忽略 / Impact < 0.1%, negligible

✅ 運行時性能 / Runtime Performance

  • 零運行時開銷 / Zero runtime overhead
  • 與手寫 Codable 代碼性能完全相同 / Exactly the same performance as hand-written Codable code

🎉 優勢總結 / Advantages Summary

開發體驗 / Development Experience

  • 🎯 完美語法 / Perfect Syntax:語法簡潔自然,直接在屬性上聲明預設值,可讀性極高。/ Clean and natural syntax, allowing you to declare default values directly on properties for high readability.
  • 🚀 減少代碼 / Reduce Code:自動生成複雜的 Codable 實現 / Automatically generate complex Codable implementation
  • 🔍 易於調試 / Easy to Debug:可以查看生成的代碼 / Can view generated code
  • 📝 類型提示 / Type Hints:完整的 IDE 支援 / Complete IDE support

技術優勢 / Technical Advantages

  • 零運行時開銷 / Zero Runtime Overhead:編譯時處理 / Compile-time processing
  • 🔒 完全類型安全 / Completely Type Safe:編譯時檢查 / Compile-time checking
  • 🧵 無線程問題 / No Threading Issues:無全域狀態 / No global state
  • 💾 無記憶體洩漏 / No Memory Leaks:無靜態存儲 / No static storage

可維護性 / Maintainability

  • 📚 豐富文檔 / Rich Documentation:完整的使用指南 / Complete usage guide
  • 🧪 完整測試 / Complete Testing:涵蓋各種使用場景 / Cover various usage scenarios
  • 🔄 易於擴展 / Easy to Extend:可添加新功能 / Can add new features
  • 🛠️ 工具支援 / Tool Support:測試和調試工具 / Testing and debugging tools

📋 TODO 和未來計劃 / TODO and Future Plans

已完成 ✅ / Completed ✅

  • 核心 DecodeDefault Property Wrapper / Core DecodeDefault Property Wrapper
  • AutoCodable Macro 實現 / AutoCodable Macro implementation
  • @GenerateEmptyInit 宏實現 / @GenerateEmptyInit Macro implementation
  • @GenerateMemberwiseInit 宏實現 / @GenerateMemberwiseInit Macro implementation
  • 自動空初始化器生成 / Automatic empty initializer generation
  • 自動成員初始化器生成 / Automatic memberwise initializer generation
  • 自訂類型預設值支援 / Custom type default value support
  • 完整測試套件 / Complete test suite
  • 使用文檔和範例 / Usage documentation and examples
  • 類型轉換支援 / Type conversion support
  • 邊緣情況處理 / Edge case handling
  • 結構性測試驗證 / Structural test validation

未來增強 🔮 / Future Enhancements 🔮

  • 支援更多預設值類型(Date, URL 等)/ Support more default value types (Date, URL, etc.)
  • 添加自定義錯誤處理 / Add custom error handling
  • 支援嵌套 Macro 展開 / Support nested Macro expansion
  • 性能優化和更好的錯誤訊息 / Performance optimization and better error messages
  • SwiftUI 整合範例 / SwiftUI integration examples

📞 支援和貢獻 / Support and Contribution

問題回報 / Issue Reporting

如果遇到任何問題,請檢查 / If you encounter any issues, please check:

  1. Swift 版本是否 >= 5.9 / Swift version >= 5.9
  2. 是否正確添加了 Package 依賴 / Package dependency correctly added
  3. 是否在正確的類型上使用了 @AutoCodable / @AutoCodable used on correct types

📋 查看完整變更記錄 / Full Changelog: CHANGELOG.md

AutoCodable Package - Make JSON decoding simpler, safer, and more elegant! 🎉

About

Swift macro package for automatic Codable implementation

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages