Skip to content

Instantly share code, notes, and snippets.

@nunogoncalves
Last active January 26, 2024 08:37
Show Gist options
  • Save nunogoncalves/4852077f4e576872f72b70d9e79942f3 to your computer and use it in GitHub Desktop.
Save nunogoncalves/4852077f4e576872f72b70d9e79942f3 to your computer and use it in GitHub Desktop.
Better Decoding Error Messages
import Foundation
enum BetterDecodingError: CustomStringConvertible {
case dataCorrupted(_ message: String)
case keyNotFound(_ message: String)
case typeMismatch(_ message: String)
case valueNotFound(_ message: String)
case any(_ error: Error)
init(with error: Error) {
guard let decodingError = error as? DecodingError else {
self = .any(error)
return
}
switch decodingError {
case let .dataCorrupted(context):
let debugDescription = (context.underlyingError as NSError?)?.userInfo["NSDebugDescription"] ?? ""
self = .dataCorrupted("Data corrupted. \(context.debugDescription) \(debugDescription)")
case let .keyNotFound(key, context):
self = .keyNotFound("Key not found. Expected -> \(key.stringValue) <- at: \(context.prettyPath())")
case let .typeMismatch(_, context):
self = .typeMismatch("Type mismatch. \(context.debugDescription), at: \(context.prettyPath())")
case let .valueNotFound(_, context):
self = .valueNotFound("Value not found. -> \(context.prettyPath()) <- \(context.debugDescription)")
}
}
var description: String {
switch self {
case let .dataCorrupted(message), let .keyNotFound(message), let .typeMismatch(message), let .valueNotFound(message):
return message
case let .any(error):
return error.localizedDescription
}
}
}
extension DecodingError.Context {
func prettyPath(separatedBy separator: String = ".") -> String {
codingPath.map { $0.stringValue }.joined(separator: ".")
}
}
let jsonKeyNotFound = """
{
"name": "pedro",
"age": 20,
"job": {
"description": "Enginner",
"yearsActive": 2,
"boss": {
"name": null,
"age": 20
}
}
}
"""
let invalidJson = """
{
"name: "pedro"
}
"""
let jsonValueNotFound = """
{
"name": "pedro",
"age": 20,
"job": {
"description": "Enginner",
"yearsActive": 2,
"boss": {
"name": null,
"age": 20
}
}
}
"""
let jsonValueTypeMismatch = """
{
"name": "pedro",
"age": 20,
"job": {
"description": "Enginner",
"yearsActive": 2,
"boss": {
"name": "Jose",
"age": "20"
}
}
}
"""
struct Boss: Decodable {
let name: String
let age: Int
}
struct Job: Decodable {
let description: String
let yearsActive: Int
let boss: Boss
}
struct Person: Decodable {
let name: String
let age: Int
let job: Job
}
[jsonValueTypeMismatch, invalidJson, jsonValueNotFound, jsonKeyNotFound].forEach { json in
do {
try JSONDecoder().decode(Person.self, from: json.data(using: .utf8)!)
} catch {
print(
BetterDecodingError(with: error),
"\n", "\n", "vs", "\n", "\n",
error,
"\n","\n", "----------------------------------", "\n"
)
}
}
// Type mismatch. Expected to decode Int but found a string/data instead., at: job.boss.age
//
// vs
//
// typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "job",
// intValue: nil), CodingKeys(stringValue: "boss", intValue: nil), CodingKeys(stringValue: "age",
// intValue: nil)], debugDescription: "Expected to decode Int but // found a string/data instead.",
// underlyingError: nil))
//
// ----------------------------------
//
// Data currupted. The given data was not valid JSON. No value for key in object around character 14.
//
// vs
//
// dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not
// valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "No value for key
// in object around character 14." UserInfo={NSDebugDescription=No // value for key in object around
// character 14.})))
//
// ----------------------------------
//
// Value not found. -> job.boss.name <- Expected String value but found null instead.
//
// vs
//
// valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "job",
// intValue: nil), CodingKeys(stringValue: "boss", intValue: nil), CodingKeys(stringValue: "name",
// intValue: nil)], debugDescription: "Expected String value but // found null instead.", underlyingError: nil))
//
// ----------------------------------
//
// Value not found. -> job.boss.name <- Expected String value but found null instead.
//
// vs
//
// valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "job",
// intValue: nil), CodingKeys(stringValue: "boss", intValue: nil), CodingKeys(stringValue: "name",
// intValue: nil)], debugDescription: "Expected String value but // found null instead.",
// underlyingError: nil))
//
// ----------------------------------
@onurhuseyincantay
Copy link

Nice one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment