擦除类型的可编码一致性?
Codable Conformance with Erased Types?
我正在尝试编写一个通用函数来解析几种不同的数据类型。
最初此方法仅适用于 Codable 类型,因此其泛型类型受 <T: Codable>
约束,一切正常。不过现在,如果 return 类型是 Codable,我试图将其扩展为 check,并根据该 check
相应地解析数据
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(:)
实现。
所以,为了澄清这个问题的关键:是否可以conditionally/optionally 添加类型信息(例如协议一致性)返回 到已擦除的类型?换句话说,一旦一个类型变成 <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
我们无法用 Decodable.Type
满足 T.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]")
}
}
我还做了一些其他更改:
已将 return 类型从 T?
更改为 T
。通常,您要么希望通过使函数 return 成为可选函数来处理错误,要么通过抛出它来处理错误——调用者同时处理这两种错误可能会非常混乱。
为 T.Type
添加了一个显式参数。这避免了依赖调用者使用 return 类型推断来满足 T
,IMO 与 return 类型 which is discouraged by the API design guidelines.[=42= 重载的模式类似]
制作了 default:
案例 fatalError
因为提供不可解码类型可能是程序员错误。
我正在尝试编写一个通用函数来解析几种不同的数据类型。
最初此方法仅适用于 Codable 类型,因此其泛型类型受 <T: Codable>
约束,一切正常。不过现在,如果 return 类型是 Codable,我试图将其扩展为 check,并根据该 check
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(:)
实现。
所以,为了澄清这个问题的关键:是否可以conditionally/optionally 添加类型信息(例如协议一致性)返回 到已擦除的类型?换句话说,一旦一个类型变成 <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
我们无法用 Decodable.Type
满足 T.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]")
}
}
我还做了一些其他更改:
已将 return 类型从
T?
更改为T
。通常,您要么希望通过使函数 return 成为可选函数来处理错误,要么通过抛出它来处理错误——调用者同时处理这两种错误可能会非常混乱。为
T.Type
添加了一个显式参数。这避免了依赖调用者使用 return 类型推断来满足T
,IMO 与 return 类型 which is discouraged by the API design guidelines.[=42= 重载的模式类似]制作了
default:
案例fatalError
因为提供不可解码类型可能是程序员错误。