Swift 5如何解码含API自动生成数字ID键的无顶层键JSON
Hey, let's work through your JSON decoding problem. The core issue here is that your current Rooms struct doesn't align with the dynamic structure of your JSON—where top-level keys are unpredictable room IDs, and their values are the room objects.
First, Let's Understand the JSON Structure
Your simplified JSON looks like this:
{ "1": { "name": "Tea room", "description": "A room for tea" }, "2": { "name": "Playground", "description": "Here you can play all day long" }, "3": { "name": "Kitchen", "description": "Hungry persons can go here" } }
This is essentially a dictionary where keys are integers (formatted as strings) and values are room objects. Your original Rooms struct expects fields like id and rooms, which don't exist in the JSON—hence the decoding failure.
Solution 1: Decode Directly to a Dictionary (Simplest Approach)
The quickest fix is to decode the JSON directly into a [Int: Room] dictionary. This lets you access each room by its ID immediately.
First, clean up your Room struct (you can omit the CodingKeys if property names match the JSON keys exactly):
struct Room: Decodable { let name: String let description: String }
Then perform the decoding:
do { let roomsDictionary = try JSONDecoder().decode([Int: Room].self, from: jsonResult) // Iterate through the dictionary to get IDs and rooms for (roomId, room) in roomsDictionary { print("Room ID: \(roomId), Name: \(room.name), Description: \(room.description)") } } catch { print("Decoding failed: \(error.localizedDescription)") }
Solution 2: Custom Rooms Struct with Custom Decoding
If you prefer to encapsulate the rooms in a custom struct (for better organization in your code), you can implement custom decoding logic to handle the dynamic keys.
First, define the Rooms struct with a custom init(from:) method:
struct Rooms: Decodable { // Store rooms as an array of tuples (ID + Room) for easy access let roomList: [(id: Int, details: Room)] init(from decoder: Decoder) throws { // Get the top-level keyed container let container = try decoder.container(keyedBy: DynamicCodingKeys.self) var rooms = [(Int, Room)]() // Iterate over all dynamic keys for key in container.allKeys { // Convert the string key to an integer ID guard let roomId = Int(key.stringValue) else { throw DecodingError.dataCorrupted( DecodingError.Context( codingPath: [key], debugDescription: "Room ID '\(key.stringValue)' is not a valid integer" ) ) } // Decode the Room object for this key let room = try container.decode(Room.self, forKey: key) rooms.append((roomId, room)) } self.roomList = rooms } // Custom coding keys to handle dynamic string keys private struct DynamicCodingKeys: CodingKey { let stringValue: String init?(stringValue: String) { self.stringValue = stringValue } let intValue: Int? init?(intValue: Int) { self.stringValue = String(intValue) self.intValue = intValue } } } // Reuse the same Room struct from Solution 1 struct Room: Decodable { let name: String let description: String }
Then decode using this struct:
do { let rooms = try JSONDecoder().decode(Rooms.self, from: jsonResult) for roomEntry in rooms.roomList { print("Room ID: \(roomEntry.id), Name: \(roomEntry.details.name)") } } catch { print("Decoding failed: \(error.localizedDescription)") }
Why Your Original Code Failed
Your original Rooms struct expected the JSON to have top-level fields id and rooms, but your actual JSON has dynamic keys (room IDs) as the top-level structure. The decoder couldn't find the fields your struct was looking for, leading to a decoding error.
Choose the solution that best fits your workflow: the dictionary approach is great for quick, straightforward use, while the custom struct adds structure for larger codebases.
内容的提问来源于stack exchange,提问作者zorci

