A Swift library for serializing Codable
types to and from Any
and UserDefaults
.
RawRepresentable
types are encoded to their raw value:
// "fish"
let any = try KeyValueEncoder().encode(Food(rawValue: "fish"))
Collection
types are encoded to [Any]
:
// ["fish", "chips"]
let any = try KeyValueEncoder().encode(["fish", "chips"])
Structs and classes are encoded to [String: Any]
:
struct User: Codable {
var id: Int
var name: String
}
// ["id": 1, "name": "Herbert"]
let any = try KeyValueEncoder().encode(User(id: 1, name: "Herbert"))
Decode values from Any
:
let food = try KeyValueDecoder().decode(Food.self, from: "fish")
let meals = try KeyValueDecoder().decode([String].self, from: ["fish", "chips"])
let user = try KeyValueDecoder().decode(User.self, from: ["id": 1, "name": "Herbert"])
DecodingError
is thrown when decoding fails. Context
includes a keyPath to the failed property.
// throws DecodingError.typeMismatch 'Expected String at SELF[1], found Int'
let meals = try KeyValueDecoder().decode([String].self, from: ["fish", 1])
// throws DecodingError.valueNotFound 'Expected String at SELF[1].name, found nil'
let user = try KeyValueDecoder().decode(User.self, from: [["id": 1, "name": "Herbert"], ["id:" 2])
// throws DecodingError.typeMismatch 'Int at SELF[2], cannot be exactly represented by UInt8'
let ascii = try KeyValueDecoder().decode([UInt8].self, from: [10, 100, 1000])
The encoding of Date
can be adjusted by setting the strategy.
By default Date
instances are encoded by simply casting to Any
but this adjusted by setting the strategy.
The default strategy casts to Any
leaving the instance unchanged:
var encoder = KeyValueEncoder()
encoder.dateEncodingStrategy = .date
// Date()
let any = try encoder.encode(Date())
ISO8601 compatible strings can be used:
encoder.dateEncodingStrategy = .iso8601()
// "1970-01-01T00:00:00Z"
let any = try encoder.encode(Date(timeIntervalSince1970: 0))
Epochs are also supported using .secondsSince1970
and millisecondsSince1970
.
The encoding of Optional.none
can be adjusted by setting the strategy.
The default strategy preserves Optional.none
:
var encoder = KeyValueEncoder()
encoder.nilEncodingStrategy = .default
// [1, 2, nil, 3]
let any = try encoder.encode([1, 2, Int?.none, 3])
Compatibility with PropertyListEncoder
is preserved using a placeholder string:
encoder.nilEncodingStrategy = .stringNull
// [1, 2, "$null", 3]
let any = try encoder.encode([1, 2, Int?.none, 3])
Compatibility with JSONSerialization
is preserved using NSNull
:
encoder.nilEncodingStrategy = .nsNull
// [1, 2, NSNull(), 3]
let any = try encoder.encode([1, 2, Int?.none, 3])
Nil values can also be completely removed:
encoder.nilEncodingStrategy = .removed
// [1, 2, 3]
let any = try encoder.encode([1, 2, Int?.none, 3])
The decoding of BinaryInteger
types (Int
, UInt
etc) can be adjusted via intDecodingStrategy
.
The default strategy IntDecodingStrategy.exact
ensures the source value is exactly represented by the decoded type allowing floating point values with no fractional part to be decoded:
// [10, 20, -30, 50]
let values = try KeyValueDecoder().decode([Int8].self, from: [10, 20.0, -30.0, Int64(50)])
// throws DecodingError.typeMismatch because 1000 cannot be exactly represented by Int8
_ = try KeyValueDecoder().decode(Int8.self, from: 1000])
Values with a fractional part can also be decoded to integers by rounding with any FloatingPointRoundingRule
:
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .rounding(rule: .toNearestOrAwayFromZero)
// [10, -21, 50]
let values = try decoder.decode([Int].self, from: [10.1, -20.9, 50.00001]),
Values can also be clamped to the representable range:
var decoder = KeyValueDecoder()
decoder.intDecodingStrategy = .clamping(roundingRule: .toNearestOrAwayFromZero)
// [10, 21, 127, -128]
let values = try decoder.decode([Int8].self, from: [10, 20.5, 1000, -Double.infinity])
Keys can be encoded to snake_case by setting the strategy:
var encoder = KeyValueEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
// ["first_name": "fish", "surname": "chips"]
let dict = try encoder.encode(Person(firstName: "fish", surname: "chips))
And decoded from snake_case:
var decoder = KeyValueDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let person = try decoder.decode(Person.self, from: dict)
Encode and decode Codable
types with UserDefaults
:
try UserDefaults.standard.encode(
User(id: "1", name: "Herbert"),
forKey: "owner"
)
try UserDefaults.standard.encode(
URL(string: "fish.com"),
forKey: "url"
)
try UserDefaults.standard.encode(
Duration.nanoseconds(1),
forKey: "duration"
)
Values are persisted in a friendly representation of plist native types:
let defaults = UserDefaults.standard.dictionaryRepresentation()
[
"owner": ["id": 1, "name": "Herbert"],
"url": URL(string: "fish.com"),
"duration": [0, 1000000000]
]
Decode values from the defaults:
let owner = try UserDefaults.standard.decode(Person.self, forKey: "owner")
let url = try UserDefaults.standard.decode(URL.self, forKey: "url")
let duration = try UserDefaults.standard.decode(Duration.self, forKey: "duration")