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)
|
||||
```
|
||||
*/
|
||||
static func toValue(_ anyObject: Any) -> Self? {
|
||||
// Return directly if `anyObject` can cast to Value, since it means `Value` is a natively supported type.
|
||||
static func toValue<T: Defaults.Serializable>(_ anyObject: Any, type: T.Type = Self.self) -> T? {
|
||||
if
|
||||
isNativelySupportedType,
|
||||
let anyObject = anyObject as? Self
|
||||
T.isNativelySupportedType,
|
||||
let anyObject = anyObject as? T
|
||||
{
|
||||
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)
|
||||
```
|
||||
*/
|
||||
static func toSerializable(_ value: Self) -> Any? {
|
||||
// Return directly if `Self` is a natively supported type, since it does not need serialization.
|
||||
if isNativelySupportedType {
|
||||
static func toSerializable<T: Defaults.Serializable>(_ value: T) -> Any? {
|
||||
if T.isNativelySupportedType {
|
||||
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 nil
|
||||
return toSerializable(next)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,13 +38,57 @@ public final class DefaultsUserBridge: Defaults.Bridge {
|
|||
|
||||
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 {
|
||||
fileprivate static let customBridge = Key<User>("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])
|
||||
}
|
||||
|
||||
|
||||
final class DefaultsCustomBridge: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
@ -148,6 +192,35 @@ final class DefaultsCustomBridge: XCTestCase {
|
|||
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() {
|
||||
XCTAssertEqual(Defaults[.customBridge], fixtureCustomBridge)
|
||||
let newUser = User(username: "sindresorhus", password: "123456789")
|
||||
|
|
Loading…
Reference in New Issue