Skip to content

Instantly share code, notes, and snippets.

@krzysztofzablocki
Last active August 22, 2021 12:09
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krzysztofzablocki/c566408283d623b0092eb8b3267348db to your computer and use it in GitHub Desktop.
Save krzysztofzablocki/c566408283d623b0092eb8b3267348db to your computer and use it in GitHub Desktop.
A good way to deal with mapping network data to your local models in a consistent way, This is how @noremac standardised it in Times codebase
import Combine
import Foundation
/// This protocols allows you to declare your type as having a distinct network
/// representation.
///
/// Rather than writing and maintaining a custom `Decodable` implementation for
/// your type, declare a brand new struct that exactly matches the expected
/// network response, and then write an initializer for your actual type that
/// accepts a `NetworkResponse`.
///
/// struct Article: NetworkResponseConvertible {
/// struct NetworkResponse: Decodable {
/// struct Headline: Decodable {
/// var default: String
/// }
///
/// var headline: Headline
/// }
///
/// var headline: String
///
/// init(networkResponse: NetworkResponse) {
/// self.headline = networkResponse.headline.default
/// }
/// }
public protocol NetworkResponseConvertible {
/// The decoder type. Defaults to `JSONDecoder`.
associatedtype Decoder: TopLevelDecoder = JSONDecoder
/// The type of your network response. Must be `Decodable`.
associatedtype NetworkResponse: Decodable
/// Initialize an instance with the given network response.
/// - Parameter networkResponse: The network response.
init(networkResponse: NetworkResponse)
}
public extension NetworkResponseConvertible {
/// Returns an instance of `Self` decoded from the given input.
/// - Parameters:
/// - input: The input (generally `Data`).
/// - decoder: The decoder.
/// - Throws: Decoding errors.
/// - Returns: An instance of `Self`.
static func decoded(from input: Decoder.Input, decoder: Decoder) throws -> Self {
Self(networkResponse: try decoder.decode(NetworkResponse.self, from: input))
}
}
public extension Publisher where Output == Data {
func networkDecode<Value, Decoder>(
type _: Value.Type,
decoder: Decoder
) -> AnyPublisher<Value, Error>
where Value: NetworkResponseConvertible, Decoder: TopLevelDecoder, Decoder.Input == Output
{
decode(type: Value.NetworkResponse.self, decoder: decoder)
.map(Value.init(networkResponse:))
.eraseToAnyPublisher()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment