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

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 因为提供不可解码类型可能是程序员错误。