首页 > 解决方案 > Swift 4.1 可编码/可解码嵌套数组和字典

问题描述

需要更多关于 json 的帮助,关于如何布局的任何建议?

是否应该将players_loop 添加到我放置的位置,等等... 绕了一段时间。

结构:


    struct LMSRequest: Decodable {
        let id : Int
        let method : String
        let result : [String:Result]
        let params : [Param]
    }

    struct players_loop: Decodable {
        let uuid : String
        let ip : String
        let playerid : String
        let connected : String
        let firmware : String
        let displaytype : String
        let power : Int
        let name : String
        let playerindex : String
        let canpoweroff : Int
        let isplayer : Int
        let seq_no : Int
        let isplaying : Int
        let modelname : String
        let model : String
    }

    enum Result: CustomStringConvertible {
        case string(String)
        case int(Int)
        case array([Result])

        case players_loop([players_loop])

        var description: String {
            switch self {
            case let .string(string): return string
            case let .int(int): return "\(int)"
            case let .players_loop(players_loop): return "\(players_loop)"
            //case let .double(double): return "\(double)"
            //case let .number(number): return "\(number)"
            case let .array(array): return "\(array)"
            }
        }
    }

    extension Result: Decodable {
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let string = try? container.decode(String.self) {
                self = .string(string)
            } else if let int = try? container.decode(Int.self) {
                self = .int(int)
            //} else if let double = try? container.decode(Double.self) {
                //self = .double(double)
            } else {
                self = .array(try container.decode([Result].self))
            }
        }
    }

    extension Result {
        var stringValue: String? { if case let .string(value) = self { return value } else { return nil } }
        var intValue: Int? { if case let .int(value) = self { return value } else { return nil } }
        //var doubleValue: Double? { if case let .double(value) = self { return value } else { return nil } }
        var arrayValue: [Result]? { if case let .array(value) = self { return value } else { return nil } }

        subscript(_ index: Int) -> Result? {
            return arrayValue?[index]
        }
    }

    enum Param: CustomStringConvertible {
        case string(String)
        case int(Int)
        case array([Param])

        var description: String {
            switch self {
            case let .string(string): return string
            case let .int(int): return "\(int)"
            case let .array(array): return "\(array)"
            }
        }
    }

    extension Param: Decodable {
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let string = try? container.decode(String.self) {
                self = .string(string)
            } else if let int = try? container.decode(Int.self) {
                self = .int(int)
            } else {
                self = .array(try container.decode([Param].self))
            }
        }
    }

    extension Param {
        var stringValue: String? { if case let .string(value) = self { return value } else { return nil } }
        var intValue: Int? { if case let .int(value) = self { return value } else { return nil } }
        var arrayValue: [Param]? { if case let .array(value) = self { return value } else { return nil } }

        subscript(_ index: Int) -> Param? {
            return arrayValue?[index]
        }
    }

json:



    let json = """
    {
        "id": 1,
        "method": "slim.request",
        "result": {
            "mac": "B8:27:EB:7A:FA:4B",
            "uuid": "e54389e6-7f63-4aac-aa83-a199226279f4",
            "other player count": 0,
            "info total artists": 6018,
            "version": "7.9.1",
            "info total albums": 6327,
            "info total songs": 71808,
            "lastscan": "1528917689",
            "info total genres": 553,
            "player count": 2,
            "players_loop": [
                {
                    "model": "squeezelite",
                    "modelname": "SqueezeLite",
                    "seq_no": 0,
                    "isplaying": 1,
                    "isplayer": 1,
                    "canpoweroff": 1,
                    "connected": 1,
                    "firmware": "v1.8.7-1052",
                    "playerindex": "-",
                    "displaytype": "none",
                    "power": 1,
                    "name": "piCorePlayer",
                    "playerid": "b8:27:eb:7d:09:80",
                    "uuid": null,
                    "ip": "10.2.2.21:47100"
                }
            ]
        },
        "params": [
            "b8:27:eb:db:6d:62",
            [
                "serverstatus",
                "-",
                1,
                "tags:GPASIediqtymkovrfijnCYXRTIuwxNlasc"
            ]
        ]
    }
    """.data(using: .utf8)!

测试:



    do {
        let decoder = JSONDecoder()
        //decoder.keyDecodingStrategy = .convertFromSnakeCase
        let lms = try decoder.decode(LMSRequest.self, from: json)
        print(lms)

        let inner = lms.params[1]
        print(type(of: lms.params[1][0]))
        print(inner[3] as Any)

    } catch let jsonErr {
        print("Failed to decode:", jsonErr)
    }

解码失败:typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), _DictionaryCodingKey(stringValue: "players_loop", intValue: nil), _JSONKey(stringValue: " Index 0", intValue: 0)], debugDescription: "期望解码 Array 但找到了一个字典。",底层错误: nil))

参数部分效果很好,很重要,在这里回答: Swift 4.1 Codable/Decodable Nested Array

所以我使用参数作为“结果”的起点——>但现在尝试添加 player_loop。

谢谢!!!

https://gist.github.com/sckramer/5b90d3b15eef2c19196abab2d7698e70

标签: arraysjsonswift4.1

解决方案


你有几个问题。首先,您需要了解这些optional字段。从您附加的 JSON 中,可以清楚地看到uuidinsideplayers_loop可以为空。所以这个字段在你的模型中也应该是可选的。

我故意更改了结构的名称以反映 Swift 标识符约定(类型的名称应以UpperCase开头)。

struct PlayersLoop: Decodable {
    let uuid : String?  // can take null
    let ip : String
    let playerid : String
    let connected : Int
    let firmware : String
    let displaytype : String
    let power : Int
    let name : String
    let playerindex : String
    let canpoweroff : Int
    let isplayer : Int
    let seq_no : Int
    let isplaying : Int
    let modelname : String
    let model : String
}

然后在您的Result枚举的可解码要求一致性中,您需要为您的PlayersLoop结构添加另一个案例。

extension Result: Decodable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let string = try? container.decode(String.self) {
            self = .string(string)
        } else if let int = try? container.decode(Int.self) {
            self = .int(int)
        } else if let playersLoop = try? container.decode([PlayersLoop].self) {
            self = .players_loop(playersLoop)
        } else {
            self = .array(try container.decode([Result].self))
        }
    }
}    

现在,除了这里的问题之外,在为可解码要求提供自定义初始化程序时,您应该更加明确。一旦你使用try?你就对编译器说

“我不在乎这是否失败”

所以你应该首先处理错误情况。如果你这样做了,你根本不会把这个问题作为一个问题发布。


推荐阅读