swift - 与擦除类型的可编码一致性?
问题描述
我正在尝试编写一个通用函数来解析几种不同的数据类型。
最初这种方法只适用于 Codable 类型,因此它的泛型类型受到约束<T: Codable>
并且一切正常。不过现在,我正在尝试扩展它以检查返回类型是否为 Codable,并根据该检查相应地解析数据
func parse<T>(from data: Data) throws -> T? {
switch T.self {
case is Codable:
// convince the compiler that T is Codable
return try? JSONDecoder().decode(T.self, from: data)
case is [String: Any].Type:
return try JSONSerialization.jsonObject(with: data, options: []) as? T
default:
return nil
}
}
所以你可以看到类型检查工作正常,但是一旦我检查了它,我就会坚持JSONDecoder().decode(:)
接受T
作为一种类型。Codable
上面的代码没有编译,有错误
Cannot convert value of type 'T' (generic parameter of instance method 'parse(from:)') to expected argument type 'T' (generic parameter of instance method 'decode(_:from:)')
和
In argument type 'T.Type', 'T' does not conform to expected type 'Decodable'
我尝试了许多铸造技术,比如let decodableT: <T & Decodable> = T.self
等等,但都失败了——通常是基于Decodable
协议和T
具体类型的事实。
是否有可能(有条件地)将协议一致性重新引入像这样的已擦除类型?我很感激你有任何想法,无论是解决这种方法还是类似的通用解析方法,在这里可能会更成功。
编辑:一个并发症
@vadian 很有帮助地建议创建两种parse(:)
具有不同类型约束的方法,以使用一个签名来处理所有情况。在许多情况下,这是一个很好的解决方案,如果您稍后遇到这个问题,它可能会很好地解决您的难题。
不幸的是,这仅在调用时已知类型的情况下才有效parse(:)
-在我的应用程序中,此方法由另一个泛型方法调用,这意味着该类型已被擦除并且编译器无法选择正确约束parse(:)
执行。
所以,要澄清这个问题的症结所在:是否可以有条件地/可选地将类型信息(例如协议一致性)添加回已擦除的类型?换句话说,一旦一个类型变成了<T>
,有什么办法可以把它转换成<T: Decodable>
?
解决方案
您可以有条件T.self
地转换为Decodable.Type
以获得描述底层Decodable
符合类型的元类型:
switch T.self {
case let decodableType as Decodable.Type:
但是,如果我们尝试传递decodableType
给JSONDecoder
,我们就会遇到问题:
// error: Cannot invoke 'decode' with an argument list of type
// '(Decodable.Type, from: Data)'
return try? JSONDecoder().decode(decodableType, from: data)
这不起作用,因为decode(_:from:)
有一个通用的占位符T : Decodable
:
func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T
我们不能满足T.Type
因为Decodable.Type
不Decodable
符合它自己。
正如在上面链接的问答中所探讨的,此问题的一种解决方法是打开值Decodable.Type
以挖掘出符合的底层具体类型Decodable
——然后我们可以使用它来满足T
.
这可以通过协议扩展来完成:
extension Decodable {
static func openedJSONDecode(
using decoder: JSONDecoder, from data: Data
) throws -> Self {
return try decoder.decode(self, from: data)
}
}
然后我们可以称之为:
return try? (decodableType.openedJSONDecode(using: JSONDecoder(), from: data) as! T)
你会注意到我们必须插入一个强制转换到T
. 这是因为通过转换T.self
为Decodable.Type
我们消除了元类型也描述类型的事实T
。因此,我们需要强制转换以获取此信息。
总而言之,你会希望你的函数看起来像这样:
func jsonDecode<T>(_ metatype: T.Type, from data: Data) throws -> T {
switch metatype {
case let codableType as Decodable.Type:
let decoded = try codableType.openedJSONDecode(
using: JSONDecoder(), from: data
)
// The force cast `as! T` is guaranteed to always succeed due to the fact
// that we're decoding using `metatype: T.Type`. The cast to
// `Decodable.Type` unfortunately erases this information.
return decoded as! T
case is [String: Any].Type, is [Any].Type:
let rawDecoded = try JSONSerialization.jsonObject(with: data, options: [])
guard let decoded = rawDecoded as? T else {
throw DecodingError.typeMismatch(
type(of: rawDecoded), .init(codingPath: [], debugDescription: """
Expected to decode \(metatype), found \(type(of: rawDecoded)) instead.
""")
)
}
return decoded
default:
fatalError("\(metatype) is not Decodable nor [String: Any] nor [Any]")
}
}
我做了一些其他的改变:
将返回类型从 更改
T?
为T
。通常,您要么希望通过让函数返回一个可选项或让它抛出来处理错误——调用者同时处理这两种方式可能会非常混乱。为 .添加了显式参数
T.Type
。这避免了依赖调用者使用返回类型推断来满足,IMO与 API 设计指南不鼓励的T
按返回类型重载的模式类似。提出这种
default:
情况fatalError
,因为提供不可解码的类型可能是程序员的错误。
推荐阅读
- scala - 无法下载流的播放迁移的依赖项(Scala/sbt)
- algorithm - 有向无环图的拓扑排序
- python - 带有嵌套数据的 JSONField 上的 Django Queryset,其中字典键的键名中有连字符
- reactjs - React Type 脚本 Ant 设计步骤设置目标
- android - 按钮背景未加载 - Android Studio
- mysql - mysql配置文件'/opt/bitnami/mysql/conf/my.cnf'不可写
- linux - 从文件中 grep 字符串并读取具有完整变量名的第一个匹配字符串
- c# - 如何从后面的代码调用视图单元内的标签
- c# - 如何读取 JSON 并返回项目的值?
- javascript - 等待多个承诺并从 Firebase 获取一些值