CodableWrappers
Property Wrappers
Simplified Serialization withMove your Codable and (En/De)coder customization to annotations!
struct YourType: Codable {
@MillisecondsSince1970DateCoding
var millisecondsDate: Date
@Base64Coding
var someData: Data
@OmitCoding
var temporaryProperty
}
2.0's released! Release Notes
Installation
Swift Package Manager *Preferred*
URL:
https://github.com/GottaGetSwifty/CodableWrappers.git
Manifest:
dependencies: [
.package(url: "https://github.com/GottaGetSwifty/CodableWrappers.git", .upToNextMajor(from: "2.0.0" )),
]
CocoaPods
pod 'CodableWrappers', '~> 2.0.0'
Info
Available Property Wrappers
- @EncodeNulls *New in 2.0*
- Lossy Collections *New in 2.0*
- Empty Defaults *New in 2.0*
- Other Fallbacks *New in 2.0*
- @OmitCoding
- @Base64Coding
- @SecondsSince1970DateCoding
- @MillisecondsSince1970DateCoding
- @DateFormatterCoding<DateFormatterStaticCoder>
- @ISO8601DateCoding
- @ISO8601DateFormatterCoding<ISO8601DateFormatterStaticCoder>
- @NonConformingFloatCoding<ValueProvider>
- @NonConformingDoubleCoding<ValueProvider>
- Bool Coding
- Additional Customization
- Property Mutability
- Only Encoding or Decoding
Other Customization
- Property Mutability *New in 2.0*
- Optionals *New in 2.0*
- Only Encoding or Decoding
Additional Links
Advantages
- Declarative
- Extendable
- Declare once for all Encoders and Decoders. (e.g. JSONEncoder and PropertyListEncoder)
- Custom (de/en)coding without overriding
encode(to: Encoder)
orinit(with decoder)
for your whole Type - Varied (de/en)coding strategies allowed
- Cross Platform
Compatibility
2.x has a Swift 5.2 as it's minimum. 5.1 is available on 1.x
@EncodeNulls
For a Property that should encode null
for nil
values
struct MyType: Codable {
@EncodeNulls
var myText: String? // Will not be omitted when nil, e.g. will encode to `null` in JSON and `$null` in PLISTs
}
Lossy Collections
@LossyArrayDecoding
@LossyDictionaryDecoding
@LossySetDecoding
Filters null values during decoding without throwing an Error
private struct LossyCollectionModel: Codable, Equatable {
@LossyArrayDecoding
var array: [String] // Ignores null values without throwing an Error
@LossyDictionaryDecoding
var dictionary: [String:String] // Ignores null values without throwing an Error
@LossySetDecoding
var set: Set<String> // Ignores null values without throwing an Error
}
Empty Defaults
When you want to encode/decode an empty value rather than decoding nil or omitting encoding
struct MyType: Codable {
@FallbackEncoding<EmptyInt>
var int: Int? // will encode `0` when nil
@FallbackDecoding<EmptyString>
var string: String // will decode to "" when value was missing/nil
@FallbackCoding<EmptyArray>
var array: [Int]? // will encode/decode to [] when missing/nil
}
All Empty Values
EmptyBool
EmptyString
EmptyInt
EmptyInt16
EmptyInt32
EmptyInt64
EmptyInt8
EmptyUInt
EmptyUInt16
EmptyUInt32
EmptyUInt64
EmptyUInt8
EmptyCGFloat
EmptyDouble
EmptyFloat
EmptyFloat16
EmptyArray
EmptyDictionary
EmptySet
Empty
defaults are available for most typical Foundation Types
Other Fallbacks
Any other kind of default can be provided by a custom FallbackValueProvider
public struct DistantFutureDateProvider: FallbackValueProvider {
public static var defaultValue: Date { Date.distantFuture }
}
struct MyType: Codable {
@FallbackEncoding<DistantFutureDateProvider>
var updatedDate: Date?
}
@OmitCoding
For a Property you want to be ignore when (en/de)coding
struct MyType: Codable {
@OmitCoding
var myText: String? // Never encodes and ignores a value if one is in decoded data.
}
@Base64Coding
For a Data property that should be serialized to a Base64 encoded String
struct MyType: Codable {
@Base64Coding
var myData: Data // Now encodes to a Base64 String
}
@SecondsSince1970DateCoding
For a Date property that should be serialized to SecondsSince1970
struct MyType: Codable {
@SecondsSince1970DateCoding
var myDate: Date // Now encodes to SecondsSince1970
}
@MillisecondsSince1970DateCoding
For a Date property that should be serialized to MillisecondsSince1970
struct MyType: Codable {
@MillisecondsSince1970DateCoding
var myDate: Date // Now encodes to MillisecondsSince1970
}
@DateFormatterCoding<DateFormatterStaticCoder>
For other Date formats create a Type that adheres to the DateFormatterStaticCoder
Protocol and use the convenience @DateFormatterCoding
typealias
or @CodingUses<StaticCoder>
.
struct MyCustomDateCoder: DateFormatterStaticCoder {
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MM:dd:yy H:mm:ss"
return formatter
}()
}
struct MyType: Codable {
@DateFormatterCoding<MyCustomDateCoder>
var myDate: Date // Now encodes to the format: "MM:dd:yy H:mm:ss"
}
@ISO8601DateCoding
For a Date property that should be serialized using the ISO8601DateFormatter
struct MyType: Codable {
@ISO8601DateCoding
var myDate: Date // Now encodes to ISO8601
}
@ISO8601DateFormatterCoding<ISO8601DateFormatterStaticCoder>
For other Date formats create a Type that adheres to the ISO8601DateFormatterStaticCoder
Protocol and use the convenience @ISO8601DateFormatterCoding
typealias
or @CodingUses<StaticCoder>
.
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
struct MyCustomISO8601DateFormatter: ISO8601DateFormatterStaticCoder {
static let iso8601DateFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withDashSeparatorInDate]
return formatter
}()
}
struct MyType: Codable {
@ISO8601DateFormatterCoding<MyCustomISO8601DateFormatter>
var myDate: Date // Now encodes with MyCustomISO8601DateFormatter's formatter
}
@NonConformingFloatCoding<ValueProvider>
When using a non-conforming Float, create a Type that adheres to NonConformingDecimalValueProvider and use @NonConformingFloatCoding<NonConformingDecimalValueProvider>
struct MyNonConformingValueProvider: NonConformingDecimalValueProvider {
static var positiveInfinity: String = "100"
static var negativeInfinity: String = "-100"
static var nan: String = "-1"
}
struct MyType: Codable {
@NonConformingFloatCoding<MyNonConformingValueProvider>
var myFloat: Float // Now encodes with the MyNonConformingValueProvider values for infinity/NaN
}
@NonConformingDoubleCoding<ValueProvider>
When using a non-conforming Double, create a Type that adheres to NonConformingDecimalValueProvider and use @NonConformingDoubleCoding<NonConformingDecimalValueProvider>
struct MyNonConformingValueProvider: NonConformingDecimalValueProvider {
static var positiveInfinity: String = "100"
static var negativeInfinity: String = "-100"
static var nan: String = "-1"
}
struct MyType: Codable {
@NonConformingDoubleCoding<MyNonConformingValueProvider>
var myFloat: Float // Now encodes with the MyNonConformingValueProvider values for infinity/NaN
}
Bool Coding
Sometimes an API uses an Int
or String
for a booleans.
@BoolAsStringCoding
struct MyType: Codable {
@BoolAsStringCoding
var myBool: Bool // Now encodes/decodes as a String. `"true"` for `true` and `"false"` for `false`. (Values are lower-cased before decoding)
}
@BoolAsIntCoding
struct MyType: Codable {
@BoolAsIntCoding
var myBool: Bool // Now encodes/decodes as an Int. `1` for `true` and `0` for `false`.
}
Additional Customization
The architecture was built with extensibility in mind so Implementing your own custom coding is as simple as adhering to the StaticCoder
protocol
. You can then simply add @CodingUses<YourCustomCoder>
to your property, or create a typealias
to make it cleaner: typealias YourCustomCoding = CodingUses<YourCustomCoder>
In fact all the included Wrappers are built the same way!
Full Example
struct NanosecondsSince9170Coder: StaticCoder {
static func decode(from decoder: Decoder) throws -> Date {
let nanoSeconds = try Double(from: decoder)
let seconds = nanoSeconds * 0.000000001
return Date(secondsSince1970: seconds)
}
static func encode(value: Date, to encoder: Encoder) throws {
let nanoSeconds = value.secondsSince1970 / 0.000000001
return try nanoSeconds.encode(to: encoder)
}
}
// Approach 1: CustomCoding
struct MyType: Codable {
@CodingUses<NanosecondsSince9170Coder>
var myData: Date // Now uses the NanosecondsSince9170Coder for serialization
}
// Approach 2: CustomEncoding Property Wrapper typealias
typealias NanosecondsSince9170Coding = CodingUses<NanosecondsSince9170Coder>
struct MyType: Codable {
@NanosecondsSince9170Coding
var myData: Date // Now uses the NanosecondsSince9170Coder for serialization
}
Take a look at these other examples to see what else is possible.
Property Mutability
In 2.0 all wrappers are Mutable by default and can be made Immutable via Property Wrapper Composition
struct MyType: Codable {
@Immutable @SecondsSince1970DateCoding
var createdAt: Date
@SecondsSince1970DateCoding
var updatedAt: Date
mutating func update() {
createdAt = Date() // ERROR - Cannot assign to property: 'createdAt' is a get-only property
updatedAt = Date() // Works!
}
}
Optionals
2.0 introduces @OptionalCoding<StaticCodingWrapper>
to enable Optionals for a property.
struct MyType: Codable {
@SecondsSince1970DateCoding
var createdAt: Date
@OptionalCoding<SecondsSince1970DateCoding>
var updatedAt: Date?
}
Only Encoding or Decoding
Sometimes you are only able/wanting to implement Encoding or Decoding.
To enable this, (where practical/possible), all of the included Wrappers have Encoding and Decoding variants
Change Coder to Encoder/Decoder or Coding to Encoding/Decoding to implement only one E.g. @Base64Encoding
, @SecondsSince1970DateDecoding
, @EncodingUses<ACustomEncoder>
, etc.
struct MyType: Encodable {
@SecondsSince1970DateEncoding
var myDate: Date
}
struct MyType: Decodable {
@SecondsSince1970DateDecoding
var myDate: Date
}
Contributions
If there is a standard Serialization strategy that could be added feel free to open an issue requesting it and/or submit a pull request with the new option.