Created
June 12, 2020 16:15
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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