将类型为 Any 的 Swift Encodable class 转换为字典
Convert Swift Encodable class typed as Any to dictionary
关于 ,我决定子 class NSArrayController
以实现所需的行为。
class NSPresetArrayController: NSArrayController {
override func addObject(_ object: Any) {
if let preset = object as? Preset {
super.addObject(["name": preset.name, "value": preset.value])
} else {
super.addObject(object)
}
}
}
这行得通,但是如果我想要适用于任何 Encodable
class 的东西,而不仅仅是具有两个属性 name
和 value
的东西怎么办?
基本上,问题是从 class 创建字典,其中键是 属性 名称,值是这些属性的值。
我试过这样写:
class NSPresetArrayController: NSArrayController {
override func addObject(_ object: Any) {
if let encodableObject = object as? Encodable {
let data = try! PropertyListEncoder().encode(encodableObject)
let any = try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)
super.addObject(any)
}
}
}
但是,我得到一个编译错误:
Cannot invoke 'encode' with an argument list of type '(Encodable)'
1. Expected an argument list of type '(Value)'
我该如何解决这个问题才能编译?
您传递给 encode(_:)
的值的类型必须是实现 Encodable
的具体类型。这意味着您需要从您拥有的 Any
恢复对象的真实类型。为了进行转换,您必须具有要转换到的静态指定类型。换句话说,你不能说 object as! type(of: object)
;你必须说 object as? MyClass
(或者在一般情况下你可以说 object as? T
)。
因此,我认为解决此问题的唯一方法是静态枚举您正在使用的类型,如下所示:
import Foundation
struct S : Encodable {
let i: Int
}
struct T : Encodable {
let f: Float
}
struct U : Encodable {
let b: Bool
}
func plistObject(from encodable: Any) -> Any? {
let encoded: Data?
switch encodable {
case let s as S:
encoded = try? PropertyListEncoder().encode(s)
case let t as T:
encoded = try? PropertyListEncoder().encode(t)
case let u as U:
encoded = try? PropertyListEncoder().encode(u)
default:
encoded = nil
}
guard let data = encoded else { return nil }
return try? PropertyListSerialization.propertyList(from: data,
options: [],
format: nil)
}
不用说,这太恶心了。它是不灵活的、重复的样板文件。我不确定我是否真的可以推荐 使用它。这是对字面问题的回答,不一定是问题的解决方案。
问题是 . PropertyListEncoder
's encode(_:)
方法需要一个 Value : Encodable
参数:
func encode<Value : Encodable>(_ value: Value) throws -> Data
但是 Encodable
类型本身目前无法满足此约束(但在该语言的未来版本中可能会很好)。
如链接的问答(和 also here)中所述,解决此限制的一种方法是 打开 Encodable
值,以便挖掘出底层的具体类型,我们可以将其替换为 Value
。我们可以使用协议扩展来做到这一点,并使用包装器类型来封装它:
extension Encodable {
fileprivate func openedEncode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyEncodable : Encodable {
var value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try value.openedEncode(to: &container)
}
}
应用于您的示例:
class NSPresetArrayController : NSArrayController {
override func addObject(_ object: Any) {
guard let object = object as? Encodable else {
// Not encodable, maybe do some error handling.
return
}
do {
let encoded = try PropertyListEncoder().encode(AnyEncodable(object))
let cocoaPropertyList = try PropertyListSerialization.propertyList(from: encoded, format: nil)
super.addObject(cocoaPropertyList)
} catch {
// Couldn't encode. Do some error handling.
}
}
}
关于 NSArrayController
以实现所需的行为。
class NSPresetArrayController: NSArrayController {
override func addObject(_ object: Any) {
if let preset = object as? Preset {
super.addObject(["name": preset.name, "value": preset.value])
} else {
super.addObject(object)
}
}
}
这行得通,但是如果我想要适用于任何 Encodable
class 的东西,而不仅仅是具有两个属性 name
和 value
的东西怎么办?
基本上,问题是从 class 创建字典,其中键是 属性 名称,值是这些属性的值。
我试过这样写:
class NSPresetArrayController: NSArrayController {
override func addObject(_ object: Any) {
if let encodableObject = object as? Encodable {
let data = try! PropertyListEncoder().encode(encodableObject)
let any = try! PropertyListSerialization.propertyList(from: data, options: [], format: nil)
super.addObject(any)
}
}
}
但是,我得到一个编译错误:
Cannot invoke 'encode' with an argument list of type '(Encodable)'
1. Expected an argument list of type '(Value)'
我该如何解决这个问题才能编译?
您传递给 encode(_:)
的值的类型必须是实现 Encodable
的具体类型。这意味着您需要从您拥有的 Any
恢复对象的真实类型。为了进行转换,您必须具有要转换到的静态指定类型。换句话说,你不能说 object as! type(of: object)
;你必须说 object as? MyClass
(或者在一般情况下你可以说 object as? T
)。
因此,我认为解决此问题的唯一方法是静态枚举您正在使用的类型,如下所示:
import Foundation
struct S : Encodable {
let i: Int
}
struct T : Encodable {
let f: Float
}
struct U : Encodable {
let b: Bool
}
func plistObject(from encodable: Any) -> Any? {
let encoded: Data?
switch encodable {
case let s as S:
encoded = try? PropertyListEncoder().encode(s)
case let t as T:
encoded = try? PropertyListEncoder().encode(t)
case let u as U:
encoded = try? PropertyListEncoder().encode(u)
default:
encoded = nil
}
guard let data = encoded else { return nil }
return try? PropertyListSerialization.propertyList(from: data,
options: [],
format: nil)
}
不用说,这太恶心了。它是不灵活的、重复的样板文件。我不确定我是否真的可以推荐 使用它。这是对字面问题的回答,不一定是问题的解决方案。
问题是 PropertyListEncoder
's encode(_:)
方法需要一个 Value : Encodable
参数:
func encode<Value : Encodable>(_ value: Value) throws -> Data
但是 Encodable
类型本身目前无法满足此约束(但在该语言的未来版本中可能会很好)。
如链接的问答(和 also here)中所述,解决此限制的一种方法是 打开 Encodable
值,以便挖掘出底层的具体类型,我们可以将其替换为 Value
。我们可以使用协议扩展来做到这一点,并使用包装器类型来封装它:
extension Encodable {
fileprivate func openedEncode(to container: inout SingleValueEncodingContainer) throws {
try container.encode(self)
}
}
struct AnyEncodable : Encodable {
var value: Encodable
init(_ value: Encodable) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try value.openedEncode(to: &container)
}
}
应用于您的示例:
class NSPresetArrayController : NSArrayController {
override func addObject(_ object: Any) {
guard let object = object as? Encodable else {
// Not encodable, maybe do some error handling.
return
}
do {
let encoded = try PropertyListEncoder().encode(AnyEncodable(object))
let cocoaPropertyList = try PropertyListSerialization.propertyList(from: encoded, format: nil)
super.addObject(cocoaPropertyList)
} catch {
// Couldn't encode. Do some error handling.
}
}
}