Swift Codable 自定义默认值解码

前言

最近我们公司服务端修改了某个接口返回数据结构,减少了一些字段,导致iOS这边codeable解码失败,获取不到正确的数据信息,相关业务无法完成。不想使用可选值类型,可以使用属性包装器实现对基础类型的包装,decode解析时给定默认值,解决了这个困扰

Decodable 解码时,如果model中的字段为非可选值而 json数据中没有该字段的key,就会解析失败。常用的方法是将数据模型中的key给成可选值类型,但是可选值类型在后续使用中比较麻烦需要解包。创建一种包装类型使其Decodable解码时如果解码失败那么就添加默认值,对于json中有可能不存在key的属性使用这种包装类型。这样就不需要我们编写完全自定义的 Codable 实现,也省去了后续使用可选值的麻烦

具体代码实现

public protocol DecodableDefaultSource {
    associatedtype Value: Decodable
    static var defaultValue: Value { get }
}

public enum DecodableDefault {}

public extension DecodableDefault {
    @propertyWrapper
    struct Wrapper<Source: DecodableDefaultSource> {
        public typealias Value = Source.Value
        public var wrappedValue = Source.defaultValue
        public init(wrappedValue: Value = Source.defaultValue) {
            self.wrappedValue = wrappedValue
        }
    }

    typealias Source = DecodableDefaultSource
    typealias List = Decodable & ExpressibleByArrayLiteral
    typealias Map = Decodable & ExpressibleByDictionaryLiteral

    //定义一些常用的数据类型默认值解码类型
    enum Sources {
        public enum True: Source {
            public static var defaultValue: Bool { true }
        }

        public enum False: Source {
            public static var defaultValue: Bool { false }
        }
        
        public enum IntZero: Source {
            public static var defaultValue: Int { 0 }
        }
        
        public enum Int64Zero: Source {
            public static var defaultValue: Int64 { 0 }
        }
        
        public enum DoubleZero: Source {
            public static var defaultValue: Double { 0 }
        }
        
        public enum EmptyString: Source {
            public static var defaultValue: String { "" }
        }

        public enum EmptyList<T: List>: Source {
            public static var defaultValue: T { [] }
        }

        public enum EmptyMap<T: Map>: Source {
            public static var defaultValue: T { [:] }
        }
    }

    typealias IntZero = Wrapper<Sources.IntZero>
    typealias Int64Zero = Wrapper<Sources.Int64Zero>
    typealias DoubleZero = Wrapper<Sources.DoubleZero>
    typealias True = Wrapper<Sources.True>
    typealias False = Wrapper<Sources.False>
    typealias EmptyString = Wrapper<Sources.EmptyString>
    typealias EmptyList<T: List> = Wrapper<Sources.EmptyList<T>>
    typealias EmptyMap<T: Map> = Wrapper<Sources.EmptyMap<T>>
}

extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}

extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
    public func encode(to encoder: Encoder) throws {
        
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}

extension DecodableDefault.Wrapper: Decodable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(Value.self)
    }
}

extension KeyedDecodingContainer {
    func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
                   forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

另一种实现方式

定义常用类型的包装类型,自定义解码,此方式可以添加一些容错处理,如果json中key对应的类型不是model的类型但是可以转成model中的类型,也可以解码成功

//MARK: 另一种方式
@propertyWrapper
public struct DecodableString:Codable {
    public var wrappedValue: String
    
    public init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        var string: String = ""
        do {
            string = try container.decode(String.self)
        } catch  {
            //此处可添加一些其它类型,例如Int
            do {
                string = String(try container.decode(Bool.self))
            } catch {
                do {
                    string = String(try container.decode(Double.self))
                } catch {
                    
                }
            }
        }
        self.wrappedValue = string
    }
}

@propertyWrapper
public struct DecodableInt:Codable {
    public var wrappedValue: Int
    
    public init(wrappedValue: Int) {
        self.wrappedValue = wrappedValue
    }
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        var num: Int = 0
        do {
            num = try container.decode(Int.self)
        } catch  {
            //此处可添加一些其它类型
            do {
                num = (try container.decode(Bool.self)) ? 1 : 0
            } catch {
                do {
                    num = Int(try container.decode(Double.self))
                } catch {
                    do {
                        num = Int(try container.decode(String.self)) ?? 0
                    } catch  {
                        
                    }
                    
                }
            }
        }
        self.wrappedValue = num
    }
}
@propertyWrapper
public struct DecodableDouble:Codable {
    public var wrappedValue: Double
    
    public init(wrappedValue: Double) {
        self.wrappedValue = wrappedValue
    }
    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        var num: Double = 0
        do {
            num = try container.decode(Double.self)
        } catch  {
            //此处可添加一些其它类型
            do {
                num = Double(try container.decode(String.self)) ?? 0
            } catch {
                
            }
        }
        self.wrappedValue = num
    }
}

具体使用

//使用实例
struct User:Codable {
    var name: String
    @DecodableInt var age: Int
    @DecodableDefault.EmptyString var nickName
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SwiftUI提供了多种方法来实现自定义布局。其中,可以使用HStack、VStack、ZStack等布局容器来组合和排列视图元素,实现各种复杂界面。 另外,SwiftUI还提供了对齐指南来帮助文本对齐,如.leading、.trailing、.top等。但是当处理在不同视图之间分割的视图时,如果需要使完全不同的两个视图部分对齐,可以使用自定义的对齐辅助线。使用这些辅助线可以在整个界面中创建和使用辅助线,在视图之前或之后发生的变化并不重要,它们仍然会按照一条线排列。 例如,可以使用offset(x: CGFloat, y: CGFloat)方法为视图指定水平和垂直的偏移距离。这个方法可以将视图在水平和垂直方向上移动指定的距离。 综上所述,使用SwiftUI可以通过布局容器和对齐指南实现自定义布局,同时也可以使用偏移方法来调整视图的位置。这些功能可以帮助您创建个性化的布局。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SwiftUI 布局之自定义手动布局设置各种相对位置(教程含源码)](https://blog.csdn.net/iCloudEnd/article/details/107718464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SwiftUI之深入解析布局如何自定义AlignmentGuides](https://blog.csdn.net/Forever_wj/article/details/121958698)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值