You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:46:47