Вы можете условно привести 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
не соответствует самому себе .
Как было рассмотрено в вышеупомянутых связанных вопросах и ответах, одним из способов решения этой проблемы является open значение 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 аналогично перегрузке по типу возврата , что не рекомендуется руководящими принципами разработки API .
Сделано в default:
случае fatalError
, так как это, вероятно, должно быть ошибкой программиста для предоставления не декодируемого типа.