Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Codable 解析 JSON 配置默认值

Codable 解析 JSON 配置默认值

作者头像
韦弦zhy
发布于 2021-04-07 02:24:48
发布于 2021-04-07 02:24:48
1.9K00
代码可运行
举报
运行总次数:0
代码可运行

2017年推出的 Codable 无疑是 Swift 的一大飞跃。尽管当时社区已经构建了多种用于本地 Swift 值和 JSON 之间 的编解码工具,但由于 Codable 与 Swift 编译器本身的集成,提供了前所未有的便利性,使我们能够通过使可解码类型遵守 Decodable 协议来定义可解码类型,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Article: Decodable {
    var title: String
    var body: String
    var isFeatured: Bool
}

然而,自从 Codable 引入以来,它就缺少了一个特性,那就是向某些属性添加默认值(而不必使它们成为可选的)。例如,假设上面的isFeatured属性并不总是出现在我们将从中解码文章实例的JSON数据中,在这种情况下,我们希望它默认为 false

即使我们将该默认值添加到属性声明本身,如果基础JSON 数据中缺少该值,则默认解码过程仍将失败:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Article: Decodable {
    var title: String
    var body: String
    var isFeatured: Bool = false // 解码时并不会使用这个值
}

现在,我们总是编写自己的解码代码(通过重写init(from: Decoder) 的默认实现),但这将要求我们接管整个解码过程——这会破坏 Codable 的整个便利性,并要求我们不断更新该代码以应对模型属性的任何更改。

好消息是,我们可以采取另一种方法,那就是使用Swift的属性包装器功能,它使我们能够将自定义逻辑附加到任何存储的属性上。例如,我们可以使用该特性实现 DecodableBool 包装器,设置默认值为 false

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@propertyWrapper
struct DecodableBool {
    var wrappedValue = false
}

然后,我们可以使新的属性包装器实现Decodable协议,以使其能够“接管”它所附加的任何属性的解码过程。在这种情况下,我们确实要使用手动解码实现,因为这样可以直接从 Bool值中解码实例,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension DecodableBool: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(Bool.self)
    }
}

通过扩展实现 Decodable 协议的原因是这样写不会覆盖结构体的成员构造器。 简而言之就是直接写的话,DecodableBool的初始化器就变成了只有 init(from: Decoder),即: DecodableBool(from: Decoder) 而写在扩展的话不仅有init(from: Decoder),还有默认的便利构造器: DecodableBool() DecodableBool(wrappedValue: Bool) DecodableBool(from: Decoder) 参考 结构体便利初始化器 什么时候可以使用结构体的成员构造器?

