Swift中能否复用结构体?相似JSON结构的便捷处理问询
Absolutely! Swift’s generics are perfect for this exact scenario—they let you reuse your response/result structures while adapting them to different data types like User or Room without duplicate declarations. Here’s how to refactor your code to make it flexible and reusable:
Step 1: Refactor to Generic Structures
First, we’ll update your Response and Result structs to use a generic placeholder for the dynamic array type. Since your Meta structure is identical across both JSON responses, we can keep it as-is. We’ll also add a small protocol to handle the changing array key (users vs rooms).
1.1 Define a Protocol for Item Key Lookup
This protocol lets each data type specify its corresponding JSON array key:
protocol JSONItemKeyProviding { static var itemKey: String { get } }
1.2 Keep the Shared Meta Struct
Your existing Meta works for both responses, so no changes needed:
struct Meta: Decodable { let total_data: Int let total_page: Int }
1.3 Generic Result Struct
This struct decodes the shared meta field and dynamically fetches the correct array using the generic type’s specified key:
struct Result<T: Decodable & JSONItemKeyProviding>: Decodable { let meta: Meta let items: [T] init(from decoder: Decoder) throws { // Decode the fixed meta field let container = try decoder.container(keyedBy: CodingKeys.self) meta = try container.decode(Meta.self, forKey: .meta) // Create a dynamic container to look up the array key let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) guard let itemKey = DynamicCodingKey(stringValue: T.itemKey) else { throw DecodingError.dataCorruptedError( forKey: .meta, in: container, debugDescription: "Failed to find item key: \(T.itemKey)" ) } // Decode the array using the dynamic key items = try dynamicContainer.decode([T].self, forKey: itemKey) } enum CodingKeys: String, CodingKey { case meta } // Helper for dynamic JSON keys private struct DynamicCodingKey: CodingKey { var stringValue: String var intValue: Int? init?(stringValue: String) { self.stringValue = stringValue } init?(intValue: Int) { self.intValue = intValue self.stringValue = "\(intValue)" } } }
1.4 Generic Response Struct
This wraps the generic Result and shared status field:
struct Response<T: Decodable & JSONItemKeyProviding>: Decodable { let results: Result<T> let status: Int }
Step 2: Update User and Add Room Structs
Make your existing User conform to the new protocol, then create a Room struct following the same pattern:
User Struct
struct User: Decodable, JSONItemKeyProviding { let avatar_url: String let created_at: String let updated_at: String let email: String let id: Int let name: String let username: String // Specify the JSON key for User arrays static var itemKey: String { "users" } }
Room Struct
struct Room: Decodable, JSONItemKeyProviding { // Add properties matching your Room JSON structure let id: Int let name: String let created_at: String let capacity: Int // Specify the JSON key for Room arrays static var itemKey: String { "rooms" } }
Step 3: Decode JSON with the Generic Structures
Now you can decode both user and room responses using the same Response struct:
Decode User Data
let userDecoder = JSONDecoder() do { let userResponse = try userDecoder.decode(Response<User>.self, from: userJSONData) let users = userResponse.results.items // Use your users array here } catch { print("User decoding error: \(error)") }
Decode Room Data
let roomDecoder = JSONDecoder() do { let roomResponse = try roomDecoder.decode(Response<Room>.self, from: roomJSONData) let rooms = roomResponse.results.items // Use your rooms array here } catch { print("Room decoding error: \(error)") }
Alternative Simplified Approach (for limited variants)
If you only have a few data types (like User and Room) and don’t need future scalability, you could use an enum for result content instead. However, the generic approach is cleaner for long-term maintainability:
enum ResultContent: Decodable { case users([User]) case rooms([Room]) init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let users = try container.decodeIfPresent([User].self, forKey: .users) { self = .users(users) } else if let rooms = try container.decodeIfPresent([Room].self, forKey: .rooms) { self = .rooms(rooms) } else { throw DecodingError.dataCorruptedError( in: container, debugDescription: "Unknown result content type" ) } } enum CodingKeys: String, CodingKey { case users, rooms } } struct Response: Decodable { let results: ResultWrapper let status: Int } struct ResultWrapper: Decodable { let meta: Meta let content: ResultContent init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) meta = try container.decode(Meta.self, forKey: .meta) content = try ResultContent(from: decoder) } enum CodingKeys: String, CodingKey { case meta } }
内容的提问来源于stack exchange,提问作者graydam

