首页 > 其他分享 >Swift Codable 自定义默认值解码

Swift Codable 自定义默认值解码

时间:2023-02-02 17:45:43浏览次数:39  
标签:container 自定义 self try var wrappedValue 默认值 Swift public

 

前言

最近我们公司服务端修改了某个接口返回数据结构,减少了一些字段,导致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中的类型,也可以解码成功

@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
}

 

标签:container,自定义,self,try,var,wrappedValue,默认值,Swift,public
From: https://www.cnblogs.com/duzhaoquan/p/17086809.html

相关文章

  • Android Studi导出apk包自定义文件名
    在app的gradle配置文件中的release代码块中放置以下代码android.applicationVariants.all{variant->variant.outputs.all{output->if(outputFileNa......
  • KingbaseES V8R6备份恢复案例之---自定义表空间指定恢复目录数据恢复
    案例说明:KingbaseESV8R6在通过sys_rman执行物理备份恢复时,可以通过参数‘--kb1-path’,指定恢复的数据(data)目录,但如果原备份中包含自定义表空间时,需要建立表空间映射,再执......
  • maven自定义替换的分隔符
    自定义分隔符,需要我们配置maven-resources-plugin插件的参数,如下<plugins>    <plugin>        <groupId>org.apache.maven.plugins</groupId>     ......
  • 自定义支付宝小程序底部导航栏
    自定义支付宝小程序底部导航栏1.在app.jsontabBar配置基础上新增"customize":true2.在项目根目录下创建customize-tab-bar3.编写代码,如下:axml<viewclass="tab-ba......
  • 直播商城系统源码,播放器aliPlayer自定义清晰度切换
    直播商城系统源码,播放器aliPlayer自定义清晰度切换 <!DOCTYPEhtml><html><head>  <metacharset="utf-8">  <metahttp-equiv="x-ua-compatible"content="IE=......
  • java中的自定义枚举类
    自定义枚举类有两种写法 第一种写法:classSeason{privateStringname;//在Season内部,直接创建固定的对象,//优化,可以加入final修饰符pub......
  • robotframe work中 自定义python library使用global variable
    在用robotframework的小伙伴,有没有遇到这样一个问题:  当你用python写一个libary时, 需要用到robotframe外面定义的globalvariable. 怎么处理?  这时一般人的做......
  • Blazor入门100天 : 身份验证和授权 (4) - 自定义字段
    目录建立默认带身份验证Blazor程序角色/组件/特性/过程逻辑DB改Sqlite将自定义字段添加到用户表脚手架拉取IDS文件,本地化资源freesql生成实体类,freesql管理......
  • VSCode 自定义代码片段
    Ctrl+Shift+P输入“代码片段:配置用户代码片段”:搜索你想要设置的语言代码片段,比如,我设置.vue文件的代码片段,选择vue.json:可以配置多个代码片段,一个片段通过一个pr......
  • flutter:安装使用自定义的字体(flutter 3.7.0)
    一,在flutter项目中引入字体文件在项目中创建存放字体的文件夹fonts,并把要使用到的字体文件复制到此文件夹下:如图:说明:刘宏缔的架构森林是一个专注架构的博客,地址:htt......