最后,我们还需要 Codable在解码过程中将上述属性包装器的实例视为可选,这可以通过扩展KeyedDecodingContainer来重载解码特定的类型—— DecodableBool 来完成,在这种情况下,我们仅在存在值的情况下继续解码给定的键,否则我们将返回包装器的空实例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension KeyedDecodingContainer {
    func decode(_ type: DecodableBool.Type,
                forKey key: Key) throws -> DecodableBool {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

有了上面的内容,我们现在可以简单地用新的DecodableBool属性注释任何Bool属性,并且在解码时它将默认设置为false

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Article: Decodable {
    var title: String
    var body: String
    @DecodableBool var isFeatured: Bool
}

非常好。但是,尽管我们现在已经解决了这个特定问题,但是我们的解决方案不是很灵活。如果在某些情况下希望将 true 设置为默认值,或者还要提供其他类型的默认解码值,我们该怎么办?

因此,让我们看看是否可以将解决方案推广到可以在更大范围的情况下应用的解决方案。为此,让我们从为默认源值(即需要解码的值)创建泛型协议开始——这将使我们能够定义各种默认值,而不仅仅是布尔值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protocol DecodableDefaultSource {
    associatedtype Value: Decodable
    static var defaultValue: Value { get }
}

然后,让我们使用一个枚举为即将编写的解码代码创建一个命名空间——这将为我们提供一个非常好的语法,并提供整洁的代码封装:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum DecodableDefault {}

使用无枚举值的枚举实现名称空间的优点是它们无法初始化,这使得它们充当纯包装器,而不是可以实例化的独立类型。

我们将添加到新命名空间的第一种类型是以前的DecodableBool属性包装器的泛型变体——现在它使用DecodableDefaultSource检索其默认wrappedValue,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension DecodableDefault {
    @propertyWrapper
    struct Wrapper<Source: DecodableDefaultSource> {
        typealias Value = Source.Value
        var wrappedValue = Source.defaultValue
    }
}

接下来,让我们使上述属性包装器遵守Decodable,我们还将实现另一个特定新类型的KeyedDecodingContainer重载:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension DecodableDefault.Wrapper: Decodable {
    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()
    }
}

有了上述基础设施,现在让我们继续实现几个默认值源。我们将再次使用枚举为源代码提供额外级别的命名空间(就像Combine为其发布者提供的命名空间一样),并且我们还将添加一些类型别名以使代码更易于阅读:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension DecodableDefault {
    typealias Source = DecodableDefaultSource
    typealias List = Decodable & ExpressibleByArrayLiteral
    typealias Map = Decodable & ExpressibleByDictionaryLiteral

    enum Sources {
        enum True: Source {
            static var defaultValue: Bool { true }
        }

        enum False: Source {
            static var defaultValue: Bool { false }
        }

        enum EmptyString: Source {
            static var defaultValue: String { "" }
        }

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

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

通过将我们的 EmptyListEmptyMap 类型限制为 Swift 的两个文本协议,而不是ArrayDictionary这样的具体类型,我们可以涵盖更多的内容——因为许多不同的类型采用这些协议,包括SetIndexPath等等。

最后,让我们定义一系列方便类型别名,让我们将上述源代码引用为属性包装类型的专用版本——如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension DecodableDefault {
    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>>
}

最后一部分为我们提供了一个非常好的语法,可以用可解码的默认值来注释属性,现在可以这样做:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Article: Decodable {
    var title: String
    @DecodableDefault.EmptyString var body: String
    @DecodableDefault.False var isFeatured: Bool
    @DecodableDefault.True var isActive: Bool
    @DecodableDefault.EmptyList var comments: [Comment]
    @DecodableDefault.EmptyMap var flags: [String : Bool]
}

非常整洁,也许最好的部分是,我们的解决方案现在是真正的通用——我们可以很容易地添加新的来源,只要我们需要,同时保持我们的调用栈尽可能干净。

作为一系列的收尾工作,我们还将使用 Swift 的 条件一致性特征,使我们的属性包装器在其包装的值类型执行以下操作时符合常见协议,例如EquatablehashtableEncodable

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}

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

有了它,我们现在有了一个完整的解决方案,可以用默认的值来注释属性——所有这些都不需要对正在解码的属性类型进行任何更改,而且由于我们的DecodableDefault枚举,它有一个整洁的封装实现。

感谢阅读!?

译自 John Sundell 的 Annotating properties with default decoding values

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
propertye wrapped, optional在Swift妙用
1 关于Optional 使用swift开发项目中会用大所谓的可选类型,如下面的: var age:Int? 我们做做业务是往往使用 if 或者 guard来走 guard let age
大话swift
2021/03/18
1.1K1
Codable 解析 JSON 忽略无效的元素
默认情况下,使用 Swift 内置的 Codable API 编码或解码数组只有全部成功或者全部失败两种情况。可以成功处理所有元素,或者引发错误,这可以说是一个很好的默认设置,因为它可以确保高水平的数据一致性。
韦弦zhy
2021/04/07
3.4K0
Swift Codable 记录解析路径
在我们的工作中,各种特殊情况都有可能遇到,某些特定情况下,需要我们记录模型的解析路径,例如:
韦弦zhy
2021/11/24
8690
Swift 中的属性包装器
当处理代表某种状态形式的属性时,通常会在每次修改值时触发某种关联的逻辑。例如,我们可以根据一组规则验证每个新值,可以以某种方式转换分配的值,或者每当值更改时都可以通知一组观察者。
韦弦zhy
2021/04/08
2.8K0
Encoding and Decoding Custom Types
许多编程任务涉及通过网络连接发送数据,将数据保存到磁盘或将数据提交到API和服务。 这些任务通常要求在传输数据时将数据编码和解码为中间格式。
SheltonWan
2019/08/04
2K0
Codable发布这么久我就不学,摸鱼爽歪歪,哎~就是玩儿
对于大多数的应用程序来说,最常见的任务就是进行网络数据的发送和接收,但是在执行此操作之前,我们需要通过编码或者序列化的方式将数据转换为合适的格式来发送,然后还需要将收到的网络数据转换为合适的格式,这样才能在应用中使用它们,这样的过程叫做解码或着叫反序列化。
HelloWorld杰少
2022/08/04
1.9K0
Codable 自定义解析 JSON
大多数现代应用程序的共同点是,它们需要对各种形式的数据进行编码或解码。无论是通过网络下载的JSON数据,还是存储在本地的模型的某种形式的序列化表示形式,对于几乎任何 Swift 代码库而言,能够可靠地编码和解码不同的数据都是必不可少的。
韦弦zhy
2021/04/14
2.1K0
Swift 5.1 新特性
Swift 5.1 内置于 Xcode 11,新增了很多新特性,比较重要的有以下几个。
YungFan
2019/08/27
1.4K0
Swift 项目中涉及到 JSONDecoder,网络请求,泛型协议式编程的一些记录和想法
最近项目开发一直在使用 swift,因为 HTN 项目最近会有另外一位同事加入,所以打算对最近涉及到的一些技术和自己的一些想法做个记录,同时也能够方便同事熟悉代码。
用户7451029
2020/06/16
6.9K0
UserDefaults 浅析及其使用管理
我想每一个 iOSer 对UserDefaults都有所了解,但大家真的完全了解它吗?下面,我谈谈我对UserDefaults的看法。
CoderStar
2022/08/24
1.2K0
@AppStorage研究
在苹果生态的应用中,开发者或多或少都会使用到UserDefaults。我个人习惯将可被用户自定义的配置信息(精度、单位、色彩等)保存在UserDefaults中。随着配置信息的增加,在SwiftUI视图中使用的@AppStorage越来越多。
东坡肘子
2022/07/28
1.5K0
Kotlin 的 Property Delegate 与 Swift 的 Property Wrapper
Swift 想必大家都已经非常熟悉了,它是苹果公司推出的一门开源语言。Swift 与 Kotlin 几乎是同一段时间开始研发,也是前后呈现在公众面前。二者语法设计上有诸多相似之处,它们的关系让我甚至想到了当年的 Java 和 C#。更神奇的是,Kotlin-Native 居然支持了与 Objective-C 的互调用,进而也就相当于某种意义上支持了与 Swift 的互调用,这下它们就更亲密了。
bennyhuo
2020/06/09
5.3K0
Kotlin 的 Property Delegate 与 Swift 的 Property Wrapper
Swift:缓存Codable数据
我们的大多数应用程序都是某些后端的REST客户端。在开发此类应用程序期间,我们希望使其保持脱机状态。在这种情况下,我们必须将数据缓存在设备本地的某处,以使其无需互联网即可读取。
韦弦zhy
2020/03/20
1.7K0
Swift:缓存Codable数据
Swift Codable 将任意类型解析为想要的类型
默认情况下,使用 Swift 内置的 Codable API 解析 JSON 时,我们的属性类型需要和Json 中的类型保持一致,否则就会解析失败。
韦弦zhy
2021/11/24
2.2K0
揭秘 SwiftData 的数据建模原理
在 SwiftData 的数项改进中,用纯代码声明数据模型无疑给 Core Data 开发者留下了深刻印象。本文将深入探讨 SwiftData 是如何通过代码创建数据模型的,使用了哪些新的语言特性,并展示了如何通过声明代码来创建 PersistentModel 实例。
东坡肘子
2023/10/08
4320
揭秘 SwiftData 的数据建模原理
Swift学习之5.1和5.2新特性
Swift 5.1 内置于 Xcode 11,新增了很多新特性,比较重要的有以下几个。
YungFan
2020/05/18
2.1K0
架构之路 (六) —— VIPER架构模式(二)
源码 1. Swift 首先看下工程组织结构 下面就是源码了 1. SceneDelegate.swift import SwiftUI class SceneDelegate: UIRespond
conanma
2021/09/04
1.2K0
iOS 面试策略之系统框架-网络、推送与数据处理
如果说移动时代的前身是什么,我想一个可能的答案就是网络时代。网络的兴起,让所有设备相连成为了可能,也催生了电商、社交、搜索等多个领域的商业巨头。而移动时代,则是网络时代的必然延伸,它代表着更便捷、更广阔、更深入的连接。
会写bug的程序员
2021/05/15
1.9K0
iOS 面试策略之系统框架-网络、推送与数据处理
Swift 5.2到5.4新特性整理
SE-0287提案改进了Swift使用隐式成员表达式的能力。Swift 5.4之后不但可以使用单个 使用,而且可以链起来使用。
小刀c
2022/08/16
2.3K0
Swift 5.2到5.4新特性整理
避免 SwiftUI 视图的重复计算
随着近年来有关 SwiftUI 的文章与书籍越来越多,开发者应该都已经清楚地掌握了 —— “视图是状态的函数” 这一 SwiftUI 的基本概念。每个视图都有与其对应的状态,当状态变化时,SwiftUI 都将重新计算与其对应视图的 body 值。
东坡肘子
2022/08/03
9.6K0
避免 SwiftUI 视图的重复计算
相关推荐
propertye wrapped, optional在Swift妙用
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验