public protocol ObjectDebugger: CustomStringConvertible { var description: String { get } } extension ObjectDebugger { public var description: String { var description = "\(type(of: self))(" let mirror = Mirror(reflecting: self) for (label, value) in mirror.children { if let label = label { description += "\(label): \(value), " } } // Remove the trailing comma and space description = String(description.dropLast(2)) description += ")" return description } private func prettyPrint(object: Any, indentation: String = "") -> String { var description = "\(type(of: object)) {" let mirror = Mirror(reflecting: object) for (label, value) in mirror.children { if let label = label { let childDescription: String switch value { case let nestedObject as CustomStringConvertible: childDescription = prettyPrint( object: nestedObject, indentation: "\(indentation) ") case let stringValue as String: childDescription = "\"\(stringValue)\"" case let floatValue as Float: childDescription = "\(floatValue)" case let intValue as Int: childDescription = "\(intValue)" case let optionalValue as CustomStringConvertible?: if let unwrapped = optionalValue { childDescription = "\(unwrapped)" } else { childDescription = "nil" } default: childDescription = "\(value)" } description += "\n\(indentation) \(label): \(childDescription)" } } description += "\n\(indentation)}" return description } }