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

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

Swift custom encode for encoding Int time like pretty string

问题

I have a custom object stored in a Struct:

struct Event {
   let type: String
   let value: String
   let time: Int
}

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

{
   "type" : "rain",
   "value" : "0.5",
   "time" : 1681663944
}

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

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 ""
        }
    }
}

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

有什么想法吗?

谢谢。

编辑

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

func toJsonString() -> String {
        let jsonEncoder = JSONEncoder()
        do {
            let jsonData = try jsonEncoder.encode(self)
            var modData = Data()
            
            if var dict = jsonData.toDictionary() {
                
                dict.keys.filter{ $0.contains("time") }.forEach { key in
                    if let t = dict[key] as? Int {
                        dict[key] = t.toStringDate()
                    }
                }
                
                modData = dict.toData()
            }
            
            let json = String(data: modData, encoding: String.Encoding.utf8)
            return json ?? ""
        } catch {
            return ""
        }
    }
英文:

I have a custom object stored in a Struct:

struct Event {
   let type: String
   let value: String
   let time: Int
}

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

{
   "type" : "rain",
   "value" : "0.5",
   "time" : 1681663944
}

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.

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 ""
        }
    }
}

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:

func toJsonString() -> String {
        let jsonEncoder = JSONEncoder()
        do {
            let jsonData = try jsonEncoder.encode(self)
            var modData = Data()
            
            if var dict = jsonData.toDictionary() {
                
                dict.keys.filter{ $0.contains("time") }.forEach { key in
                    if let t = dict[key] as? Int {
                        dict[key] = t.toStringDate()
                    }
                }
                
                modData = dict.toData()
            }
            
            let json = String(data: modData, encoding: String.Encoding.utf8)
            return json ?? ""
        } catch {
            return ""
        }
    }

答案1

得分: 1

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

import Foundation

@propertyWrapper
struct PrettyEncodedDate {
   static let formatter = {
      let df = DateFormatter()
      // 自定义日期格式化程序
      df.dateStyle = .full
      return df
   }()
   
   let wrappedValue: Date
}

extension PrettyEncodedDate: Decodable {
   struct InvalidDateError: Error {
      let dateString: String
   }
   
   init(from decoder: Decoder) throws {
      let container = try decoder.singleValueContainer()
      let dateString = try container.decode(String.self)
      guard let date = Self.formatter.date(from: dateString) else {
         throw InvalidDateError(dateString: dateString)
      }
      self.init(wrappedValue: date)
   }
}

extension PrettyEncodedDate: Encodable {
   func encode(to encoder: Encoder) throws {
      var container = encoder.singleValueContainer()
      let formattedDate = Self.formatter.string(from: self.wrappedValue)
      try container.encode(formattedDate)
   }
}

这是一个示例用法:

struct Event: Codable {
   let type: String
   let value: String
   @PrettyEncodedDate var time: Date
}


let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let event = Event(type: "some type", value: "some value", time: Date.now)
let prettyJSON = try encoder.encode(event)
print(String(data: prettyJSON, encoding: .utf8)!)

这将打印:

{
  "type" : "some type",
  "value" : "some value",
  "time" : "Sunday, April 16, 2023"
}

这依赖于 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:

import Foundation

@propertyWrapper
struct PrettyEncodedDate {
   static let formatter = {
      let df = DateFormatter()
      // Customize the date formatter however you'd like
      df.dateStyle = .full
      return df
   }()
   
   let wrappedValue: Date
}

extension PrettyEncodedDate: Decodable {
   struct InvalidDateError: Error {
      let dateString: String
   }
   
   init(from decoder: Decoder) throws {
      let container = try decoder.singleValueContainer()
      let dateString = try container.decode(String.self)
      guard let date = Self.formatter.date(from: dateString) else {
         throw InvalidDateError(dateString: dateString)
      }
      self.init(wrappedValue: date)
   }
}

extension PrettyEncodedDate: Encodable {
   func encode(to encoder: Encoder) throws {
      var container = encoder.singleValueContainer()
      let formattedDate = Self.formatter.string(from: self.wrappedValue)
      try container.encode(formattedDate)
   }
}

Here's an example usage:

struct Event: Codable {
   let type: String
   let value: String
   @PrettyEncodedDate var time: Date
}


let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

let event = Event(type: "some type", value: "some value", time: Date.now)
let prettyJSON = try encoder.encode(event)
print(String(data: prettyJSON, encoding: .utf8)!)

Which prints:

{
  "type" : "some type",
  "value" : "some value",
  "time" : "Sunday, April 16, 2023"
}

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:

确定