Support serializing and deserializing nested custom types (#118)
This commit is contained in:
parent
b23fb7b057
commit
be7e30ba36
|
@ -167,18 +167,20 @@ extension Defaults.Serializable {
|
||||||
return Value.toValue(anyObject)
|
return Value.toValue(anyObject)
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
static func toValue(_ anyObject: Any) -> Self? {
|
static func toValue<T: Defaults.Serializable>(_ anyObject: Any, type: T.Type = Self.self) -> T? {
|
||||||
// Return directly if `anyObject` can cast to Value, since it means `Value` is a natively supported type.
|
|
||||||
if
|
if
|
||||||
isNativelySupportedType,
|
T.isNativelySupportedType,
|
||||||
let anyObject = anyObject as? Self
|
let anyObject = anyObject as? T
|
||||||
{
|
{
|
||||||
return anyObject
|
return anyObject
|
||||||
} else if let value = bridge.deserialize(anyObject as? Serializable) {
|
|
||||||
return value as? Self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
guard let nextType = T.Serializable.self as? any Defaults.Serializable.Type else {
|
||||||
|
// This is a special case for the types which do not conform to `Defaults.Serializable` (for example, `Any`).
|
||||||
|
return T.bridge.deserialize(anyObject as? T.Serializable) as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
return T.bridge.deserialize(toValue(anyObject, type: nextType) as? T.Serializable) as? T
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,14 +192,20 @@ extension Defaults.Serializable {
|
||||||
set(Value.toSerialize(value), forKey: key)
|
set(Value.toSerialize(value), forKey: key)
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
static func toSerializable(_ value: Self) -> Any? {
|
static func toSerializable<T: Defaults.Serializable>(_ value: T) -> Any? {
|
||||||
// Return directly if `Self` is a natively supported type, since it does not need serialization.
|
if T.isNativelySupportedType {
|
||||||
if isNativelySupportedType {
|
|
||||||
return value
|
return value
|
||||||
} else if let serialized = bridge.serialize(value as? Value) {
|
}
|
||||||
|
|
||||||
|
guard let serialized = T.bridge.serialize(value as? T.Value) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let next = serialized as? any Defaults.Serializable else {
|
||||||
|
// This is a special case for the types which do not conform to `Defaults.Serializable` (for example, `Any`).
|
||||||
return serialized
|
return serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return toSerializable(next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,57 @@ public final class DefaultsUserBridge: Defaults.Bridge {
|
||||||
|
|
||||||
private let fixtureCustomBridge = User(username: "hank121314", password: "123456")
|
private let fixtureCustomBridge = User(username: "hank121314", password: "123456")
|
||||||
|
|
||||||
|
struct PlainHourMinuteTimeRange: Hashable, Codable {
|
||||||
|
var start: PlainHourMinuteTime
|
||||||
|
var end: PlainHourMinuteTime
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlainHourMinuteTimeRange: Defaults.Serializable {
|
||||||
|
struct Bridge: Defaults.Bridge {
|
||||||
|
typealias Value = PlainHourMinuteTimeRange
|
||||||
|
typealias Serializable = [PlainHourMinuteTime]
|
||||||
|
|
||||||
|
public func serialize(_ value: Value?) -> Serializable? {
|
||||||
|
guard let value = value else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return [value.start, value.end]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func deserialize(_ object: Serializable?) -> Value? {
|
||||||
|
guard
|
||||||
|
let array = object,
|
||||||
|
let start = array[safe: 0],
|
||||||
|
let end = array[safe: 1]
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return .init(start: start, end: end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static let bridge = Bridge()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlainHourMinuteTime: Hashable, Codable, Defaults.Serializable {
|
||||||
|
var hour: Int
|
||||||
|
var minute: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Collection {
|
||||||
|
subscript(safe index: Index) -> Element? {
|
||||||
|
indices.contains(index) ? self[index] : nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Defaults.Keys {
|
extension Defaults.Keys {
|
||||||
fileprivate static let customBridge = Key<User>("customBridge", default: fixtureCustomBridge)
|
fileprivate static let customBridge = Key<User>("customBridge", default: fixtureCustomBridge)
|
||||||
fileprivate static let customBridgeArray = Key<[User]>("array_customBridge", default: [fixtureCustomBridge])
|
fileprivate static let customBridgeArray = Key<[User]>("array_customBridge", default: [fixtureCustomBridge])
|
||||||
fileprivate static let customBridgeDictionary = Key<[String: User]>("dictionary_customBridge", default: ["0": fixtureCustomBridge])
|
fileprivate static let customBridgeDictionary = Key<[String: User]>("dictionary_customBridge", default: ["0": fixtureCustomBridge])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
final class DefaultsCustomBridge: XCTestCase {
|
final class DefaultsCustomBridge: XCTestCase {
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
@ -148,6 +192,35 @@ final class DefaultsCustomBridge: XCTestCase {
|
||||||
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomBridge)
|
XCTAssertEqual(Defaults[key]["0"]?[1], fixtureCustomBridge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testRecursiveKey() {
|
||||||
|
let start = PlainHourMinuteTime(hour: 1, minute: 0)
|
||||||
|
let end = PlainHourMinuteTime(hour: 2, minute: 0)
|
||||||
|
let range = PlainHourMinuteTimeRange(start: start, end: end)
|
||||||
|
let key = Defaults.Key<PlainHourMinuteTimeRange>("independentCustomBridgeRecursiveKey", default: range)
|
||||||
|
XCTAssertEqual(Defaults[key].start.hour, range.start.hour)
|
||||||
|
XCTAssertEqual(Defaults[key].start.minute, range.start.minute)
|
||||||
|
XCTAssertEqual(Defaults[key].end.hour, range.end.hour)
|
||||||
|
XCTAssertEqual(Defaults[key].end.minute, range.end.minute)
|
||||||
|
guard let rawValue = UserDefaults.standard.array(forKey: key.name) as? [String] else {
|
||||||
|
XCTFail("rawValue should not be nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
XCTAssertEqual(rawValue, [#"{"minute":0,"hour":1}"#, #"{"minute":0,"hour":2}"#])
|
||||||
|
let next_start = PlainHourMinuteTime(hour: 3, minute: 58)
|
||||||
|
let next_end = PlainHourMinuteTime(hour: 4, minute: 59)
|
||||||
|
let next_range = PlainHourMinuteTimeRange(start: next_start, end: next_end)
|
||||||
|
Defaults[key] = next_range
|
||||||
|
XCTAssertEqual(Defaults[key].start.hour, next_range.start.hour)
|
||||||
|
XCTAssertEqual(Defaults[key].start.minute, next_range.start.minute)
|
||||||
|
XCTAssertEqual(Defaults[key].end.hour, next_range.end.hour)
|
||||||
|
XCTAssertEqual(Defaults[key].end.minute, next_range.end.minute)
|
||||||
|
guard let nextRawValue = UserDefaults.standard.array(forKey: key.name) as? [String] else {
|
||||||
|
XCTFail("nextRawValue should not be nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
XCTAssertEqual(nextRawValue, [#"{"minute":58,"hour":3}"#, #"{"minute":59,"hour":4}"#])
|
||||||
|
}
|
||||||
|
|
||||||
func testType() {
|
func testType() {
|
||||||
XCTAssertEqual(Defaults[.customBridge], fixtureCustomBridge)
|
XCTAssertEqual(Defaults[.customBridge], fixtureCustomBridge)
|
||||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||||
|
|
Loading…
Reference in New Issue