# Swift PropertyWrapper ###### tags: `iOS` `Swift` ## 目的: 在解JSON資料時,若出現格式不符合,或原本必填的參數沒給,會自動帶入預設值而不會噴錯。 ## 範例: JSON資料如下 ``` JSON { "name": "orange", //食物名稱 "calorie": 54, //熱量 "state": "good" // 食物保存狀態 1.good 2.normal 3.bad "isExpired": false //是否過期 } ``` 但若今天傳入 ``` JSON { "name": "orange", "calorie": 54, "state": "excellent" } ``` 也要能夠正確解析 ## 程式碼完成如下 ``` swift import Foundation protocol DefaultValue { associatedtype Value: Decodable static var defaultValue: Value { get } } extension Bool { enum False: DefaultValue { static let defaultValue = false } enum True: DefaultValue { static let defaultValue = true } } @propertyWrapper struct Default<T: DefaultValue> { var wrappedValue: T.Value } extension Default: Decodable { init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() wrappedValue = (try? container.decode(T.Value.self)) ?? T.defaultValue } } extension KeyedDecodingContainer { func decode<T>( type: Default<T>.Type, forKey key: Key ) throws -> Default<T> where T: DefaultValue { try decodeIfPresent(type, forKey: key) ?? Default(wrappedValue: T.defaultValue) } } struct Food: Decodable { struct FoodState: RawRepresentable, Decodable { static let good = FoodState(rawValue: "good") static let bad = FoodState(rawValue: "bad") static let normal = FoodState(rawValue: "normal") let rawValue: String } var name: String var calorie: Int @Default<Bool.False> var isExpired: Bool @Default<FoodState.Normal> var state: FoodState } extension Food.FoodState { enum Normal: DefaultValue { static let defaultValue = Food.FoodState.normal } enum Good: DefaultValue { static let defaultValue = Food.FoodState.good } enum Bad: DefaultValue { static let defaultValue = Food.FoodState.bad } } ``` ## 測試 ```swift let jsonString = """ { "name": "Apple", "calorie": 54, "state": "excellent" } """ let jsonData = jsonString.data(using: .utf8) let food = try JSONDecoder().decode(Food.self, from: jsonData!) print("calorie: \(food.calorie)") //calorie: 54 print("isExpired: \(food.isExpired)") //isExpired: false print("name: \(food.name)") //name: Apple print("state: \(food.state.rawValue)") //state: excellent ```