Skip to content

Instantly share code, notes, and snippets.

@IanKeen
Last active May 22, 2024 20:06
Show Gist options
  • Save IanKeen/26605d6b7bb4fa97100e45f7098d84d2 to your computer and use it in GitHub Desktop.
Save IanKeen/26605d6b7bb4fa97100e45f7098d84d2 to your computer and use it in GitHub Desktop.
PropertyWrapper: LosslessCodable attempts to brute force convert the incoming value to the required type - the underlying type is maintained and used when encoding the value back
public typealias LosslessStringCodable = LosslessStringConvertible & Codable
@propertyWrapper
public struct LosslessCodable<Value: LosslessStringCodable>: Codable {
private let type: LosslessStringCodable.Type
public var wrappedValue: Value
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
self.type = Value.self
}
public init(from decoder: Decoder) throws {
do {
self.wrappedValue = try Value.init(from: decoder)
self.type = Value.self
} catch let error {
func decode<T: LosslessStringCodable>(_: T.Type) -> (Decoder) -> LosslessStringCodable? {
return { try? T.init(from: $0) }
}
let types: [(Decoder) -> LosslessStringCodable?] = [
decode(String.self),
decode(Bool.self),
decode(Int.self),
decode(Int8.self),
decode(Int16.self),
decode(Int64.self),
decode(UInt.self),
decode(UInt8.self),
decode(UInt16.self),
decode(UInt64.self),
decode(Double.self),
decode(Float.self),
]
guard
let rawValue = types.lazy.compactMap({ $0(decoder) }).first,
let value = Value.init("\(rawValue)")
else { throw error }
self.wrappedValue = value
self.type = Swift.type(of: rawValue)
}
}
public func encode(to encoder: Encoder) throws {
let string = String(describing: wrappedValue)
guard let original = type.init(string) else {
let description = "Unable to encode '\(wrappedValue)' back to source type '\(type)'"
throw EncodingError.invalidValue(string, .init(codingPath: [], debugDescription: description))
}
try original.encode(to: encoder)
}
}
extension LosslessCodable: Equatable where Value: Equatable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.wrappedValue == rhs.wrappedValue
}
}
extension Optional: CustomStringConvertible where Wrapped: CustomStringConvertible {
public var description: String {
return self?.description ?? ""
}
}
extension Optional: LosslessStringConvertible where Wrapped: LosslessStringConvertible {
public init?(_ description: String) {
self = Wrapped(description)
}
}
@IanKeen
Copy link
Author

IanKeen commented May 29, 2020

Example:

let json = """
{"value": "42"}
"""
struct Foo: Codable {
    @LosslessCodable var value: Int
}

Decoding would convert the String to Int
Encoding would convert the Int back to String

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