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

时间:2019-01-27 20:06:18

标签: swift serialization codable

结合my previous questions,我决定继承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类都适用的东西,而不仅仅是具有两个名为namevalue的属性的东西怎么办?

基本上,问题是从类创建字典,其中键是属性名称,值是这些属性的值。

我试图写这样的东西:

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

如何解决此问题以使其编译?

2 个答案:

答案 0 :(得分:2)

问题是protocols don't always conform to themselvesPropertyListEncoder的{​​{3}}方法需要一个Value : Encodable参数:

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

但是Encodable类型本身目前无法满足此约束(但在将来的语言版本中可能会做得到)。

如在链接的“问答”(和encode(_:)中所探讨的)一样,解决此限制的一种方法是 open 打开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.
    }
  }
}

答案 1 :(得分:0)

您传递给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)
}

毋庸置疑,这相当荒谬。它是不灵活的,重复的样板。我不确定我是否可以推荐使用它。这是字面问题的答案,不一定是问题的解决方案。