Swift中如何统一将Int类型Unix时间编码为可读字符串?
问题描述
现有存储自定义对象的结构体:
struct Event { let type: String let value: String let time: Int }
其中time为毫秒级Unix时间戳,默认转JSON后输出为数字格式:
{ "type" : "rain", "value" : "0.5", "time" : 1681663944000 }
需求是将所有结构体中名为(或包含)time的Int类型时间戳,统一转为16/04/2023这类人类可读的日期字符串。所有对象都遵循BaseObject协议,通过协议扩展获得基于Codable的toJsonString()方法:
protocol BaseObject: Codable { } extension BaseObject { func toJsonString() -> String { let jsonEncoder = JSONEncoder() do { let jsonData = try jsonEncoder.encode(self) let json = String(data: jsonData, encoding: String.Encoding.utf8) return json ?? "" } catch { return "" } } }
目前临时方案是先编码为JSON再转字典修改time字段,但存在性能和嵌套处理的问题,寻求更优实现思路。
更优实现思路
方案一:自定义Timestamp类型(类型安全,推荐)
定义专门的Timestamp类型封装毫秒时间戳,将日期转换逻辑完全封装在类型内部,实现自定义Codable逻辑。
// 自定义Timestamp类型,封装毫秒时间戳 struct Timestamp: Codable { let milliseconds: Int // 转换为指定格式的日期字符串 private func formattedDate() -> String { let date = Date(timeIntervalSince1970: TimeInterval(milliseconds) / 1000) let formatter = DateFormatter() formatter.dateFormat = "dd/MM/yyyy" // 按需设置时区,例如:formatter.timeZone = TimeZone(identifier: "UTC") return formatter.string(from: date) } // 自定义编码逻辑:输出日期字符串 func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(formattedDate()) } // 自定义解码逻辑:解析回毫秒时间戳(如果需要解码功能) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() milliseconds = try container.decode(Int.self) } // 便捷初始化器,直接传入毫秒数 init(_ milliseconds: Int) { self.milliseconds = milliseconds } }
修改原有结构体的time属性类型:
struct Event: BaseObject { let type: String let value: String let time: Timestamp // 替换原来的Int类型 }
使用效果:
let event = Event(type: "rain", value: "0.5", time: Timestamp(1681663944000)) print(event.toJsonString()) // 输出: {"type":"rain","value":"0.5","time":"16/04/2023"}
优点:
- 类型安全,避免普通Int被误识别为时间戳
- 编码逻辑封装在类型内部,无需修改原有
toJsonString()方法 - 自动支持嵌套对象中的
time属性处理
缺点:需要修改所有结构体中time属性的类型
方案二:自定义JSONEncoder编码策略(无需修改结构体,灵活)
通过扩展KeyedEncodingContainer,拦截所有属性名包含"time"的Int类型值,自动转换为日期字符串,同时通过userInfo传递日期格式配置。
// 定义用户信息键,用于传递日期格式 extension CodingUserInfoKey { static let timestampDateFormat = CodingUserInfoKey(rawValue: "timestampDateFormat")! } // 扩展KeyedEncodingContainer,处理含"time"的Int属性 extension KeyedEncodingContainer { mutating func encode(_ value: Int, forKey key: Key) throws { // 判断属性名是否包含"time",且存在日期格式配置 guard key.stringValue.contains("time"), let dateFormat = codingUserInfo[.timestampDateFormat] as? String else { // 不符合条件则按默认方式编码 try super.encode(value, forKey: key) return } // 将毫秒时间戳转为指定格式的日期字符串 let date = Date(timeIntervalSince1970: TimeInterval(value) / 1000) let formatter = DateFormatter() formatter.dateFormat = dateFormat // 按需设置时区,例如:formatter.timeZone = TimeZone(identifier: "Asia/Shanghai") // 编码为字符串 try encode(formatter.string(from: date), forKey: key) } } // 修改BaseObject扩展的toJsonString()方法,配置编码器 extension BaseObject { func toJsonString() -> String { let jsonEncoder = JSONEncoder() // 设置日期格式 jsonEncoder.userInfo[.timestampDateFormat] = "dd/MM/yyyy" do { let jsonData = try jsonEncoder.encode(self) return String(data: jsonData, encoding: .utf8) ?? "" } catch { return "" } } }
使用效果:原有结构体无需修改,直接调用toJsonString()即可自动转换time属性:
let event = Event(type: "rain", value: "0.5", time: 1681663944000) print(event.toJsonString()) // 输出: {"type":"rain","value":"0.5","time":"16/04/2023"}
优点:
- 无需修改任何结构体的属性类型
- 支持所有层级(包括嵌套对象)中属性名含"time"的Int值自动处理
- 日期格式可通过
userInfo灵活配置
缺点:
- 依赖属性名命名规则(需包含"time"),不匹配则无法处理
- 非时间戳的Int属性若命名含"time"会被错误转换(需规范命名)
临时方案的不足
你的临时方案先编码为JSON再转字典修改,存在以下问题:
- 性能损耗:JSON转字典再转JSON的过程会额外消耗CPU和内存,大量数据场景下更明显
- 嵌套处理失效:无法处理嵌套对象中的
time属性(比如结构体内部包含另一个带time属性的结构体) - 类型不安全:转字典时需要强制类型转换,容易出现意外错误
上述两个方案均直接在Codable编码流程中处理,性能更优,支持嵌套结构,逻辑更严谨。
内容的提问来源于stack exchange,提问作者Fry
相关产品推荐
相关产品推荐

