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



作为自定义Encoder的一部分,我正在对UnkeyedEncodingContainer进行编码。然而,我为之制作的特定格式要求数组的所有元素都是相同类型的。具体来说,阵列可以包含:

  • 相同大小的整数
  • 浮动或双人
  • 其他数组(不一定都包含相同种类的元素)
  • 对象这是我需要的答案类型: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(因为如果数组中已经有不是字典的东西,那么向其中添加字典是无效的),所以我有来执行函数throw。但这打破了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)

最新更新