Лучший подход для передачи структуры данных Response JSON? - PullRequest
3 голосов
/ 25 марта 2019

Я создал бэкэнд и работаю над клиентом переднего плана, в настоящее время ответ от моего клиента на вход в систему возвращает:

{
    "user": {
        "email": "fdsa@fdsafsdfa.com",
        "token": "eyJhbGciOiJIUzI"
    }
}

Это создает проблему, из-за которой я не могу просто декодировать пользователяобъект, я должен сделать следующие 2 слоя для всего:

struct User: Codable {
    let email: String
    let token: String
}

struct UserResponseData: Codable {
    let user: User
}

Есть ли более эффективный способ прямого доступа к значениям и ограничения объекта?Возможно, мое редактирование user json-родителя во что-то более общее, например data, а затем посадить пользователя в это?Я не уверен ...

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

    class APIClient {

    static func signup(request: SignupRequestData, completion: @escaping (Result<UserResponseData>) -> Void) {
        performRequest(route: APIRouter.signup(request), completion: completion)
    }

    @discardableResult
    private static func performRequest<T:Decodable>(route: APIRouter,
                                                    decoder: JSONDecoder = JSONDecoder(),
                                                    completion:@escaping (Result<T>)->Void) -> DataRequest {
        return AF.request(route).responseDecodable (decoder: decoder){ (response: DataResponse<T>) in
            completion(response.result)
        }
    }
}

Цените некоторую помощь в продвижении более подходящей структуры, поэтому у меня нетчтобы развернуть родительский объект, чтобы получить значения, необходимые для клиента

1 Ответ

2 голосов
/ 25 марта 2019

Один довольно простой подход состоит в том, чтобы обернуть это в тип общего назначения Response, который предполагает, что первый ключ всегда является правильным.

struct AnyStringKey: CodingKey {
    var stringValue: String
    init?(stringValue: String) { self.stringValue = stringValue }
    var intValue: Int?
    init?(intValue: Int) { return nil }
}

struct Response<Payload: Decodable>: Decodable {
    let payload: Payload
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: AnyStringKey.self)
        guard let key = container.allKeys.first else {
            throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath,
                                                    debugDescription: "Missing payload key"))
        }
        self.payload = try container.decode(Payload.self, forKey: key)
    }
}

let user = try JSONDecoder().decode(Response<User>.self, from: json).payload

Возможно сделать это более продвинутым, иубедитесь, что ключ соответствует вашему ожиданию, но этого, вероятно, достаточно для вашей ситуации.

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

protocol LayerDecodable: Decodable {
    associatedtype CodingKeys: CodingKey
    init(from container: KeyedDecodingContainer<CodingKeys>) throws
}

extension LayerDecodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: AnyStringKey.self)
        guard let key = container.allKeys.first else {
            throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath,
                                                    debugDescription: "Missing payload key"))
        }
        try self.init(from: container.nestedContainer(keyedBy: CodingKeys.self, forKey: key))
    }
}

Но при этом вам необходимо реализовать декодирование вручную.

struct User: LayerDecodable {
    let email: String
    let token: String

    enum CodingKeys: CodingKey {
        case email, token
    }

    init(from container: KeyedDecodingContainer<CodingKeys>) throws {
        self.email = try container.decode(String.self, forKey: .email)
        self.token = try container.decode(String.self, forKey: .token)
    }
}

Преимущество заключается в том, что звонящий теперь чист:

let user = try JSONDecoder().decode(User.self, from: json)
...