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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C#文件安全管理解析
彭泽0902
2018/01/04
1.6K0
C#常用的IO操作方法
public class IoHelper { /// <summary> /// 判断文件是否存在 /// </summary>
彭泽0902
2018/01/04
9380
C#目录文件复制、创建操作
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.IO; usi
跟着阿笨一起玩NET
2018/09/19
2.3K0
C# WinForm实现自动更新程序的案例分享
string runPath = Process.GetCurrentProcess().MainModule.FileName;
用户7718188
2022/11/06
8900
C#文件帮助类FoderHelper
using System; using System.Data; using System.Configuration; using System.Linq; using System.Web; using System.IO; using System.Collections; using System.Collections.Generic; using System.Security.AccessControl;
用户7108768
2021/11/03
3580
[C#] 常用工具类——文件操作类
/// <para> FilesUpload:工具方法:ASP.NET上传文件的方法</para> /// <para> FileExists:返回文件是否存在</para> /// <para> IsImgFilename:判断文件名是否为浏览器可以直接显示的图片文件名</para> /// <para> CopyFiles:复制指定目录的所有文件</para> /// <para> MoveFiles:移动指定目录的所有文件</para> /// <para> D
跟着阿笨一起玩NET
2018/09/19
2.7K0
C#利用SharpZipLib生成压缩包
SharpZipLib是一个开源的C#压缩解压库,应用非常广泛。就像用ADO.NET操作数据库要打开连接、执行命令、关闭连接等多个步骤一样,用SharpZipLib进行压缩和解压也需要多个步骤。SharpZipLib功能比较强大,在很多C#的应用中,都有它的身影,我们可以通过引入SharpZipLib类库文件,在程序中实现自动压缩文件以及解压缩文件的功能,例如一个常见的情景就是用户客户端程序下载更新包,下载完成之后,在本地自动解压文件。
郑子铭
2023/02/12
8960
C#利用SharpZipLib生成压缩包
.NET Core 3.0之深入源码理解Configuration(二)
上一篇文章讨论了Configuration的几个核心对象,本文继续讨论Configuration中关于文件型配置的相关内容。相比较而言,文件型配置的使用场景更加广泛,用户自定义配置扩展也可以基于文件型配置进行扩展。如果需要查看上一篇文章,可以点击移步。
AI.NET 极客圈
2019/07/19
7440
.NET Core 3.0之深入源码理解Configuration(二)
最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)
彭泽0902
2018/01/04
3.3K0
C#常用操作类库四(File操作类)
View Code    public class FileHelper : IDisposable     {         private bool _alreadyDispose = false;         #region 构造函数         public FileHelper()         {             //             // TODO: 在此处添加构造函数逻辑             //         }         ~FileHelpe
跟着阿笨一起玩NET
2018/09/18
9810
.NET代码错误日志
1.首先创建一个类Files using System.IO; using System.Security.AccessControl; namespace 命名空间 {     public class Files     {         /// <summary>         /// 给指定的操作系统用户赋操作权限         /// </summary>         /// <param name="pathname">文件的路径</param>         /// <param
DougWang
2020/02/18
6510
C#常用目录文件操作封装类代码
这个c#类封装了常用的目录操作,包括列出目录下的文件、检测目录是否存在、得到目录下的文件列表、检测目录是否为空、查找目录下的文件等等功能
用户7108768
2021/11/03
1.1K0
Unity网络交互☀️压缩包zip下载与解压
在Unity搜索 I18N ,将这几个文件复制到Unity:Asset/DLL文件夹即可。
星河造梦坊官方
2024/08/15
1610
Unity网络交互☀️压缩包zip下载与解压
C# IO操作
明志德道
2023/10/21
2120
OxyPlot 导出图片及 WPF 元素导出为图片的方法
最近有个需求,就是将 OxyPlot 图形导出图片。经过尝试,本文记录三种方法:1、OxyPlot 自带导出方法;2、网上找的导出 WPF 界面元素的方法;3、基于方法 2 的附加属性调用方式。下面将逐一介绍。
独立观察员
2022/12/06
1.2K0
OxyPlot 导出图片及 WPF 元素导出为图片的方法
Visual Studio Package 插件开发
  这段时间公司新做了一个支付系统,里面有N个后台服务,每次有更新修改,拷贝打包发布包“不亦乐乎”。。。于是我想要不要自己定制个打包插件。
陈珙
2018/09/12
8700
Visual Studio Package 插件开发
c# 自动更新程序
1、我这里用到了json,那么不能直接饮用json的dll文件,会出现更新时候占用的问题,可以使用fastjson的开源代码,放进来解决,你可以直接使用xml格式的返回内容,这样就不需要json了,这样更方便
冰封一夏
2020/11/24
1.1K0
c# 自动更新程序
WPF项目从.Net Framework迁移到.Net6
Nuget 安装 Microsoft.Extensions.Configuration
码客说
2022/09/28
9530
WPF项目从.Net Framework迁移到.Net6
【游戏开发】Excel表格批量转换成lua的转表工具
  在上篇博客《【游戏开发】Excel表格批量转换成CSV的小工具》 中,我们介绍了如何将策划提供的Excel表格转换为轻便的CSV文件供开发人员使用。实际在Unity开发中,很多游戏都是使用Lua语言进行开发的。如果要用Lua直接读取CSV文件的话,又要写个对应的CSV解析类,不方便的同时还会影响一些加载速度,牺牲游戏性能。因此我们可以直接将Excel表格转换为lua文件,这样就可以高效、方便地在Lua中使用策划配置的数据了。在本篇博客中,马三将会和大家一起,用C#语言实现一个Excel表格转lua的转表工具——Xls2Lua,并搭配一个通用的ConfigMgr来读取lua配置文件。
马三小伙儿
2018/09/12
5.6K0
【游戏开发】Excel表格批量转换成lua的转表工具
C#封装的常用文件操作代码类
这个C#类封装了我们经常能用到的文件操作方法,包括读写文件、获取文件扩展名、复制文件、追加内容到文件、删除文件、移动文件、创建目录、递归删除文件及目录、列目录、列文件等,不可多得。
用户7108768
2021/11/02
9180
相关推荐
C#文件安全管理解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验