Swift: Why try? isn't good enough for JSON

I find myself writing code like this when handling JSON using Codable, because of the instances where the value of the same key might be of variable types:
    init(from decoder: Decoder) throws {
        do {
            let anim = try AppearAnimation(from: decoder)
            self = ComponentAnimation.appear(anim)
        }
        catch DecodingError.typeMismatch(_, _) { }
        catch { throw error }
        do {
            let anim = try FadeAnimation(from: decoder)
            self = ComponentAnimation.fade_in(anim)
        }
        catch DecodingError.typeMismatch(_, _) { }
        catch { throw error }
        do  {
            let anim = try MoveAnimation(from: decoder)
            self = ComponentAnimation.move_in(anim)
        }
        catch DecodingError.typeMismatch(_, _) { }
        catch { throw error }
        do  {
            let anim = try ScaleFadeAnimation(from: decoder)
            self = ComponentAnimation.scale_fade(anim)
        }
        catch {
            throw error
        }
    }
It could be abbreviated to
    init(from decoder: Decoder) throws {
        if let anim = try? AppearAnimation(from: decoder) {
            self = ComponentAnimation.appear(anim)
        }
        else if let anim = try? FadeAnimation(from: decoder) {
            self = ComponentAnimation.fade_in(anim)
        }
        else if let anim = try? MoveAnimation(from: decoder) {
            self = ComponentAnimation.move_in(anim)
        }
        else if let anim = try? ScaleFadeAnimation(from: decoder)
            self = ComponentAnimation.scale_fade(anim)
        }
        else {
        // create a default instance OR
        // throw your bespoke error
        }
    }
or even further using nil coalescing trickery but what is lost is the error. While errors can feel like a pain to work with at times, all those do/catch statements and try keywords, when handling json it's really helpful to know whether you are dealing with a type mismatch or corruption of the data. This is why in my first example I check for a type mismatch in the first instance, because this is the only reason the instantiation should fail. Any other reason (most likely corruption of the data) it makes no sense to carry on the attempts at instantiation.

If we get to the final type and still no luck with instantiation, the error that is thrown should tell us that it was a type mismatch, given that all other error types will have already been returned, and knowing it was a type mismatch also tells us that it failed to match all the other types as well.

In the second piece of code all failure is silent and we then would either need to decide what to do in terms of the choice between instantiation and throwing which the default decode method provides us with or go back and perform a do/catch on one of the types to generate an error, which all gets a bit messy, especially when we're trying to work with the Codable framework and want to pass on errors that it knows how to handle.

Comments