(Code Review) What can I do better here?

I am wondering if there is a different approach I could take here aside from the nested do { try } catch { do { try } catch { do { try } catch { ... }}}}}} approach I am currently using.

Similarly, any comments or suggestions on the switch self { ... } block would be appreciated.

UniversalPayload is to encode and decode a JSON payload object which contains three properties op: Int, t: String?, s: Int?, which have a known type and structure, and one property d: JSONAble? which is known to conform to JSON but has an unknown structure otherwise.

Will someone please suggest improvements to the following code?

struct UniversalPayload: Codable {
    typealias GenericPayloadData = [String: PayloadDatum?]
    
    var op: Int
    var t: String?
    var s: Int?
    var d: GenericPayloadData?
    
    enum PayloadDatum: Codable {
        case array([PayloadDatum])
        case bool(Bool)
        case int(Int)
        case null(Bool)
        case object(GenericPayloadData)
        case string(String)
        
        init(from decoder: Decoder) throws {

            let container = try decoder.singleValueContainer()
            var value: PayloadDatum?

            // Pyramid of do do do do doooom
            do { value = try .bool(container.decode(Bool.self)) } catch {

                do { value = try .int(container.decode(Int.self)) } catch {

                    do { value = try .string(container.decode(String.self)) } catch {

                        do { value = try .array(container.decode([PayloadDatum].self)) } catch {

                            do { value = try .object(container.decode(GenericPayloadData.self)) } catch {
                                value = .null(container.decodeNil())
                            } // catch .object
                        } // catch .array
                    } // catch .string
                } // catch .int
            } // catch .bool
            self = value!
        } // init

        func encode(to encoder: Encoder) throws {

            var container = encoder.singleValueContainer()

            switch self {

            case .array(let arrayValue):
                try? container.encode(arrayValue)

            case .bool(let boolValue):
                try? container.encode(boolValue)

            case .int(let intValue):
                try? container.encode(intValue)

            case .object(let objectValue):
                try? container.encode(objectValue)

            case .string(let stringValue):
                try? container.encode(stringValue)

            case .null(let nullValue):
                if nullValue {
                    try? container.encodeNil()
                }
                else {
                    let context = EncodingError.Context(codingPath: encoder.codingPath,
                                                        debugDescription: "Unknown Type")
                    throw EncodingError.invalidValue(self, context )
                } // else
            } // switch
        } // encode
    } // PayloadDatum
} // UniversalPayload

I would try this kind of approach which has also the advantage of not forcing an unwrap on value.

if let value = try? .int(container.decode(Bool.self)) {
  self = value
} else if let value = try? .string(container.decode(Int.self)) {
  self = value
} else if let value = try? .string(container.decode(String.self)) {

.... the rest should be pretty clear ...

}
(Code Review) What can I do better here?
 
 
Q