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.
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 supported using .secondsSince1970 and .millisecondsSince1970 or use .custom to provide a closure for alternate coding.
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 types conformin to BinaryInteger (e.g. Int, UInt) 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")