简体   繁体   中英

Swift 4.1 Encodable/Decodable Nested Array AND Dictionary

Need more help with json, any suggestions on how to lay this out?

Should players_loop be added in where I put it, etc... been going in circles for awhile.

struct:


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

test:



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

Failed to decode: 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: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))

Params section works great and is in the gist, answered here: Swift 4.1 Codable/Decodable Nested Array

So I used Params as the starting point for "Results" --> but now trying to add in players_loop.

Thanks!!!

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

You have several issues. First of all, you need to be aware of the optional fields. From your attached JSON, it's clearly seen that uuid inside players_loop can be null. So this field should be optional as well in your model.

I purposefully changed the name of the struct to reflect Swift identifier convention (Type's name should start with 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
}

Then in the decodable requirement conformance of your Result enum you need to add another case for your PlayersLoop structure.

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

Now, aside from your issues here, you should be more explicit when providing custom initializer for decodable requirement. As soon as you use try? you are saying to the compiler

"I don't care if this fails"

So you should be handling the error cases as well in the first place. If you did so, you wouldn't be in the position of posting this issue as a question at all.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM