A Swift library for serializing Codable types to and from Any and UserDefaults.
Encode and decode Codable types with UserDefaults:
tryUserDefaults.standard.encode(User(id:"1", name:"Herbert"), forKey:"owner")tryUserDefaults.standard.encode(URL(string:"fish.com"), forKey:"url")tryUserDefaults.standard.encode(Duration.nanoseconds(1), forKey:"duration")Values are persisted in a friendly representation of plist native types:
letdefaults=UserDefaults.standard.dictionaryRepresentation()["owner":["id":1,"name":"Herbert"],"url":URL(string:"fish.com"),"duration":[0,1000000000]]Decode values from the defaults:
letowner=tryUserDefaults.standard.decode(Person.self, forKey:"owner")leturl=tryUserDefaults.standard.decode(URL.self, forKey:"url")letduration=tryUserDefaults.standard.decode(Duration.self, forKey:"duration")All values are encoded and decoded via Any.
RawRepresentable types are encoded to their raw value:
// "fish"
letany=tryKeyValueEncoder().encode(Food(rawValue:"fish"))Collection types are encoded to [Any]:
// ["fish", "chips"]
letany=tryKeyValueEncoder().encode(["fish","chips"])Structs and classes are encoded to [String: Any]:
structUser:Codable{varid:Intvarname:String}
// ["id": 1, "name": "Herbert"]
letany=tryKeyValueEncoder().encode(User(id:1, name:"Herbert"))Decode values from Any:
letfood=tryKeyValueDecoder().decode(Food.self, from:"fish")letmeals=tryKeyValueDecoder().decode([String].self, from:["fish","chips"])letuser=tryKeyValueDecoder().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'
letmeals=tryKeyValueDecoder().decode([String].self, from:["fish",1])
// throws DecodingError.valueNotFound 'Expected String at SELF[1].name, found nil'
letuser=tryKeyValueDecoder().decode(User.self, from:[["id":1,"name":"Herbert"],["id:"2])
// throws DecodingError.typeMismatch 'Int at SELF[2], cannot be exactly represented by UInt8'
letascii=tryKeyValueDecoder().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:
varencoder=KeyValueEncoder()
encoder.dateEncodingStrategy =.date
// Date()
letany=try encoder.encode(Date())ISO8601 compatible strings can be used:
encoder.dateEncodingStrategy =.iso8601()
// "1970-01-01T00:00:00Z"
letany=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:
varencoder=KeyValueEncoder()
encoder.nilEncodingStrategy =.default
// [1, 2, nil, 3]
letany=try encoder.encode([1,2,Int?.none,3])Compatibility with PropertyListEncoder is preserved using a placeholder string:
encoder.nilEncodingStrategy =.stringNull
// [1, 2, "$null", 3]
letany=try encoder.encode([1,2,Int?.none,3])Compatibility with JSONSerialization is preserved using NSNull:
encoder.nilEncodingStrategy =.nsNull
// [1, 2, NSNull(), 3]
letany=try encoder.encode([1,2,Int?.none,3])Nil values can also be completely removed:
encoder.nilEncodingStrategy =.removed
// [1, 2, 3]
letany=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]
letvalues=tryKeyValueDecoder().decode([Int8].self, from:[10,20.0,-30.0,Int64(50)])
// throws DecodingError.typeMismatch because 1000 cannot be exactly represented by Int8
_ =tryKeyValueDecoder().decode(Int8.self, from:1000])Values with a fractional part can also be decoded to integers by rounding with any FloatingPointRoundingRule:
vardecoder=KeyValueDecoder()
decoder.intDecodingStrategy =.rounding(rule:.toNearestOrAwayFromZero)
// [10, -21, 50]
letvalues=try decoder.decode([Int].self, from:[10.1,-20.9,50.00001]),Values can also be clamped to the representable range:
vardecoder=KeyValueDecoder()
decoder.intDecodingStrategy =.clamping(roundingRule:.toNearestOrAwayFromZero)
// [10, 21, 127, -128]
letvalues=try decoder.decode([Int8].self, from:[10,20.5,1000,-Double.infinity])Keys can be encoded to snake_case by setting the strategy:
varencoder=KeyValueEncoder()
encoder.keyEncodingStrategy =.convertToSnakeCase
// ["first_name": "fish", "surname": "chips"]
letdict=try encoder.encode(Person(firstName:"fish", surname:"chips))And decoded from snake_case:
vardecoder=KeyValueDecoder()
decoder.keyDecodingStrategy =.convertFromSnakeCase
letperson=try decoder.decode(Person.self, from: dict)