强制编码器的 UnkeyedEncodingContainer 只包含一种类型的值

Forcing an Encoder's UnkeyedEncodingContainer to only contain one type of value

作为自定义 Encoder 的一部分,我正在编写一个 UnkeyedEncodingContainer。但是,我制作的特定格式要求数组的所有元素都属于同一类型。具体来说,数组可以包含:

根据要求,这里有一些应该或不应该编码的例子:

var valid1 = []
var valid2 = [3, 3, 5, 9]
var valid3 = ["string", "array"]

var invalid1 = [3, "test"]
var invalid2 = [5, []]
var invalid3 = [[3, 5], {"hello" : 3}]
// These may not all even be valid Swift arrays, they are only
// intended as examples

举个例子,这是我想出的最好的方法,但它不起作用:


UnkeyedEncodingContainer 包含一个函数 checkCanEncode 和一个实例变量 ElementType :

var elementType : ElementType {
    if self.count == 0 {
        return .None
    } else {
        return self.storage[0].containedType
    }
}


func checkCanEncode(_ value : Any?, compatibleElementTypes : [ElementType]) throws {
    guard compatibleElementTypes.contains(self.elementType) || self.elementType == .None else {
        let context = EncodingError.Context(
            codingPath: self.nestedCodingPath,
            debugDescription: "Cannot encode value to an array of \(self.elementType)s"
        )
        throw EncodingError.invalidValue(value as Any, context)
    }
}
// I know the .None is weird and could be replaced by an optional,
// but it is useful as its rawValue is 0. The Encoder has to encode
// the rawValue of the ElementType at some point, so using an optional
// would actually be more complicated

然后所有内容都被编码为包含 singleValueContainer :

func encode<T>(_ value: T) throws where T : Encodable {
    let container = self.nestedSingleValueContainer()
    try container.encode(value)
    try checkCanEncode(value, compatibleElementTypes: [container.containedType])
}
// containedType is an instance variable of SingleValueContainer that is set
// when a value is encoded into it

但这会导致 nestedContainernestedUnkeyedContainer 出现问题:(分别用于存储的字典和数组)

// This violates the protocol, this function should not be able to throw
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) throws -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
    let container = KeyedContainer<NestedKey>(
        codingPath: self.nestedCodingPath,
        userInfo: self.userInfo
    )
    try checkCanEncode(container, compatibleElementTypes: [.Dictionary])
    self.storage.append(container)
    return KeyedEncodingContainer(container)
}

如您所见,因为我需要 checkCanEncode 来知道是否有可能首先创建一个 NestedContainer(因为如果数组中已经包含了不存在的内容字典,然后向其添加字典是无效的),我 使函数抛出。但这打破了要求非抛出版本的 UnkeyedEncodingContainer 协议。

但我不能只处理函数内部的错误!如果某个东西试图将一个数组放入一个整数数组中,它 必须 失败。因此这是一个无效的解决方案。


补充说明:

在对值进行编码后进行检查已经感觉很粗略,但是仅在生成最终编码的有效负载时进行检查绝对违反了“无僵尸”原则(一旦程序进入我宁愿避免的无效状态) 就会失败。但是,如果没有更好的解决方案,我可能会接受它作为最后的手段。

我考虑过的另一个解决方案是将数组编码为带有编号键的字典,因为这种格式的字典可能包含混合类型。然而,这可能会造成解码问题,所以再次强调,这是最后的手段。


建议您不要编辑其他人的问题。如果您有编辑建议,请在评论中提出,否则请管好自己的事

除非有人有更好的主意,否则这是我能想到的最好的主意:

  • 不强制 UnkeyedEncodingContainer
  • 中的所有元素都属于同一类型
  • 如果所有元素都是同一类型,则将其编码为数组
  • 如果元素有不同的类型,将其编码为以整数作为键的字典

就编码格式而言,这完全没问题,成本最低,解码稍微复杂一点(检查键是否包含整数),并且大大扩大了与格式兼容的不同 Swift 对象的数量.

注意:请记住,生成数据的“真实”编码步骤实际上并不是协议的一部分。那就是我提议恶作剧应该发生的地方

我不知道这是否对您有帮助,但在 Swift 中,您可以 重载 函数。这意味着您可以声明具有相同签名但具有不同参数类型或约束的函数。编译器总是会做出正确的选择。它比运行时的类型检查更有效。

如果数组符合Encodable

则调用第一个方法
func encode<T: Encodable>(object: [T]) throws -> Data {
        return try JSONEncoder().encode(object)
}

否则调用第二种方法。如果数组甚至不能用 JSONSerialization 编码,您可以添加自定义编码逻辑。

func encode<T>(object: [T]) throws -> Data {
    do {
        return try JSONSerialization.data(withJSONObject: object)
    } catch {
        // do custom encoding and return Data
        return try myCustomEncoding(object)
    }
}

这个例子

let array = [["1":1, "2":2]]
try encode(object: array)

调用第一个方法。

另一方面——即使实际类型不是异构的——调用第二种方法

let array : [[String:Any]] = [["1":1, "2":2]]
try encode(object: array)