@@ -2,84 +2,138 @@ import Foundation
22
33public typealias LosslessStringCodable = LosslessStringConvertible & Codable
44
5+ /// Provides an ordered list of types for decoding the lossless value, prioritizing the first type that successfully decodes as the produced value.
6+ ///
7+ /// `LosslessDecodingStrategy` provides a generic strategy type that the `LosslessValueCodable` property wrapper can use to provide
8+ /// the ordered list of decodable types in order to maximize preservation and robustness for the otherwise lossy data.
9+ public protocol LosslessDecodingStrategy {
10+ associatedtype Value : LosslessStringCodable
11+
12+ static var losslessDecodableTypes : [ ( Decoder ) -> LosslessStringCodable ? ] { get }
13+ }
14+
515/// Decodes Codable values into their respective preferred types.
616///
7- /// `@LosslessValue ` attempts to decode Codable types into their respective preferred types while preserving the data.
17+ /// `@LosslessValueCodable ` attempts to decode Codable types into their preferred order while preserving the data in the most lossless format .
818///
9- /// This is useful when data may return unpredictable values when a consumer is expecting a certain type. For instance,
10- /// if an API sends SKUs as either an `Int` or `String`, then a `@LosslessValue` can ensure the types are always decoded
11- /// as `String`s.
19+ /// The preferred type order is provided by a generic `LosslessDecodingStrategy` that provides an ordered list of `losslessDecodableTypes`.
1220@propertyWrapper
13- public struct LosslessValue < T : LosslessStringCodable > : Codable {
21+ public struct LosslessValueCodable < Strategy : LosslessDecodingStrategy > : Codable {
1422 private let type : LosslessStringCodable . Type
15-
16- public var wrappedValue : T
1723
18- public init ( wrappedValue: T ) {
24+ public var wrappedValue : Strategy . Value
25+
26+ public init ( wrappedValue: Strategy . Value ) {
1927 self . wrappedValue = wrappedValue
20- self . type = T . self
28+ self . type = Strategy . Value . self
2129 }
22-
30+
2331 public init ( from decoder: Decoder ) throws {
2432 do {
25- self . wrappedValue = try T . init ( from: decoder)
26- self . type = T . self
27-
33+ self . wrappedValue = try Strategy . Value. init ( from: decoder)
34+ self . type = Strategy . Value. self
2835 } catch let error {
29- func decode< T: LosslessStringCodable > ( _: T . Type ) -> ( Decoder ) -> LosslessStringCodable ? {
30- return { try ? T . init ( from: $0) }
31- }
32-
33- func decodeBoolFromNSNumber( ) -> ( Decoder ) -> LosslessStringCodable ? {
34- return { ( try ? Int . init ( from: $0) ) . flatMap { Bool ( exactly: NSNumber ( value: $0) ) } }
35- }
36-
37- let types : [ ( Decoder ) -> LosslessStringCodable ? ] = [
38- decode ( String . self) ,
39- decodeBoolFromNSNumber ( ) ,
40- decode ( Bool . self) ,
41- decode ( Int . self) ,
42- decode ( Int8 . self) ,
43- decode ( Int16 . self) ,
44- decode ( Int64 . self) ,
45- decode ( UInt . self) ,
46- decode ( UInt8 . self) ,
47- decode ( UInt16 . self) ,
48- decode ( UInt64 . self) ,
49- decode ( Double . self) ,
50- decode ( Float . self) ,
51- ]
52-
5336 guard
54- let rawValue = types . lazy. compactMap ( { $0 ( decoder) } ) . first,
55- let value = T . init ( " \( rawValue) " )
56- else { throw error }
57-
37+ let rawValue = Strategy . losslessDecodableTypes . lazy. compactMap ( { $0 ( decoder) } ) . first,
38+ let value = Strategy . Value . init ( " \( rawValue) " )
39+ else { throw error }
40+
5841 self . wrappedValue = value
5942 self . type = Swift . type ( of: rawValue)
6043 }
6144 }
62-
45+
6346 public func encode( to encoder: Encoder ) throws {
6447 let string = String ( describing: wrappedValue)
65-
48+
6649 guard let original = type. init ( string) else {
6750 let description = " Unable to encode ' \( wrappedValue) ' back to source type ' \( type) ' "
6851 throw EncodingError . invalidValue ( string, . init( codingPath: [ ] , debugDescription: description) )
6952 }
70-
53+
7154 try original. encode ( to: encoder)
7255 }
7356}
7457
75- extension LosslessValue : Equatable where T : Equatable {
76- public static func == ( lhs: LosslessValue < T > , rhs: LosslessValue < T > ) -> Bool {
58+ extension LosslessValueCodable : Equatable where Strategy . Value : Equatable {
59+ public static func == ( lhs: LosslessValueCodable < Strategy > , rhs: LosslessValueCodable < Strategy > ) -> Bool {
7760 return lhs. wrappedValue == rhs. wrappedValue
7861 }
7962}
8063
81- extension LosslessValue : Hashable where T : Hashable {
64+ extension LosslessValueCodable : Hashable where Strategy . Value : Hashable {
8265 public func hash( into hasher: inout Hasher ) {
8366 hasher. combine ( wrappedValue)
8467 }
8568}
69+
70+ public struct LosslessDefaultStrategy < Value: LosslessStringCodable > : LosslessDecodingStrategy {
71+ public static var losslessDecodableTypes : [ ( Decoder ) -> LosslessStringCodable ? ] {
72+ func decode< T: LosslessStringCodable > ( _: T . Type ) -> ( Decoder ) -> LosslessStringCodable ? {
73+ return { try ? T . init ( from: $0) }
74+ }
75+
76+ return [
77+ decode ( String . self) ,
78+ decode ( Bool . self) ,
79+ decode ( Int . self) ,
80+ decode ( Int8 . self) ,
81+ decode ( Int16 . self) ,
82+ decode ( Int64 . self) ,
83+ decode ( UInt . self) ,
84+ decode ( UInt8 . self) ,
85+ decode ( UInt16 . self) ,
86+ decode ( UInt64 . self) ,
87+ decode ( Double . self) ,
88+ decode ( Float . self) ,
89+ ]
90+ }
91+ }
92+
93+ public struct LosslessBooleanStrategy < Value: LosslessStringCodable > : LosslessDecodingStrategy {
94+ public static var losslessDecodableTypes : [ ( Decoder ) -> LosslessStringCodable ? ] {
95+ func decode< T: LosslessStringCodable > ( _: T . Type ) -> ( Decoder ) -> LosslessStringCodable ? {
96+ return { try ? T . init ( from: $0) }
97+ }
98+
99+ func decodeBoolFromNSNumber( ) -> ( Decoder ) -> LosslessStringCodable ? {
100+ return { ( try ? Int . init ( from: $0) ) . flatMap { Bool ( exactly: NSNumber ( value: $0) ) } }
101+ }
102+
103+ return [
104+ decode ( String . self) ,
105+ decodeBoolFromNSNumber ( ) ,
106+ decode ( Bool . self) ,
107+ decode ( Int . self) ,
108+ decode ( Int8 . self) ,
109+ decode ( Int16 . self) ,
110+ decode ( Int64 . self) ,
111+ decode ( UInt . self) ,
112+ decode ( UInt8 . self) ,
113+ decode ( UInt16 . self) ,
114+ decode ( UInt64 . self) ,
115+ decode ( Double . self) ,
116+ decode ( Float . self) ,
117+ ]
118+ }
119+ }
120+
121+ /// Decodes Codable values into their respective preferred types.
122+ ///
123+ /// `@LosslessValue` attempts to decode Codable types into their respective preferred types while preserving the data.
124+ ///
125+ /// This is useful when data may return unpredictable values when a consumer is expecting a certain type. For instance,
126+ /// if an API sends SKUs as either an `Int` or `String`, then a `@LosslessValue` can ensure the types are always decoded
127+ /// as `String`s.
128+ public typealias LosslessValue < T> = LosslessValueCodable < LosslessDefaultStrategy < T > > where T: LosslessStringCodable
129+
130+ /// Decodes Codable values into their respective preferred types.
131+ ///
132+ /// `@LosslessBoolValue` attempts to decode Codable types into their respective preferred types while preserving the data.
133+ ///
134+ /// - Note:
135+ ///
136+ /// This differs from `@LosslessValue` in that it strongly prefers to keep the boolean value above all else, and some integer values will be lossy. For instance,
137+ /// if you decode `{ "some_type": 1 }` then `some_type` will be `true` and not `1`. If you do not want this behavior then stick with `@LosslessValue` or create
138+ /// your own custom `LosslessDecodingStrategy` type.
139+ public typealias LosslessBoolValue < T> = LosslessValueCodable < LosslessBooleanStrategy < T > > where T: LosslessStringCodable
0 commit comments