Swift自定义编码,用于将整数时间编码为漂亮的字符串。

huangapple go评论109阅读模式
英文:

Swift custom encode for encoding Int time like pretty string

问题

I have a custom object stored in a Struct:

  1. struct Event {
  2. let type: String
  3. let value: String
  4. let time: Int
  5. }

其中,time 是以毫秒为单位的 Unix 时间。在应用程序中,我有一个可滚动的文本字段,当我以 JSON 格式打印此事件时:

  1. {
  2. "type" : "rain",
  3. "value" : "0.5",
  4. "time" : 1681663944
  5. }

但是对于 time 参数,我想将 Unix 时间转换为人类可读的形式(即 16/04/2023),因此我需要自定义该参数的编码方式。问题是,所有我的对象都符合此协议,因此我可以免费使用CodabletoJsonString()方法。

  1. protocol BaseObject: Codable {
  2. }
  3. extension BaseObject {
  4. func toJsonString() -> String {
  5. let jsonEncoder = JSONEncoder()
  6. do {
  7. let jsonData = try jsonEncoder.encode(self)
  8. let json = String(data: jsonData, encoding: String.Encoding.utf8)
  9. return json ?? ""
  10. } catch {
  11. return ""
  12. }
  13. }
  14. }

因此,我不需要为每个对象单独编写 toJsonString() 方法。天真的解决方案应该是为每个对象编写自定义的 toJsonString(),但我有很多结构体,如果我找到了一个巧妙的方法来拦截 time 属性,我可以用这种类型覆盖应用程序中的所有属性的编码表示。

有什么想法吗?

谢谢。

编辑

目前,我找到了这个解决方案:

  1. func toJsonString() -> String {
  2. let jsonEncoder = JSONEncoder()
  3. do {
  4. let jsonData = try jsonEncoder.encode(self)
  5. var modData = Data()
  6. if var dict = jsonData.toDictionary() {
  7. dict.keys.filter{ $0.contains("time") }.forEach { key in
  8. if let t = dict[key] as? Int {
  9. dict[key] = t.toStringDate()
  10. }
  11. }
  12. modData = dict.toData()
  13. }
  14. let json = String(data: modData, encoding: String.Encoding.utf8)
  15. return json ?? ""
  16. } catch {
  17. return ""
  18. }
  19. }
英文:

I have a custom object stored in a Struct:

  1. struct Event {
  2. let type: String
  3. let value: String
  4. let time: Int
  5. }

where time is a UnixTime in millis. In the App I have a scrollable text field when I print this event in json format

  1. {
  2. "type" : "rain",
  3. "value" : "0.5",
  4. "time" : 1681663944
  5. }

but for time parameter I would like to convert the UnixTime in a human readable form (i.e. 16/04/2023), so I need to customize haw this parameter is encoded. The problem is that all my objects are conforms to this protocol, so I have the toJsonString() method for free, based on the power of Codable.

  1. protocol BaseObject: Codable {
  2. }
  3. extension BaseObject {
  4. func toJsonString() -> String {
  5. let jsonEncoder = JSONEncoder()
  6. do {
  7. let jsonData = try jsonEncoder.encode(self)
  8. let json = String(data: jsonData, encoding: String.Encoding.utf8)
  9. return json ?? ""
  10. } catch {
  11. return ""
  12. }
  13. }
  14. }

So, I don't have a toJsonString() method for each object separately. Naïve solution should be write a custom toJsonString() for each object, but I have a lot of struct and if I found a smart way to intercept time attributes I can override the encode representation of all attributes in the App with this type.

Ideas ?

Thanks.

EDIT

For now I found this solution:

  1. func toJsonString() -> String {
  2. let jsonEncoder = JSONEncoder()
  3. do {
  4. let jsonData = try jsonEncoder.encode(self)
  5. var modData = Data()
  6. if var dict = jsonData.toDictionary() {
  7. dict.keys.filter{ $0.contains("time") }.forEach { key in
  8. if let t = dict[key] as? Int {
  9. dict[key] = t.toStringDate()
  10. }
  11. }
  12. modData = dict.toData()
  13. }
  14. let json = String(data: modData, encoding: String.Encoding.utf8)
  15. return json ?? ""
  16. } catch {
  17. return ""
  18. }
  19. }

