如何在Codable自动解码时仅自定义Date字段的解码逻辑?
太懂这种痛点了——结构体字段一大堆,就因为一个Date格式不固定,总不能把所有字段的解码逻辑都手动写一遍吧?其实有两种完美的解决方案,既保留自动解码的便利,又能搞定格式多变的Date字段,看你的具体需求选就行:
情况1:所有Date字段都可能有多种格式
如果你的JSON里所有Date类型的字段格式都不固定,那直接给JSONDecoder设置自定义的日期解码策略就行,完全不用修改你的Person结构体:
// 初始化JSONDecoder let jsonDecoder = JSONDecoder() // 自定义日期解码策略,尝试多种格式 jsonDecoder.dateDecodingStrategy = .custom { decoder in // 获取单个值容器(Date在JSON里是字符串形式) let container = try decoder.singleValueContainer() let dateString = try container.decode(String.self) // 定义所有可能的日期格式,按你遇到的优先级排序 let possibleFormats = [ "yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss", "yyyy-MM-dd", "yyyy/MM/dd", "MM/dd/yyyy" ] let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") // 固定locale避免地区差异导致的解析失败 // 逐个尝试解析,找到匹配的格式就返回 for format in possibleFormats { formatter.dateFormat = format if let date = formatter.date(from: dateString) { return date } } // 所有格式都匹配失败时抛出错误 throw DecodingError.dataCorruptedError( in: container, debugDescription: "无法解析日期字符串:\(dateString),没有匹配的格式" ) } // 然后正常解码,和你原来的代码完全一样! let person = try jsonDecoder.decode(Person.self, from: data)
这种方法的好处是:你的Person结构体完全不用改动,所有字段自动解码,只需要在初始化JSONDecoder的时候加这段策略就行,代码侵入性极低,非常省心。
情况2:只有特定的Date字段格式不固定(其他Date字段格式正常)
如果只是Person里的某一个或几个Date字段格式多变,其他Date字段是标准格式,那可以自定义Person的init(from:)方法,但只手动处理那个特殊字段,其余字段交给自动合成的解码逻辑。
这里给你一个更巧妙的写法,避免手动写所有字段的解码代码:
首先,你的Person结构体保持原样:
struct Person: Codable { let name: String let age: Int let birthDate: Date // 这个是格式多变的字段 let registrationDate: Date // 这个是格式固定的字段 // 其他一大堆字段... let email: String let address: String }
然后给Person扩展自定义的解码初始化方法:
extension Person { // 定义一个私有镜像结构体,和Person字段完全一致,只是把格式多变的Date改成String private struct PrivatePerson: Codable { let name: String let age: Int let birthDate: String let registrationDate: Date let email: String let address: String // 其他所有字段和Person一一对应即可 } init(from decoder: Decoder) throws { // 先让系统自动解码PrivatePerson,所有字段都不用手动写解码逻辑 let privatePerson = try PrivatePerson(from: decoder) // 手动处理birthDate的多种格式 let possibleFormats = ["yyyy-MM-dd", "yyyy/MM/dd", "yyyy-MM-dd HH:mm:ss"] let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") var parsedBirthDate: Date? for format in possibleFormats { formatter.dateFormat = format if let date = formatter.date(from: privatePerson.birthDate) { parsedBirthDate = date break } } // 解析失败就抛出错误 guard let birthDate = parsedBirthDate else { throw DecodingError.dataCorruptedError( forKey: CodingKeys.birthDate, in: decoder.container(keyedBy: CodingKeys.self), debugDescription: "无法解析birthDate:\(privatePerson.birthDate),没有匹配的格式" ) } // 把PrivatePerson的字段赋值给Person,Xcode可以自动补全,非常方便 self.name = privatePerson.name self.age = privatePerson.age self.birthDate = birthDate self.registrationDate = privatePerson.registrationDate self.email = privatePerson.email self.address = privatePerson.address // 其他字段依次赋值即可 } }
这种方法的核心是用私有镜像结构体承接自动解码,只需要处理特殊的Date字段,其他字段直接赋值,不用手动写每个字段的container.decode(),大大减少了重复代码。
两种方法都能完美实现你的需求:其余字段自动解码,仅手动处理格式多变的Date字段。优先推荐第一种方法(自定义dateDecodingStrategy),因为代码侵入性最低;如果是只有个别Date字段特殊,再用第二种方法。
内容的提问来源于stack exchange,提问作者MegaManX
相关产品推荐
相关产品推荐

