Swift中JSON反序列化失败问题排查求助
Hey there, let's dig into your JSON deserialization issue with those Swift structs generated by json4swift. I spotted a couple of key problems in the generated code that are causing the errors—let's break it down step by step.
问题背景
I've verified the Room object list JSON response via Postman (example below), used json4swift.com to generate DTO structs like Room, Location, Account, and integrated them into my Swift project, but now I'm getting JSON deserialization errors. Need help troubleshooting.
示例JSON响应
{ "id": "506e9a1d-cded-4853-a9df-7f8a57d6550d", "name": "TeleTwo", "shortName": "2", "displayName": "2", "inService": true, "managed": true, "capacity": 2, "schedulingOption": "VC Scheduling", "location": { "id": "50b07258-2668-416e-aef4-a8a48e9e7389", "name": "Telepresence Test", "shortName": "Telepresence Test", "displayName": "Telepresence Test", "city": "Nash", "country": "United States", "account": { "id": "509788c6-197f-40f1-a7f7-274a23af9062", "name": "Development", "shortName": "Dev", "displayName": "Dev", "active": true, "exchangeEnabled": false, "tmsEnabled": false, "commProxyEnabled": true, "services": [ "Premier", "VMR_Pool_Static", "VMR" ], "logo": "/provisioning/image/2472" }, "latitude": 45.786368, "longitude": -78.505822, "active": false, "summary": null }, "summary": { "totalDevices": 2 }, "vip": false }
生成的DTO结构体
Room.swift
struct Room : Codable { let id : String? let name : String? let shortName : String? let displayName : String? let inService : Bool? let managed : Bool? let capacity : Int? let schedulingOption : String? let location : Location? let summary : RoomSummary? let vip : Bool? enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case shortName = "shortName" case displayName = "displayName" case inService = "inService" case managed = "managed" case capacity = "capacity" case schedulingOption = "schedulingOption" case location case summary case vip = "vip" } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) shortName = try values.decodeIfPresent(String.self, forKey: .shortName) displayName = try values.decodeIfPresent(String.self, forKey: .displayName) inService = try values.decodeIfPresent(Bool.self, forKey: .inService) managed = try values.decodeIfPresent(Bool.self, forKey: .managed) capacity = try values.decodeIfPresent(Int.self, forKey: .capacity) schedulingOption = try values.decodeIfPresent(String.self, forKey: .schedulingOption) location = try Location(from: decoder) summary = try RoomSummary(from: decoder) vip = try values.decodeIfPresent(Bool.self, forKey: .vip) } }
Location.swift
struct Location : Codable { let id : String? let name : String? let shortName : String? let displayName : String? let city : String? let country : String? let account : Account? let latitude : Double? let longitude : Double? let active : Bool? let summary : LocationSummary? enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case shortName = "shortName" case displayName = "displayName" case city = "city" case country = "country" case account case latitude = "latitude" case longitude = "longitude" case active = "active" case summary } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) shortName = try values.decodeIfPresent(String.self, forKey: .shortName) displayName = try values.decodeIfPresent(String.self, forKey: .displayName) city = try values.decodeIfPresent(String.self, forKey: .city) country = try values.decodeIfPresent(String.self, forKey: .country) account = try Account(from: decoder) latitude = try values.decodeIfPresent(Double.self, forKey: .latitude) longitude = try values.decodeIfPresent(Double.self, forKey: .longitude) active = try values.decodeIfPresent(Bool.self, forKey: .active) summary = try LocationSummary(from: decoder) } }
Account.swift(代码截断)
struct Account : Codable { let id : String? let name : String! let shortName : String! let displayName : String! let active : Bool! let exchangeEnabled : Bool! let tmsEnabled : Bool! let commProxyEnabled : Bool! let services : [String]! enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case shortName = "shortName" case displayName = "displayName" case active = "active" case exchangeEnabled = "exchangeEnabled" case tmsEnabled = "tmsEnabled" case commProxyEnabled = "commProxyEnabled" case services = "services" } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) shortName = try values.decodeIfPresent(String.self, forKey: .shortName) displayName = try values.decodeIfPresent(String.self, forKey: .displayName) active = try values.decodeIfPre...
核心错误原因
The main issue is how the generated code handles nested objects in the init(from decoder: Decoder) methods:
- In
Room, when decodinglocationandsummary, it callstry Location(from: decoder)directly. This tells the decoder to look for Location's fields at the top level of the JSON, not inside thelocationkey of the Room object. Same problem withsummary. - In
Location, decodingaccountandsummaryuses the same wrong approach—trying to decode Account from the top-level decoder instead of the nestedaccountkey. - Also,
Accountuses force-unwrapped optional types (!), which will crash if any of those fields are missing or null in the JSON.
修复方案
1. Fix nested object decoding
For nested objects, you need to decode them from the current struct's keyed container, not the top-level decoder. Here's the corrected Room initializer:
init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) shortName = try values.decodeIfPresent(String.self, forKey: .shortName) displayName = try values.decodeIfPresent(String.self, forKey: .displayName) inService = try values.decodeIfPresent(Bool.self, forKey: .inService) managed = try values.decodeIfPresent(Bool.self, forKey: .managed) capacity = try values.decodeIfPresent(Int.self, forKey: .capacity) schedulingOption = try values.decodeIfPresent(String.self, forKey: .schedulingOption) // Correct: decode Location from the "location" key in Room's container location = try values.decodeIfPresent(Location.self, forKey: .location) // Correct: decode RoomSummary from the "summary" key summary = try values.decodeIfPresent(RoomSummary.self, forKey: .summary) vip = try values.decodeIfPresent(Bool.self, forKey: .vip) }
Do the same for Location's initializer:
init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) shortName = try values.decodeIfPresent(String.self, forKey: .shortName) displayName = try values.decodeIfPresent(String.self, forKey: .displayName) city = try values.decodeIfPresent(String.self, forKey: .city) country = try values.decodeIfPresent(String.self, forKey: .country) // Correct: decode Account from the "account" key in Location's container account = try values.decodeIfPresent(Account.self, forKey: .account) latitude = try values.decodeIfPresent(Double.self, forKey: .latitude) longitude = try values.decodeIfPresent(Double.self, forKey: .longitude) active = try values.decodeIfPresent(Bool.self, forKey: .active) summary = try values.decodeIfPresent(LocationSummary.self, forKey: .summary) }
2. Replace force-unwrapped types with optionals
Force-unwrapped (!) properties are risky—if the JSON has a null or missing value, your app will crash. Switch to optional types (?) instead, or use decode() instead of decodeIfPresent() if you're certain the field will always exist:
struct Account : Codable { let id : String? let name : String? let shortName : String? let displayName : String? let active : Bool? let exchangeEnabled : Bool? let tmsEnabled : Bool? let commProxyEnabled : Bool? let services : [String]? let logo : String? // Don't forget the "logo" field from your JSON! enum CodingKeys: String, CodingKey { case id = "id" case name = "name" case shortName = "shortName" case displayName = "displayName" case active = "active" case exchangeEnabled = "exchangeEnabled" case tmsEnabled = "tmsEnabled" case commProxyEnabled = "commProxyEnabled" case services = "services" case logo = "logo" // Add this key too } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) id = try values.decodeIfPresent(String.self, forKey: .id) name = try values.decodeIfPresent(String.self, forKey: .name) shortName = try values.decodeIfPresent(String.self, forKey: .shortName) displayName = try values.decodeIfPresent(String.self, forKey: .displayName) active = try values.decodeIfPresent(Bool.self, forKey: .active) exchangeEnabled = try values.decodeIfPresent(Bool.self, forKey: .exchangeEnabled) tmsEnabled = try values.decodeIfPresent(Bool.self, forKey: .tmsEnabled) commProxyEnabled = try values.decodeIfPresent(Bool.self, forKey: .commProxyEnabled) services = try values.decodeIfPresent([String].self, forKey: .services) logo = try values.decodeIfPresent(String.self, forKey: .logo) // Add this line } }
3. Add missing summary structs
Your JSON includes summary fields for both Room and Location, so you need to define those structs:
struct RoomSummary: Codable { let totalDevices: Int? } struct LocationSummary: Codable { // Add fields here if your API ever returns non-null summary data for Location }
Test the fix
Once you've made these changes, test deserialization with this code:
guard let jsonData = yourJsonString.data(using: .utf8) else { print("Invalid JSON string") return } do { let room = try JSONDecoder().decode(Room.self, from: jsonData) print("Successfully decoded room: \(room.name ?? "Unnamed")") } catch { print("Deserialization error: \(error.localizedDescription)") print("Full error details: \(error)") }
内容的提问来源于stack exchange,提问作者owen gerig

