Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Created April 24, 2020 16:57
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IanKeen/3beaa677f0f2572ce685c887579cce8c to your computer and use it in GitHub Desktop.
Save IanKeen/3beaa677f0f2572ce685c887579cce8c to your computer and use it in GitHub Desktop.
PropertyWrapper: Convert to/from raw type during decoding/encoding
import Foundation
public protocol CodingStrategy {
associatedtype Converted
associatedtype RawValue: Codable
static func toRaw(_ value: Converted) throws -> RawValue
static func toValue(_ raw: RawValue) throws -> Converted
}
@propertyWrapper
public struct Convertible<T: CodingStrategy>: Codable {
public let rawValue: T.RawValue
public let wrappedValue: T.Converted
public init(from decoder: Decoder) throws {
try self.init(rawValue: T.RawValue(from: decoder))
}
public func encode(to encoder: Encoder) throws {
try rawValue.encode(to: encoder)
}
public init(wrappedValue value: T.Converted) {
self.wrappedValue = value
self.rawValue = try! T.toRaw(value)
}
public init(rawValue: T.RawValue) throws {
self.rawValue = rawValue
self.wrappedValue = try T.toValue(rawValue)
}
public var projectedValue: T.RawValue { return rawValue }
}
extension Convertible: Equatable where T.RawValue: Equatable, T.Converted: Equatable { }
extension Convertible: Hashable where T.RawValue: Hashable, T.Converted: Hashable { }
extension Optional: CodingStrategy where Wrapped: CodingStrategy {
public static func toRaw(_ value: Optional<Wrapped.Converted>) throws -> Optional<Wrapped.RawValue> {
return try value.map(Wrapped.toRaw)
}
public static func toValue(_ raw: Optional<Wrapped.RawValue>) throws -> Optional<Wrapped.Converted> {
return try raw.map(Wrapped.toValue)
}
}
extension KeyedDecodingContainer {
public func decode<T: CodingStrategy, U>(_ type: Convertible<T>.Type, forKey key: KeyedDecodingContainer.Key) throws -> Convertible<T> where T.RawValue == U? {
guard let value = try decodeIfPresent(T.RawValue.self, forKey: key) else { return try Convertible<T>(rawValue: nil) }
return try Convertible<T>(rawValue: value)
}
}
extension KeyedEncodingContainer {
public mutating func encode<T: CodingStrategy, U>(_ value: Convertible<T>, forKey key: KeyedEncodingContainer<K>.Key) throws where T.RawValue == U? {
guard let value = value.rawValue else { return }
try encode(value, forKey: key)
}
}
public struct Timestamp: CodingStrategy {
public static func toRaw(_ value: Date) throws -> TimeInterval {
return value.timeIntervalSince1970
}
public static func toValue(_ raw: TimeInterval) throws -> Date {
return Date(timeIntervalSince1970: raw)
}
}
@IanKeen
Copy link
Author

IanKeen commented Apr 24, 2020

Example:

{"value": 1587747480}
struct Foo: Codable {
   @Convertible<Timestamp> var value: Date
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment