与擦除类型的编码一致性?

Ren*_*905 5 generics casting type-erasure swift codable

我正在尝试编写一个通用函数来解析几种不同的数据类型。

最初这个方法只适用于 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
    }

}
Run Code Online (Sandbox Code Playgroud)

所以,你可以看到类型检查工作正常,但我被困在得到JSONDecoder().decode(:)接受TCodable类型,一旦我检查了,这是。上面的代码不能编译,有错误

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>

Ham*_*ish 4

您可以有条件地转换T.selfDecodable.Type以获得描述底层Decodable一致类型的元类型:

\n\n
  switch T.self {\n  case let decodableType as Decodable.Type:\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,如果我们尝试传递decodableTypeJSONDecoder,我们就会遇到问题:

\n\n
// error: Cannot invoke \'decode\' with an argument list of type\n// \'(Decodable.Type, from: Data)\'\nreturn try? JSONDecoder().decode(decodableType, from: data)\n
Run Code Online (Sandbox Code Playgroud)\n\n

这不起作用,因为decode(_:from:)有一个通用占位符T : Decodable

\n\n
func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T \n
Run Code Online (Sandbox Code Playgroud)\n\n

我们不能满足T.TypeDecodable.Type因为Decodable 它不符合它本身

\n\n

正如上面链接的问答中所探讨的,此问题的一种解决方法是打开Decodable.Type值,以便挖掘出符合Decodable\xe2\x80\x93 的底层具体类型,然后我们可以用它来满足T.

\n\n

这可以通过协议扩展来完成:

\n\n
extension Decodable {\n  static func openedJSONDecode(\n    using decoder: JSONDecoder, from data: Data\n  ) throws -> Self {\n    return try decoder.decode(self, from: data)\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以将其称为:

\n\n
return try? (decodableType.openedJSONDecode(using: JSONDecoder(), from: data) as! T)\n
Run Code Online (Sandbox Code Playgroud)\n\n

您会注意到,我们必须将强制转换插入到T。这是因为通过强制转换,T.self我们Decodable.Type删除了元类型也描述类型的事实T。因此我们需要强制转换才能取回这些信息。

\n\n

总而言之,您会希望您的函数看起来像这样:

\n\n
func jsonDecode<T>(_ metatype: T.Type, from data: Data) throws -> T {\n  switch metatype {\n  case let codableType as Decodable.Type:\n    let decoded = try codableType.openedJSONDecode(\n      using: JSONDecoder(), from: data\n    )\n\n    // The force cast `as! T` is guaranteed to always succeed due to the fact\n    // that we\'re decoding using `metatype: T.Type`. The cast to\n    // `Decodable.Type` unfortunately erases this information.\n    return decoded as! T\n\n  case is [String: Any].Type, is [Any].Type:\n    let rawDecoded = try JSONSerialization.jsonObject(with: data, options: [])\n    guard let decoded = rawDecoded as? T else {\n      throw DecodingError.typeMismatch(\n        type(of: rawDecoded), .init(codingPath: [], debugDescription: """\n        Expected to decode \\(metatype), found \\(type(of: rawDecoded)) instead.\n        """)\n      )\n    }\n    return decoded\n\n  default:\n    fatalError("\\(metatype) is not Decodable nor [String: Any] nor [Any]")\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我还做了其他一些更改:

\n\n
    \n
  • 将返回类型从 更改T?T。一般来说,您要么希望通过让函数返回一个可选值来处理错误,要么让它抛出 \xe2\x80\x93 来处理错误,这对于调用者来说处理这两种情况可能会非常混乱。

  • \n
  • 添加了显式参数T.Type. 这避免了依赖调用者使用返回类型推断来满足T,IMO 是类似于按返回类型重载的模式,API 设计指南不鼓励这种模式。

  • \n
  • default:之所以这样做fatalError,是因为提供不可解码的类型可能应该是程序员的错误。

  • \n
\n

  • 这是一个非常强大且通用的答案,但对 `openedJSONDecode` 的需求感觉像是一个语法技巧,指向 Swift 泛型中的问题。信息就在那里,但是编译器在不翻转语法的情况下无法弄清楚它。这比“as!”更让我困扰。T`(这绝对让我烦恼,因为它觉得编译器应该能够证明这一点)。我只是在这里嘀咕,但我很感兴趣是否有对此进行更深入的讨论。 (2认同)