Я постараюсь объяснить, что я хочу сделать, как можно лучше, точно так же, как я пытался в течение последних нескольких дней во время поиска в Google.
Мое приложение взаимодействует с несколькими разными API, но давайте рассмотримответы от одного API в первую очередь. Ответ от каждой конечной точки содержит некоторые «общие параметры», такие как состояния или сообщения об ошибках, и один объект или массив объектов, которые нас больше всего интересуют, он приносит важные данные, мы можем захотеть закодировать их, сохранить их, поместитьэто Realm, CoreData и т. д.
Например, ответ с одним объектом:
{
"status": "success",
"response_code": 200,
"messages": [
"message1",
"message2"
]
"data": {
OBJECT we're interested in.
}
}
Или ответ с массивом объектов:
{
"status": "success",
"response_code": 200,
"messages": [
"message1",
"message2"
]
"data": [
{
OBJECT we're interested in.
},
{
OBJECT we're interested in.
}
]
}
Хорошо,Это достаточно просто, легко понять.
Теперь я хочу написать один «корневой» объект, который будет содержать «общие параметры» или status
, response_code
и messages
и иметь другой, свойство для конкретного объекта (или массива объектов), который нас интересует.
Наследование
Первый подход - создание корневого объекта, например:
class Root: Codable {
let status: String
let response_code: Int
let messages: [String]?
private enum CodingKeys: String, CodingKey {
case status, response_code, messages
}
required public init(from decoder: Decoder) throws {
let container = try? decoder.container(keyedBy: CodingKeys.self)
status = try container?.decodeIfPresent(String.self, forKey: .code) ?? ""
response_code = try container?.decodeIfPresent(Int.self, forKey: .error) ?? 0
messages = try container?.decodeIfPresent([String].self, forKey: .key)
}
public func encode(to encoder: Encoder) throws {}
}
Когда у меня есть этот корневой объект, я могу создать конкретный объект, который наследуется от этого корневого объекта, и передать свой конкретный объект в JSONDecoder, и там у меня есть хорошее решение. Но это решение не подходит для массивов . Может быть, для кого-то это не так, , но я не могу не подчеркнуть, насколько сильно я не хочу создавать дополнительный «множественный» объект, который существует только для хранения массива объектов , например:
class Objects: Root {
let objects: [Object]
// Code that decodes array of "Object" from "data" key
}
struct Object: Codable {
let property1
let property2
let property3
// Code that decodes all properties of Object
}
Не выглядит чисто, для этого требуется отдельный объект, который просто хранит массив, в некоторых случаях он создает проблемы с сохранением в Realm из-за наследования, он , прежде всего , создает менее читаемый код.
Generics
Моя вторая идея состояла в том, чтобы попробовать что-то с Generics, поэтому я сделал что-то вроде этого :
struct Root<T: Codable>: Codable {
let status: String
let response_code: Int
let messages: [String]?
let data: T?
private enum CodingKeys: String, CodingKey {
case status, response_code, messages, data
}
required public init(from decoder: Decoder) throws {
let container = try? decoder.container(keyedBy: CodingKeys.self)
status = try container?.decodeIfPresent(String.self, forKey: .code) ?? ""
response_code = try container?.decodeIfPresent(Int.self, forKey: .error) ?? 0
messages = try container?.decodeIfPresent([String].self, forKey: .key)
data = try container.decodeIfPresent(T.self, forKey: .data)
}
public func encode(to encoder: Encoder) throws {}
}
Благодаря этому я смог передать JSONDecoder как отдельные объекты, так и массивы объектов следующим образом:
let decodedValue = try JSONDecoder().decode(Root<Object>.self, from: data)
// or
let decodedValue = try JSONDecoder().decode(Root<[Object]>.self, from: data)
, и это довольно мило. Я могу получить нужную структуру в свойстве .data структуры Root и использовать ее как угодно, как отдельный объект или как массив объектов. Я могу легко его хранить, манипулировать, как хочу, без ограничений наследование приводит в верхнем примере.
В случае, когда эта идея не подходит для моего случая, я хочу получить доступ к «общим свойствам» в каком-то месте, в котором не уверен, какой T был установленк.
Это упрощенное объяснение того, что на самом деле происходит в моем приложении, я немного расширю его, чтобы объяснить, где это универсальное решение не работает для меня, и, наконец, задам мой вопрос.
Проблема и вопрос
Как упоминалось выше, приложение работает с 3 API, и все 3 API имеют различные структуры Root
, и, конечно, многоразличных "подструктур" - чтобы назвать их. У меня есть одно место, один APIResponse
объект в приложении, который восходит к пользовательской части приложения, в которой я извлекаю 1 читаемую ошибку из decoded value
, decoded value
, являющейся этой «подструктурой», являющейся любым из моих«конкретные объекты», Car
, Dog
, House
, Phone
.
Благодаря решению Inheritance я смог сделать что-то вроде этого:
struct APIResponse <T> {
var value: T? {
didSet {
extractErrorDescription()
}
}
var errorDescription: String? = "Oops."
func extractErrorDescription() {
if let responseValue = value as? Root1, let error = responseValue.errors.first {
self.errorDescription = error
}
else if let responseValue = value as? Root2 {
self.errorDescription = responseValue.error
}
else if let responseValue = value as? Root3 {
self.errorDescription = responseValue.message
}
}
}
, но с решением Generics я не могу этого сделать. Если я попытаюсь написать этот же код с использованием Root1
или Root2
или Root3
, как показано в примере Generics , например:
func extractErrorDescription() {
if let responseValue = value as? Root1, let error = responseValue.errors.first {
self.errorDescription = error
}
}
, я получу сообщение об ошибкеGeneric parameter 'T' could not be inferred in cast to 'Root1'
и здесь, где я пытаюсь извлечь ошибку, я не знаю, какая подструктура была передана в Root1. Было ли это Root1<Dog>
или Root1<Phone>
или Root1<Car>
- я не знаю, как выяснить, и мне, очевидно, нужно знать, чтобы выяснить, является ли значение Root1
или Root2
или Root3
.
Решение, которое я ищу, - это решение, которое позволило бы мне различать Root
объекты с помощью решения Generics, показанного выше, или решение, которое позволяет мне декодировать архитектуру совершенно другим способом, учитывая всеЯ написал, особенно возможность избегать «множественных» объектов
* Если JSON не проходит валидатор JSON, пожалуйста, не обращайте внимания, он был написан от руки только ради этого вопроса
** Если написанный код не запускается, пожалуйста, не обращайте внимания, это скорее архитектурный вопрос, чем компиляция некоторого фрагмента кода.