我正在尝试编写一个通用函数来解析几种不同的数据类型。
最初,此方法仅适用于Codable类型,因此其通用类型受<T: Codable>
约束,一切都很好。现在,如果返回类型是可编码的,我尝试将其扩展为 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(:)
实现。
因此,为了澄清这个问题的症结所在:是否可以有条件/可选地将类型信息(例如协议一致性) back 添加到已擦除的类型?换句话说,一旦类型变为<T>
,是否有任何方法可以将其强制转换为<T: Decodable>
?
答案 0 :(得分:2)
您可以有条件地将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
doesn't conform to itself。
如上面的链接的问答中所述,该问题的一种解决方法是打开 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
添加了显式参数。这样可以避免依赖调用方使用返回类型推断来满足T
,而IMO与返回类型which is discouraged by the API design guidelines的重载类似。
采用default:
的情况fatalError
,因为提供不可分解的类型可能是程序员的错误。