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 Optional.none
can be adjusted by setting the strategy.
The default strategy preserves Optional.none
:
let 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
:
let 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:
let 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])
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")