Кодируемое соответствие стертым типам? - PullRequest
0 голосов
/ 03 марта 2019

Я пытаюсь написать универсальную функцию для анализа нескольких различных типов данных.

Первоначально этот метод работал только для Codable типов, поэтому его универсальный тип был ограничен <T: Codable>, и все было хорошо.Теперь я пытаюсь расширить его до check , если тип возвращаемого значения 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
    }

}

Таким образом, вы можете видеть, что type-проверка работает нормально, но я застрял при получении 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 - конкретным типом.

Можно ли (условно) восстановить соответствие протокола стертому типу, как этот?Я был бы признателен за любые ваши идеи, как для решения этого подхода, так и для аналогичных подходов общего синтаксического анализа, которые могли бы быть более успешными здесь.parse(:) методы с различными ограничениями типов для обработки всех случаев с одной сигнатурой.Во многих случаях это отличное решение, и если вы натолкнетесь на этот вопрос позже, оно вполне может решить вашу головоломку.

К сожалению, это работает, только если тип известен в то время, когда parse(:)вызывается - и в моем приложении этот метод вызывается другим универсальным методом, это означает, что тип уже удален, и компилятор не может выбрать правильно ограниченную реализацию parse(:).

Итак, чтобы уточнитьСуть этого вопроса: возможно ли условно / необязательно добавить информацию о типе (например, соответствие протокола) назад к стертому типу?Другими словами, если тип стал <T>, есть ли способ привести его к <T: Decodable>?

1 Ответ

0 голосов
/ 03 марта 2019

Вы можете условно привести 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, так как это, вероятно, должно быть ошибкой программиста для предоставления не декодируемого типа.

...