答案1

得分: 1

你可以使用属性包装器来为特定属性提供自定义编码行为:

  1. import Foundation
  2. @propertyWrapper
  3. struct PrettyEncodedDate {
  4. static let formatter = {
  5. let df = DateFormatter()
  6. // 自定义日期格式化程序
  7. df.dateStyle = .full
  8. return df
  9. }()
  10. let wrappedValue: Date
  11. }
  12. extension PrettyEncodedDate: Decodable {
  13. struct InvalidDateError: Error {
  14. let dateString: String
  15. }
  16. init(from decoder: Decoder) throws {
  17. let container = try decoder.singleValueContainer()
  18. let dateString = try container.decode(String.self)
  19. guard let date = Self.formatter.date(from: dateString) else {
  20. throw InvalidDateError(dateString: dateString)
  21. }
  22. self.init(wrappedValue: date)
  23. }
  24. }
  25. extension PrettyEncodedDate: Encodable {
  26. func encode(to encoder: Encoder) throws {
  27. var container = encoder.singleValueContainer()
  28. let formattedDate = Self.formatter.string(from: self.wrappedValue)
  29. try container.encode(formattedDate)
  30. }
  31. }

这是一个示例用法:

  1. struct Event: Codable {
  2. let type: String
  3. let value: String
  4. @PrettyEncodedDate var time: Date
  5. }
  6. let encoder = JSONEncoder()
  7. encoder.outputFormatting = .prettyPrinted
  8. let event = Event(type: "some type", value: "some value", time: Date.now)
  9. let prettyJSON = try encoder.encode(event)
  10. print(String(data: prettyJSON, encoding: .utf8)!)

这将打印:

  1. {
  2. "type" : "some type",
  3. "value" : "some value",
  4. "time" : "Sunday, April 16, 2023"
  5. }

这依赖于 time 是一个 Date(它可能应该是的)。如果必须保持为 Int(虽然我建议不要这样做),你可以调整属性包装器以更改当前的行为(String -> DateDate -> String)以添加额外的处理步骤(String -> Date -> IntInt -> Date -> String)。

英文:

You can use a property wrapper to hook in and give custom encoding behaviour for a specific property:

  1. import Foundation
  2. @propertyWrapper
  3. struct PrettyEncodedDate {
  4. static let formatter = {
  5. let df = DateFormatter()
  6. // Customize the date formatter however you'd like
  7. df.dateStyle = .full
  8. return df
  9. }()
  10. let wrappedValue: Date
  11. }
  12. extension PrettyEncodedDate: Decodable {
  13. struct InvalidDateError: Error {
  14. let dateString: String
  15. }
  16. init(from decoder: Decoder) throws {
  17. let container = try decoder.singleValueContainer()
  18. let dateString = try container.decode(String.self)
  19. guard let date = Self.formatter.date(from: dateString) else {
  20. throw InvalidDateError(dateString: dateString)
  21. }
  22. self.init(wrappedValue: date)
  23. }
  24. }
  25. extension PrettyEncodedDate: Encodable {
  26. func encode(to encoder: Encoder) throws {
  27. var container = encoder.singleValueContainer()
  28. let formattedDate = Self.formatter.string(from: self.wrappedValue)
  29. try container.encode(formattedDate)
  30. }
  31. }

Here's an example usage:

  1. struct Event: Codable {
  2. let type: String
  3. let value: String
  4. @PrettyEncodedDate var time: Date
  5. }
  6. let encoder = JSONEncoder()
  7. encoder.outputFormatting = .prettyPrinted
  8. let event = Event(type: "some type", value: "some value", time: Date.now)
  9. let prettyJSON = try encoder.encode(event)
  10. print(String(data: prettyJSON, encoding: .utf8)!)

Which prints:

  1. {
  2. "type" : "some type",
  3. "value" : "some value",
  4. "time" : "Sunday, April 16, 2023"
  5. }

This relies on time to be a Date (which it probably should be). If it must stay an Int (though I'd caution against that), you can tweak the property wrapper to change the current behaviour (String -> Date, Date -> String) to add an extra processing step (String -> Date -> Int, Int -> Date -> String).

huangapple
  • 本文由 发表于 2023年4月17日 01:02:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76029189.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定