Search code examples
iosswiftswift4option-typecodable

How to do-catch error in init(from decoder:Decoder) from a Codable struct?


let jsonString = """
                    {
                        "name":1,
                        "gender":"male",
                    }
                    """

struct Person: Codable {
    var name: String
    var gender: String

    public init(from decoder: Decoder) throws {
        do {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            name = try container.decode(String.self, forKey: .name)
            gender = try container.decode(String.self, forKey: .gender)
        } catch {
            print("XXXXXX \(error)")
        }
    }
}

From the code above, it won't compile because it complains,

Return from initializer without initializing all stored properties.

I want to throw some errors but how do I do that without,

  1. making default value for every properties.
  2. making them all optional.

Solution

  • You don't need a do-catch in init(from decoder: Decoder) because it is already marked as throws. So just do:

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        gender = try container.decode(String.self, forKey: .gender)
    }
    

    Whatever does the decoding can use a do-catch to see any exception thrown inside the above init(from:) method, like in the following example:

    struct Person: Codable {
        var name: String
        var gender: String
    
        // Note: This is not a very good example because this init method
        // is not even necessary in this case, since the automatically-
        // synthesized `init(from:)` method does exactly the same thing. I've
        // left it here to illustrate that you don't need to have a `do-catch`
        // in this method and can instead just use `try`, since the method
        // is marked as `throws`.
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            name = try container.decode(String.self, forKey: .name)
            gender = try container.decode(String.self, forKey: .gender)
        }
    }
    
    class PersonDecoder {
        func person(decodedFrom data: Data) -> Person? {
            do {
                // The `JSONDecoder.decode(_:from:)` method calls
                // `Person.init(from:)`, which can throw, which is why
                // `JSONDecoder.decode(_:from:)` also throws.
                let person = try JSONDecoder().decode(Person.self, from: data)
                return person
            } catch {
                // Inspect any thrown errors here.
                print(error)
    
                return nil
            }
        }
    }
    
    let personData = Data("""
    {
        "name": 1,
        "gender": "male"
    }
    """.utf8)
    
    let personDecoder = PersonDecoder()
    
    // Prints: "The data couldn’t be read because it isn’t in the correct format."
    // and `person` is nil.
    let person = personDecoder.person(decodedFrom: personData)