简体   繁体   English

将类型为 Any 的 Swift Encodable 类转换为字典

[英]Convert Swift Encodable class typed as Any to dictionary

In connection with my previous questions , I decided to subclass NSArrayController in order to achieve the desired behavior.结合之前的问题,我决定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)
        }
    }
}

This works, but what if I wanted something that works for any Encodable class, and not just one with two properties called name and value ?这是有效的,但是如果我想要一些适用于任何Encodable类的东西,而不仅仅是具有两个名为namevalue属性呢?

Basically, the problem is creating a dictionary from a class, where the keys are the property names, and the values are the values of these properties.基本上,问题是从类创建字典,其中键是属性名称,值是这些属性的值。

I tried writing something like this:我试着写这样的东西:

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

However, I get a compile error:但是,我收到一个编译错误:

Cannot invoke 'encode' with an argument list of type '(Encodable)'
1. Expected an argument list of type '(Value)'

How do I fix this so it compiles?我如何解决这个问题,以便它编译?

The problem is that protocols don't always conform to themselves .问题是协议并不总是符合它们自己 PropertyListEncoder 's encode(_:) method expects a Value : Encodable argument: PropertyListEncoderencode(_:)方法需要一个Value : Encodable参数:

func encode<Value : Encodable>(_ value: Value) throws -> Data

However the Encodable type itself is currently unable to satisfy this constraint (but it might well do in a future version of the language).然而Encodable类型本身目前无法满足此约束(但它可能会在该语言的未来版本中满足)。

As explored in the linked Q&A (and also here ), one way to work around this limitation is to open the Encodable value in order to dig out the underlying concrete type, which we can substitute for Value .正如链接的问答(以及此处)中所探讨的,解决此限制的一种方法是打开Encodable值以挖掘底层具体类型,我们可以用它来替代Value We can do this with a protocol extension, and use a wrapper type in order to encapsulate it:我们可以使用协议扩展来做到这一点,并使用包装器类型来封装它:

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

Applied to your example:应用于您的示例:

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.
    }
  }
}

The type of the value that you pass to encode(_:) has to be a concrete type that implements Encodable .您传递给encode(_:)的值的类型必须是实现Encodable的具体类型。 This means you need to recover the object's real type from the Any that you have.这意味着您需要从您拥有的Any中恢复对象的真实类型。 In order to cast, you must have a statically-specified type to which you are casting.为了进行强制转换,您必须具有要强制转换的静态指定类型。 You can't say object as! type(of: object)你不能说object as! type(of: object) object as! type(of: object) , in other words; object as! type(of: object) ,换句话说; you have to say object as? MyClass你必须说object as? MyClass object as? MyClass (or in a generic context you can say object as? T ). object as? MyClass (或者在一般情况下,您可以说object as? T )。

Therefore, I believe that the only way to get around this is to statically enumerate the types you are working with, like so:因此,我相信解决这个问题的唯一方法是静态枚举您正在使用的类型,如下所示:

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

Needless to say, this is rather gross.不用说,这相当恶心。 It's inflexible, repetitive boilerplate.它是不灵活的、重复的样板。 I'm not sure I can actually recommend its use.我不确定我真的可以推荐它的使用。 It's an answer to the literal question, not necessarily a solution to the problem.这是字面问题的答案,不一定是问题的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM