Make Complex Codable Serializate a breeze with declarative annotations!
@CustomCodable @SnakeCase
struct User: Codable {
let firstName: String
let lastName: String
@SecondsSince1970DateCoding
var joinDate: Date
@CustomCodingKey("data")
var imageData: Data
}
3.0 released! Release Notes
Full DocC documentation here thanks to the Swift Package Index!
URL:
https://github.com/GottaGetSwifty/CodableWrappers.git
Manifest:
dependencies: [
.package(url: "https://github.com/GottaGetSwifty/CodableWrappers.git", .upToNextMajor(from: "3.0.0" )),
]
pod 'CodableWrappers', '~> 3.0.0'
Prerequisite for customizing CodingKeys
@Codable
struct MyType: Codable {
}
Use a custom String value for a Property's CodingKey
@CustomCodable
struct YourType: Codable {
@CodingKey("your-Custom_naming")
let firstProperty: String // Coding key will be "your-Custom_naming"
}
Set a property's CodingKey with a custom value
@CustomCodable
struct YourType: Codable {
@CodingKeyPrefix("beta-")
let firstProperty: String // CodingKey will be "beta-firstProperty"
}
@CustomCodable @CodingKeyPrefix("beta-")
struct YourType: Codable {
let firstProperty: String // CodingKey will be "beta-firstProperty"
}
/// Add a Suffix a property's CodingKey or Type's CodingKeys with a custom value
@CustomCodable
struct YourType: Codable {
@CodingKeySuffix("-beta")
let firstProperty: String // CodingKey will be "firstProperty-beta"
}
@CustomCodable @CodingKeySuffix("-beta")
struct YourType: Codable {
let firstProperty: String // CodingKey will be "firstProperty-beta"
}
/// Make the CodingKey for a property or Type camelCase
@CustomCodable
struct YourType: Codable {
@CamelCase
let first-property: String // CodingKey will be "firstProperty"
}
@CustomCodable @CamelCase
struct YourType: Codable {
let first-property: String // CodingKey will be "firstProperty"
}
Make the CodingKey for a property or Type flatcase
@CustomCodable
struct YourType: Codable {
@FlatCase
let firstProperty: String // CodingKey will be "firstproperty"
}
@CustomCodable @FlatCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "firstproperty"
}
Make the CodingKey for a property or Type PascalCase
@CustomCodable
struct YourType: Codable {
@PascalCase
let firstProperty: String // CodingKey will be "FirstProperty"
}
@CustomCodable @PascalCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "FirstProperty"
}
Make the CodingKey for a property or Type UPPERCASE
@CustomCodable
struct YourType: Codable {
@UpperCase
let firstProperty: String // CodingKey will be "FIRSTPROPERTY"
}
@CustomCodable @UpperCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "FIRSTPROPERTY"
}
Make the CodingKey for a property or Type snake_case
@CustomCodable
struct YourType: Codable {
@SnakeCase
let firstProperty: String // CodingKey will be "first_property"
}
@CustomCodable @SnakeCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "first_property"
}
Make the CodingKey for a property or Type camel_Snake_Case
@CustomCodable
struct YourType: Codable {
@CamelSnakeCase
let firstProperty: String // CodingKey will be "first_Property"
}
@CustomCodable @CamelSnakeCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "first_Property"
}
Make the CodingKey for a property or Type Pascal_Snake_Case
@CustomCodable
struct YourType: Codable {
@PascalSnakeCase
let firstProperty: String // CodingKey will be "First_Property"
}
@CustomCodable @PascalSnakeCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "First_Property"
}
Make the CodingKey for a property or Type SCREAMING_SNAKE_CASE
@CustomCodable
struct YourType: Codable {
@ScreamingSnakeCase
let firstProperty: String // CodingKey will be "FIRST_PROPERTY"
}
@CustomCodable @ScreamingSnakeCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "FIRST_PROPERTY"
}
Make the CodingKey for a property or Type kebab-case
@CustomCodable
struct YourType: Codable {
@KebabCase
let firstProperty: String // CodingKey will be "first-property"
}
@CustomCodable @KebabCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "first-property"
}
Make the CodingKey for a property or Type camel-Kebab-Case
@CustomCodable
struct YourType: Codable {
@CamelKebabCase
let firstProperty: String // CodingKey will be "first-Property"
}
@CustomCodable @CamelKebabCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "first-Property"
}
Make the CodingKey for a property or Type Pascal-Kebab-Case
@CustomCodable
struct YourType: Codable {
@PascalKebabCase
let firstProperty: String // CodingKey will be "First-Property"
}
@CustomCodable @PascalKebabCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "First-Property"
}
Make the CodingKey for a property or Type SCREAMING-KEBAB-CASE
@CustomCodable
struct YourType: Codable {
@ScreamingKebabCase
let firstProperty: String // CodingKey will be "FIRST-PROPERTY"
}
@CustomCodable @ScreamingKebabCase
struct YourType: Codable {
let firstProperty: String // CodingKey will be "FIRST-PROPERTY"
}
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
}
@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
}
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
}
Empty
defaults are available for most typical Foundation Types
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?
}
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.
}
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
}
For a Date property that should be serialized to SecondsSince1970
struct MyType: Codable {
@SecondsSince1970DateCoding
var myDate: Date // Now encodes to SecondsSince1970
}
For a Date property that should be serialized to MillisecondsSince1970
struct MyType: Codable {
@MillisecondsSince1970DateCoding
var myDate: Date // Now encodes to MillisecondsSince1970
}
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"
}
For a Date property that should be serialized using the ISO8601DateFormatter
struct MyType: Codable {
@ISO8601DateCoding
var myDate: Date // Now encodes to ISO8601
}
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
}
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
}
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
}
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`.
}
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!
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.
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!
}
}
2.0 introduces @OptionalCoding<StaticCodingWrapper>
to enable Optionals for a property.
struct MyType: Codable {
@SecondsSince1970DateCoding
var createdAt: Date
@OptionalCoding<SecondsSince1970DateCoding>
var updatedAt: Date?
}
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
}
If there is a standard Serialization or Coding Key strategy that could be added feel free to open an issue requesting it and/or submit a pull request with the new option.