Support serializing and deserializing nested custom types (#118)

This commit is contained in:
hank121314 2022-10-28 16:27:17 +08:00 committed by GitHub
parent b23fb7b057
commit be7e30ba36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 13 deletions

View File

@ -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)
}
}

View File

@ -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")