强制编码器的 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
但这会在涉及 nestedContainer
时导致问题nestedUnkeyedContainer
:(分别用于存储的字典和数组)
// 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 协议,该协议要求非抛出版本。
但我不能只处理函数内部的错误!如果尝试将数组放入整数数组中,它一定会失败。因此这是一个无效的解决方案。
附加说明:
在对值进行编码后进行检查已经感觉很粗略,但是仅在生成最终编码的有效负载时进行检查绝对违反了“无僵尸”原则(一旦程序失败进入无效状态),我宁愿避免这种情况。但是,如果没有更好的解决方案,我可能会接受它作为最后的手段。
我考虑过的另一个解决方案是将数组编码为带有编号键的字典,因为这种格式的字典可能包含混合类型。然而,这可能会带来解码问题,因此,这又是最后的手段。
系统会建议您不要编辑其他人的问题。如果您有修改建议,请在评论中提出,否则管好自己的事
As part of a custom Encoder
, I am coding an UnkeyedEncodingContainer
. However, the specific format I am making it for asks that all elements of an array be of the same type. Specifically, arrays can contain :
- Integers one same size
- Floats or Doubles
- Other arrays (not necessarily all containing the same kinds of elements)
- Objects
Here is the type of answer I need : The basis of anUnkeyedEncodingContainer
implementation that conforms to the protocol, and enforces that all elements be of one same type among the above specified ones.
As requested, here are examples of things that should or should not be encodable :
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
As an example, here is the best I have come up with, which does not work :
The UnkeyedEncodingContainer
contains a function, checkCanEncode, and an instance variable, 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
Everything is then encoded as a contained 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
But this causes an issue when it comes to nestedContainer
and nestedUnkeyedContainer
: (used for stored dictionaries and arrays respectively)
// 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)
}
As you can see, since I need checkCanEncode
to know whether it is even possible to create a NestedContainer
in the first place (because if the array already has stuff inside that aren't dictionaries, then adding dictionaries to it is invalid), I have to make the function throw. But this breaks the UnkeyedEncodingContainer
protocol which demands non-throwing versions.
But I can't just handle the error inside the function ! If something tries to put an array inside an array of integers, it must fail. Therefore this is an invalid solution.
Additional remarks :
Checking after having encoded the values already feels sketchy, but checking only when producing the final encoded payload is definitely a violation of the "No Zombies" principle (fail as soon as the program enters an invalid state) which I would rather avoid. However if no better solution is possible I may accept it as a last resort.
One other solution I have thought about is encoding the array as a dictionary with numbered keys, since dictionaries in this format may contain mixed types. However this is likely to pose decoding issues, so once again, it is a last resort.
You will be advised not to edit other people’s questions. If you have edits to suggest please do so in the comments, otherwise mind your own business
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

发布评论
评论(2)
我不知道这是否对您有帮助,但在 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)
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
除非有人有更好的主意,否则这是我能想到的最好的办法:
UnkeyedEncodingContainer
中的所有元素都具有相同的类型编码格式而言,这完全没问题,成本最小,解码仅稍微复杂一些(检查键是否包含整数),并大大拓宽了有多少种不同的 Swift对象将与兼容 格式。
注意:请记住,生成数据的“真实”编码步骤实际上并不是协议的一部分。这就是我建议恶作剧应该发生的地方
Unless anyone has a better idea, here is the best I could come up with :
UnkeyedEncodingContainer
This is completely fine as far as the encoding format goes, has minimal costs and only slightly complicates decoding (check whether keys contain integers) and greatly widens how many different Swift object will be compatible with the format.
Note : Remember that the "real" encoding step where the data is generated is not actually part of the protocol. That is where I am proposing the shenanigans should take place ????