Skip to content

Instantly share code, notes, and snippets.

@igorcferreira
Created June 12, 2020 16:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save igorcferreira/65bc1f156fb392968f9d2c0f96235228 to your computer and use it in GitHub Desktop.
Save igorcferreira/65bc1f156fb392968f9d2c0f96235228 to your computer and use it in GitHub Desktop.
Demonstrate the usage of protocol for an easier Generic JSONDecoder with Fallback values
/// One of the motivations for this gist is a protocol that receives a Generic value and needs to return a value that may or may not be an optional.
/// Something like:
///
/// protocol Parser {
/// static func parse<T: Decodable>(data: Data) -> T?
/// }
///
/// This causes the issue of having to check the response type, even when you don't want to accept a nil value (ever)
/// let response: String? = parser.parse(data: Data())
/// if response == nil {
/// //handle nil
/// } else {
/// //actual desired code
/// }
///
/// It would be better if the type can define when it wants to receive nil when the parse fails, and when it requires a full object:
let emptyData = Data()
//With the Optional extension, parses of Optional will return nil if failed
do {
let response = try FallbackJSONDecoder().decode(MessageResponse?.self, from: emptyData)
print("\(response?.message ?? "<nil>")") //This will print "<nil>"
} catch {
print("\(error)") //Never called
}
//With the MessageResponse definition, parses of the Type will return the default element
do {
let response = try FallbackJSONDecoder().decode(MessageResponse.self, from: emptyData)
print("\(response.message)") //This will print "<Empty message>"
} catch {
print("\(error)") //Never called
}
//If the type do not conforms to DecodingFallbackProtocol, the code throws the same way as JSONDecoder
do {
let response = try FallbackJSONDecoder().decode(String.self, from: emptyData)
print("\(response)") //Never called
} catch {
print("\(error)") //Print JSON parsing error "The given data was not valid JSON."
}
/// This can be achieved by an increment of JSONDecoder that uses a simple Protocol and some protocol extensions
/// to make a way where the type defines the fallback object or error:
protocol DecodingFallbackProtocol {
static func getDefaultWrapper() -> Self
}
extension Optional: DecodingFallbackProtocol {
static func getDefaultWrapper() -> Self {
return nil
}
}
private extension Decodable {
static func getFallback() -> Self? {
if let optional = Self.self as? DecodingFallbackProtocol.Type,
let response = optional.getDefaultWrapper() as? Self {
return response
} else {
return nil
}
}
}
open class FallbackJSONDecoder: JSONDecoder {
open override func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
do {
return try super.decode(T.self, from: data)
} catch (let error) {
if let response = T.getFallback() {
return response
}
throw error
}
}
}
struct MessageResponse: Decodable, DecodingFallbackProtocol {
static func getDefaultWrapper() -> MessageResponse {
return MessageResponse(message: "<Empty message>")
}
let message: String
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment