在Swift中,JSON可以从递归枚举中解码和编码

问题描述 投票:0回答:1

我有一个看起来像这样的JSON文件:

{
    "items" : [
        { "name": "a name", "version": "a version" },
        { "version": "a 2nd version" },
        {
            "any_of": [
                { "name": "some name" },
                { "name": "some other name", "version": "some other version" },
                [
                    { "name": "another name" },
                    { "version": "another version" },
                    {
                        "any_of": [
                            [
                                { "version": "some version" },
                                { "version": "some version" }
                            ],
                            { "version": "yet another version" }
                        ]
                    }
                ]
            ]
        },
        {
            "any_of" : [
                { "name": "a name" },
                { "name": "another name" }
            ]
        }
    ]
}

JSON文件具有递归结构。 any_of键指示其数组中所有元素之间的OR关系,缺少any_of键指示AND关系。我想使用Swift的Codable协议解码(和编码)JSON文件,目前我有一个Codable结构代表nameversionJSON对象:

struct NameVersion: Codable {
    let name: String?
    let version: String?

    func toString() -> String { "\(name ?? "")\(version ?? "")" }
}

和代表整个JSON结构的Codable枚举:

enum Items: Codable {
    init(from decoder: Decoder) throws {
        //  decode from JSON here
    }

    func encode(to encoder: Encoder) throws {
        //  encode to JSON here
    }

    case item(NameVersion)

    //  A set of `Items` instances with an "OR" relationship.
    //  This represents a JSON array with an "any_of" key.
    case anyOfItems(Set<Items>)

    //  A set of `Items` instances with an "AND" relationship
    //  This represents a JSON array without an "any_of" key.
    case allOfItems(Set<Items>)

    //  This function might help illustrate the structure and my goal.
    func toString() -> String {
        switch self {
        case let .item(item):
            return item.toString()
        case let .anyOfItems(items):
            return "(\(items.map { $0.toString() }.joined(separator: " ∨ ")))"
        case let .allOfItems(items):
            return "(\(items.map { $0.toString() }.joined(separator: " ∧ ")))"
        }
    }
}

我在为init(from:)枚举实现encode(to:)Items函数时遇到麻烦。我检查了堆栈溢出问题Swift Codable protocol with recursive enums,但情况有所不同,我的枚举未嵌套在结构中,并且item类型的NameVersion关联值不是直接来自键值对。

json swift recursion enums codable
1个回答
0
投票

几乎尝试了所有事情之后,我发现解码JSON的最佳方法是通过UnkeyedDecodingContainer protocol

根据文档,无密钥的容器“被用来顺序地保持可解码类型的编码属性,没有密钥。”这描述了给定JSON结构的完美匹配。

由于UnkeyedDecodingContainer只是CodableDecodable的别名,让我们先使Encodable符合ItemsDecodable之前。


Encodable符合性

鉴于此Decodable结构包含底层JSON对象:

Codable

解码JSON:

struct NameVersion: Codable {
    let name: String?
    let version: String?
}

尽管存在一种indirect enum Items: Codable { /** Initialises an `Items` instance by decoding from the given `decoder`. - Parameter decoder: The decoder to read data from. */ init(from decoder: Decoder) throws { // // This initialiser is designed recursively decode nested JSON arrays into // recursive Swift enums, so we need an instance of a collection type to // hold all the intermediate results. // // Because the unkeyed values in JSON are in a sequence, and because 2 of // Item's cases have associated values of Set<Items> type, we need a // Set<Items> variable to hold all the values while the JSON values are // decoded one by one. var itemsSet: Set<Items> = [] // Create an unkeyed container holding the current level of JSON values. var unkeyedValues = try decoder.unkeyedContainer() // "Loop" through values in the unkeyed container. // The unkeyed container does not conform to the `Sequence` protocol, // but its `currentIndex` property grows by 1 every time when a value // is decoded successfully. while unkeyedValues.count! > unkeyedValues.currentIndex { let containerIndexBeforeLoop = unkeyedValues.currentIndex // Case 1: the JSON value decodes to a NameVersion instance. if let nameVersion = try? unkeyedValues.decode(NameVersion.self) { itemsSet.insert(Items.item(nameVersion)) } // Case 2: the JSON value is a { "any_of": [] } object. // This requires a service structure to take care of it. // More detailed explanation on this later. else if let anyOfItems = try? unkeyedValues.decode(AnyOfItems.self) { itemsSet.insert(anyOfItems.items) } // Case 3: the JSON value is an array without a key. else if let items = try? unkeyedValues.decode(Items.self) { itemsSet.insert(items) } // If the unkeyed container's current index didn't increase by 1 // during this loop, then the the unkeyed value at the current index // was not decoded, and will not be in future loops. There is no way // to increment the index manually, so the unkeyed container will keep // trying for the same value. The only choice is to break out of the // loop in this situation. if unkeyedValues.currentIndex <= containerIndexBeforeLoop { break } } if itemsSet.count == 1 { // If there is only 1 Item in the set, we can just assign it to self. self = ItemsSet.popFirst()! } else { // Since all "any_of" JSON arrays are taken care of by the service // structure, all Items instances in the set are decoded from an // unkeyed JSON array. self = .allOfItems(itemsSet) } } func encode(to encoder: Encoder) throws { // TODO: encode to JSON here } case item(NameVersion) // A set of `Items` instances with an "OR" relationship. // This represents a JSON array with an "any_of" key. case anyOfItems(Set<Items>) // A set of `Item` instances with an "AND" relationship // This represents a JSON array without an "any_of" key. case allOfItems(Set<Items>) } 方法,用于从无密钥容器中获取嵌套的密钥容器,该方法保存了.nestedContainer() JSON对象的数据,但是该嵌套容器无法调用{ "any_of": [] }方法来解码JSON。

相反,我遵循decode(forKey:, from:)来解码嵌套数据,并创建了以下服务结构来解码this solution JSON对象。

{ "any_of": [] }

大多数重复代码可以提取到其自己的功能:

struct AnyOfItems: Codable {

    /**
    Initialises an `Items` instance by decoding from the given `decoder`.

    - Parameter decoder: The decoder to read data from.
    */
    init(from decoder: Decoder) throws {
        var itemsSet: Set<Items> = []

        var unkeyedValues = try decoder.unkeyedContainer()

        while unkeyedValues.count! > unkeyedValues.currentIndex {
            let containerIndexBeforeLoop = unkeyedValues.currentIndex

            if let nameVersion = try? unkeyedValues.decode(NameVersion.self) {
                itemsSet.insert(Items.item(nameVersion))
            } else if let anyOfItems = try? unkeyedValues.decode(AnyOfItems.self) {
                itemsSet.insert(anyOfItems.items)
            } else if let items = try? unkeyedValues.decode(Items.self) {
                itemsSet.insert(items)
            }

            if unkeyedValues.currentIndex <= containerIndexBeforeLoop { break }
        }

        if itemsSet.count == 1 {
            items = itemsSet.popFirst()!
        } else {
            //  The decoding part for AnyOfItems is largely the same as that for 
            //  Items, but they differ in that for AnyOfItems, the set of Items 
            //  are given to the .anyOfItems case.
            itsms = Items.anyOfItems(itemsSet)
        }
    }

    let items: Items
}

indirect enum Items: Codable { init(from decoder: Decoder) throws { // Still has to be a variable, because .popFirst() is a mutating method. var itemsSet: Set<Items> = try decodeItems(from: decoder) if itemsSet.count == 1 { self = ItemsSet.popFirst()! } else { self = .allOfItems(itemsSet) } } func encode(to encoder: Encoder) throws { // TODO: encode to JSON here } case item(NameVersion) case anyOfItems(Set<Items>) case allOfItems(Set<Items>) } struct AnyOfItems: Codable { init(from decoder: Decoder) throws { var itemsSet: Set<Items> = try decodeItems(from: decoder) if itemsSet.count == 1 { items = itemsSet.popFirst()! } else { items = Items.anyOfItems(itemsSet) } } let items: Items } func decodeItems(from decoder: Decoder) throws -> Set<Items> { var itemsSet: Set<Items> = [] var unkeyedValues = try decoder.unkeyedContainer() while unkeyedValues.count! > unkeyedValues.currentIndex { let containerIndexBeforeLoop = unkeyedValues.currentIndex if let nameVersion = try? unkeyedValues.decode(NameVersion.self) { itemsSet.insert(Items.item(nameVersion)) } else if let anyOfItems = try? unkeyedValues.decode(AnyOfItems.self) { itemsSet.insert(anyOfItems.items) } else if let items = try? unkeyedValues.decode(Items.self) { itemsSet.insert(items) } if unkeyedValues.currentIndex <= containerIndexBeforeLoop { break } } return itemsSet } 符合性

编码要简单得多。

Encodable

indirect enum Items: Codable { init(from decoder: Decoder) throws { // JSON decoded here } /** Encodes an `Items` instance`. - Parameter encoder: The encoder to encode data to. */ func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() switch self { case .item(let item): try container.encode(item) case .allOfItems(let items): try container.encode(contentsOf: items) case .anyOfItems(let items): try container.encode(AnyOfItems(Items.anyOfItems(items))) } } case item(NameVersion) case anyOfItems(Set<Items>) case allOfItems(Set<Items>) } struct AnyOfItems: Codable { init(from decoder: Decoder) throws { // JSON decoded here } /** Encodes an `Items` instance`. - Parameter encoder: The encoder to encode data to. */ func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(items, forKey: .items) } /** A memberwise initialiser. */ init(_ items: Items) { self.items = items } let items: Items private enum CodingKeys: String, CodingKey { case items = "any_of" } } 符合性

最后,将所有内容放在一起:

Codable
© www.soinside.com 2019 - 2024. All rights reserved.