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

发布于 01-13 18:15 字数 3387 浏览 2 评论 0原文

作为自定义 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 an UnkeyedEncodingContainer 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

输什么也不输骨气2025-01-20 18:15:51

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

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

编码格式而言,这完全没问题,成本最小,解码仅稍微复杂一些(检查键是否包含整数),并大大拓宽了有多少种不同的 Swift对象将与兼容 格式。

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

Unless anyone has a better idea, here is the best I could come up with :

  • Do not enforce that all elements be of the same type inside the UnkeyedEncodingContainer
  • If all elements are the same type, encode it as an array
  • If elements have varying types, encode it as a dictionary with integers as keys

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 ????

绻影浮沉2025-01-20 18:15:51

我不知道这是否对您有帮助,但在 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)

I don't know whether this helps you but in Swift you can overload functions. This means that you can declare functions with the same signature but with different parameter types or constraints. The compiler will take always the right choice. It's much more efficient than a type check at runtime.

The first method is called if the array conforms to Encodable

func encode<T: Encodable>(object: [T]) throws -> Data {
        return try JSONEncoder().encode(object)
}

Otherwise the second method is called. If the array cannot even be encoded with JSONSerialization you can add custom encoding logic.

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

This example

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

calls the first method.

On the other hand this – even if the actual type is not heterogenous – calls the second method

let array : [[String:Any]] = [["1":1, "2":2]]
try encode(object: array)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文