使用Swift 4解码嵌套JSON仅返回一个对象

时间:2018-12-27 07:17:34

标签: ios json swift decodable jsondecoder

我正在尝试解码从网络请求中接收到的JSON,但它仅解码第一个对象。 JSON看起来像这样

{
  "lastUpdatedOn": "2018-08-21T14:38:38.251Z",
  "players": [
    {
      "player": {
        "id": 10138,
        "firstName": "Alex",
        "lastName": "Abrines",
        "primaryPosition": "SG",
        "alternatePositions": [],
        "jerseyNumber": 8,
        "currentTeam": {
          "id": 96,
          "abbreviation": "OKL"
        },
        "currentRosterStatus": "ROSTER",
        "currentInjury": null,
        "height": "6'6\"",
        "weight": 190,
        "birthDate": "1993-08-01",
        "age": 25,
        "birthCity": "Palma de Mallorca",
        "birthCountry": "Spain",
        "rookie": false,
        "highSchool": null,
        "college": null,
        "handedness": {
          "shoots": "R"
        },
        "officialImageSrc": "https://ak-static.cms.nba.com/wp-content/uploads/headshots/nba/latest/260x190/203518.png",
        "socialMediaAccounts": [],
        "currentContractYear": null,
        "drafted": {
          "year": 2013,
          "team": {
            "id": 96,
            "abbreviation": "OKL"
          },
          "pickTeam": {
            "id": 96,
            "abbreviation": "OKL"
          },
          "round": 2,
          "roundPick": 2,
          "overallPick": 32
        },
        "externalMappings": [
          {
            "source": "NBA.com",
            "id": 203518
          }
        ]
      },
      "teamAsOfDate": {
        "id": 96,
        "abbreviation": "OKL"
      }
    },
    {
      "player": {
        "id": 9466,
        "firstName": "Quincy",
        "lastName": "Acy",
        "primaryPosition": "PF",
        "alternatePositions": [],
        "jerseyNumber": 13,
        "currentTeam": {
          "id": 84,
          "abbreviation": "BRO"
        },
        "currentRosterStatus": "ROSTER",
        "currentInjury": null,
        "height": "6'7\"",
        "weight": 240,
        "birthDate": "1990-10-06",
        "age": 27,
        "birthCity": "Tyler, TX",
        "birthCountry": "USA",
        "rookie": false,
        "highSchool": "Mesquite Horn",
        "college": "Baylor University",
        "handedness": {
          "shoots": "R"
        },
        "officialImageSrc": "https://ak-static.cms.nba.com/wp-content/uploads/headshots/nba/latest/260x190/203112.png",
        "socialMediaAccounts": [
          {
            "mediaType": "TWITTER",
            "value": "QuincyAcy"
          }
        ],
        "currentContractYear": {
          "seasonStartYear": 2017,
          "baseSalary": 1709538,
          "minorsSalary": 0,
          "signingBonus": 0,
          "otherBonuses": 0,
          "capHit": 1709538,
          "fullNoTradeClause": false,
          "modifiedNoTradeClause": false,
          "noMovementClause": false,
          "overallContract": {
            "signingTeam": {
              "id": 84,
              "abbreviation": "BRO"
            },
            "signedOn": null,
            "totalYears": 2,
            "totalSalary": 3499630,
            "totalBonuses": 0,
            "expiryStatus": "UFA",
            "annualAverageSalary": 1749815
          }
        },
        "drafted": {
          "year": 2012,
          "team": {
            "id": 81,
            "abbreviation": "TOR"
          },
          "pickTeam": {
            "id": 81,
            "abbreviation": "TOR"
          },
          "round": 2,
          "roundPick": 7,
          "overallPick": 37
        },
        "externalMappings": [
          {
            "source": "NBA.com",
            "id": 203112
          }
        ]
      },
      "teamAsOfDate": {
        "id": 84,
        "abbreviation": "BRO"
      }
    },
    {
      "player": {
        "id": 9301,
        "firstName": "Jordan",
        "lastName": "Adams",
        "primaryPosition": "SG",
        "alternatePositions": [],
        "jerseyNumber": 3,
        "currentTeam": null,
        "currentRosterStatus": "RETIRED",
        "currentInjury": null,
        "height": "6'5\"",
        "weight": 209,
        "birthDate": "1994-07-08",
        "age": 24,
        "birthCity": "Atlanta, GA",
        "birthCountry": "USA",
        "rookie": false,
        "highSchool": "Oak Hill Academy",
        "college": "University of California, Los Angeles",
        "handedness": {
          "shoots": "R"
        },
        "officialImageSrc": "https://ak-static.cms.nba.com/wp-content/uploads/headshots/nba/latest/260x190/203919.png",
        "socialMediaAccounts": [
          {
            "mediaType": "TWITTER",
            "value": "jordanadams1231"
          }
        ],
        "currentContractYear": null,
        "drafted": {
          "year": 2014,
          "team": {
            "id": 107,
            "abbreviation": "MEM"
          },
          "pickTeam": {
            "id": 107,
            "abbreviation": "MEM"
          },
          "round": 1,
          "roundPick": 22,
          "overallPick": 22
        },
        "externalMappings": [
          {
            "source": "NBA.com",
            "id": 203919
          }
        ]
      },
      "teamAsOfDate": null
    }
  ],
  "references": {
    "teamReferences": [
      {
        "id": 84,
        "city": "Brooklyn",
        "name": "Nets",
        "abbreviation": "BRO",
        "homeVenue": {
          "id": 78,
          "name": "Barclays Center"
        },
        "teamColoursHex": [],
        "socialMediaAccounts": [],
        "officialLogoImageSrc": null
      },
      {
        "id": 96,
        "city": "Oklahoma City",
        "name": "Thunder",
        "abbreviation": "OKL",
        "homeVenue": {
          "id": 90,
          "name": "Chesapeake Energy Arena"
        },
        "teamColoursHex": [],
        "socialMediaAccounts": [],
        "officialLogoImageSrc": null
      }
    ]
  }
}

我的结构目前看起来像这样,我正在尝试使用自定义解码器,我能够使用嵌套结构使其工作,但随后我必须遍历解码后的数据并将所有播放器添加到数组中。

    struct Player: Codable {

        let firstName: String
        let lastName: String

        enum RootKeys: String, CodingKey {
            case players
        }

        enum PlayerKeys: String, CodingKey {
            case player
        }


        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: RootKeys.self)

            var playersUnkeyedContainer = try container.nestedUnkeyedContainer(forKey: .players)

            let playersContainer = try playersUnkeyedContainer.nestedContainer(keyedBy: PlayerKeys.self)

            let playerContainer = try playersContainer.nestedContainer(keyedBy: Player.CodingKeys.self, forKey: .player)

            firstName = try playerContainer.decode(String.self, forKey: .firstName)
            lastName = try playerContainer.decode(String.self, forKey: .lastName)
        } 
    }

当我尝试这样做

let decodedPlayers = try jsonDecoder.decode(Player.self, from: data)

它可以工作,但是decodedPlayers只是一个播放器,如果我尝试这样做:

let decodedPlayers = try jsonDecoder.decode([Player].self, from: data)

我收到此错误消息:     “希望对Array进行解码,但找到了字典。”

4 个答案:

答案 0 :(得分:0)

这对您有帮助!!

// To parse the JSON, add this file to your project and do:
    //
    //   let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)

    import Foundation

    struct Welcome: Codable {
        let lastUpdatedOn: String
        let players: [PlayerElement]
        let references: References
    }

    struct PlayerElement: Codable {
        let player: PlayerPlayer
        let teamAsOfDate: TeamAsOfDate?
    }

    struct PlayerPlayer: Codable {
        let id: Int
        let firstName, lastName, primaryPosition: String
        let alternatePositions: [JSONAny]
        let jerseyNumber: Int
        let currentTeam: TeamAsOfDate?
        let currentRosterStatus: String
        let currentInjury: JSONNull?
        let height: String
        let weight: Int
        let birthDate: String
        let age: Int
        let birthCity, birthCountry: String
        let rookie: Bool
        let highSchool, college: String?
        let handedness: Handedness
        let officialImageSrc: String
        let socialMediaAccounts: [SocialMediaAccount]
        let currentContractYear: CurrentContractYear?
        let drafted: Drafted
        let externalMappings: [ExternalMapping]
    }

    struct CurrentContractYear: Codable {
        let seasonStartYear, baseSalary, minorsSalary, signingBonus: Int
        let otherBonuses, capHit: Int
        let fullNoTradeClause, modifiedNoTradeClause, noMovementClause: Bool
        let overallContract: OverallContract
    }

    struct OverallContract: Codable {
        let signingTeam: TeamAsOfDate
        let signedOn: JSONNull?
        let totalYears, totalSalary, totalBonuses: Int
        let expiryStatus: String
        let annualAverageSalary: Int
    }

    struct TeamAsOfDate: Codable {
        let id: Int
        let abbreviation: String
    }

    struct Drafted: Codable {
        let year: Int
        let team, pickTeam: TeamAsOfDate
        let round, roundPick, overallPick: Int
    }

    struct ExternalMapping: Codable {
        let source: String
        let id: Int
    }

    struct Handedness: Codable {
        let shoots: String
    }

    struct SocialMediaAccount: Codable {
        let mediaType, value: String
    }

    struct References: Codable {
        let teamReferences: [TeamReference]
    }

    struct TeamReference: Codable {
        let id: Int
        let city, name, abbreviation: String
        let homeVenue: HomeVenue
        let teamColoursHex, socialMediaAccounts: [JSONAny]
        let officialLogoImageSrc: JSONNull?
    }

    struct HomeVenue: Codable {
        let id: Int
        let name: String
    }

    // MARK: Encode/decode helpers

    class JSONNull: Codable, Hashable {

        public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
            return true
        }

        public var hashValue: Int {
            return 0
        }

        public init() {}

        public required init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if !container.decodeNil() {
                throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
            }
        }

        public func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encodeNil()
        }
    }

    class JSONCodingKey: CodingKey {
        let key: String

        required init?(intValue: Int) {
            return nil
        }

        required init?(stringValue: String) {
            key = stringValue
        }

        var intValue: Int? {
            return nil
        }

        var stringValue: String {
            return key
        }
    }

    class JSONAny: Codable {
        let value: Any

        static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
            let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
            return DecodingError.typeMismatch(JSONAny.self, context)
        }

        static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
            let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
            return EncodingError.invalidValue(value, context)
        }

        static func decode(from container: SingleValueDecodingContainer) throws -> Any {
            if let value = try? container.decode(Bool.self) {
                return value
            }
            if let value = try? container.decode(Int64.self) {
                return value
            }
            if let value = try? container.decode(Double.self) {
                return value
            }
            if let value = try? container.decode(String.self) {
                return value
            }
            if container.decodeNil() {
                return JSONNull()
            }
            throw decodingError(forCodingPath: container.codingPath)
        }

        static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
            if let value = try? container.decode(Bool.self) {
                return value
            }
            if let value = try? container.decode(Int64.self) {
                return value
            }
            if let value = try? container.decode(Double.self) {
                return value
            }
            if let value = try? container.decode(String.self) {
                return value
            }
            if let value = try? container.decodeNil() {
                if value {
                    return JSONNull()
                }
            }
            if var container = try? container.nestedUnkeyedContainer() {
                return try decodeArray(from: &container)
            }
            if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
                return try decodeDictionary(from: &container)
            }
            throw decodingError(forCodingPath: container.codingPath)
        }

        static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
            if let value = try? container.decode(Bool.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(Int64.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(Double.self, forKey: key) {
                return value
            }
            if let value = try? container.decode(String.self, forKey: key) {
                return value
            }
            if let value = try? container.decodeNil(forKey: key) {
                if value {
                    return JSONNull()
                }
            }
            if var container = try? container.nestedUnkeyedContainer(forKey: key) {
                return try decodeArray(from: &container)
            }
            if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
                return try decodeDictionary(from: &container)
            }
            throw decodingError(forCodingPath: container.codingPath)
        }

        static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
            var arr: [Any] = []
            while !container.isAtEnd {
                let value = try decode(from: &container)
                arr.append(value)
            }
            return arr
        }

        static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
            var dict = [String: Any]()
            for key in container.allKeys {
                let value = try decode(from: &container, forKey: key)
                dict[key.stringValue] = value
            }
            return dict
        }

        static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
            for value in array {
                if let value = value as? Bool {
                    try container.encode(value)
                } else if let value = value as? Int64 {
                    try container.encode(value)
                } else if let value = value as? Double {
                    try container.encode(value)
                } else if let value = value as? String {
                    try container.encode(value)
                } else if value is JSONNull {
                    try container.encodeNil()
                } else if let value = value as? [Any] {
                    var container = container.nestedUnkeyedContainer()
                    try encode(to: &container, array: value)
                } else if let value = value as? [String: Any] {
                    var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
                    try encode(to: &container, dictionary: value)
                } else {
                    throw encodingError(forValue: value, codingPath: container.codingPath)
                }
            }
        }

        static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
            for (key, value) in dictionary {
                let key = JSONCodingKey(stringValue: key)!
                if let value = value as? Bool {
                    try container.encode(value, forKey: key)
                } else if let value = value as? Int64 {
                    try container.encode(value, forKey: key)
                } else if let value = value as? Double {
                    try container.encode(value, forKey: key)
                } else if let value = value as? String {
                    try container.encode(value, forKey: key)
                } else if value is JSONNull {
                    try container.encodeNil(forKey: key)
                } else if let value = value as? [Any] {
                    var container = container.nestedUnkeyedContainer(forKey: key)
                    try encode(to: &container, array: value)
                } else if let value = value as? [String: Any] {
                    var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
                    try encode(to: &container, dictionary: value)
                } else {
                    throw encodingError(forValue: value, codingPath: container.codingPath)
                }
            }
        }

        static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
            if let value = value as? Bool {
                try container.encode(value)
            } else if let value = value as? Int64 {
                try container.encode(value)
            } else if let value = value as? Double {
                try container.encode(value)
            } else if let value = value as? String {
                try container.encode(value)
            } else if value is JSONNull {
                try container.encodeNil()
            } else {
                throw encodingError(forValue: value, codingPath: container.codingPath)
            }
        }

        public required init(from decoder: Decoder) throws {
            if var arrayContainer = try? decoder.unkeyedContainer() {
                self.value = try JSONAny.decodeArray(from: &arrayContainer)
            } else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
                self.value = try JSONAny.decodeDictionary(from: &container)
            } else {
                let container = try decoder.singleValueContainer()
                self.value = try JSONAny.decode(from: container)
            }
        }

        public func encode(to encoder: Encoder) throws {
            if let arr = self.value as? [Any] {
                var container = encoder.unkeyedContainer()
                try JSONAny.encode(to: &container, array: arr)
            } else if let dict = self.value as? [String: Any] {
                var container = encoder.container(keyedBy: JSONCodingKey.self)
                try JSONAny.encode(to: &container, dictionary: dict)
            } else {
                var container = encoder.singleValueContainer()
                try JSONAny.encode(to: &container, value: self.value)
            }
        }
    }

答案 1 :(得分:0)

您的JSON不是数组。因此,您不能使用:

let decodedPlayers = try jsonDecoder.decode([Player].self, from: data)

您需要:

   [{"id": 10138, "firstName": "Alex"}, {"id": 10139, "firstName": "Alex2"} ]

OR

struct YourData: Codable {
    let lastUpdatedOn: String
    let players: [Player]
}

extension YourData: Decodable {

    enum CodingKeys: CodingKey { case lastUpdatedOn, players }

    enum NodeKeys: CodingKey { case nodes }

    init(from decoder: Decoder) throws {
        let rootContainer = try decoder.container(keyedBy: CodingKeys.self)
        try lastUpdatedOn = rootContainer.decode(String.self, forKey: .lastUpdatedOn)
        let playersNode = try rootContainer.nestedContainer(keyedBy: NodeKeys.self, forKey: .players)
        var nodes = try playersNode(forKey: .nodes)
        var playerArray: [Player] = []

        while !nodes.isAtEnd {
            let ply = try nodes.decode(Player.self)
            playerArray += [ply]
        }
        players = playerArray
    }
}

最后:

let decodedPlayers = try jsonDecoder.decode(YourData.self, from: data)

参考文献:

答案 2 :(得分:0)

使用嵌套容器的解决方案似乎很麻烦

我的建议是使用嵌套结构以传统方式解码结构

struct Root: Decodable {
    let lastUpdatedOn : String
    let players : [PlayerData]
}

struct PlayerData: Decodable {
    let player : Player
    let teamAsOfDate : TeamAsOfDate?
}

struct TeamAsOfDate: Decodable {
    let id : Int
    let abbreviation : String
}

struct Player: Decodable {
    let firstName, lastName : String
}

do {
    let result = try JSONDecoder().decode(Root.self, from: data)
    print(result)
} catch { print(error) }

答案 3 :(得分:0)

这有效:

struct Player: Decodable {
    let firstName: String
    let lastName: String
}

struct AllPlayers: Decodable {
    let players: [NBAPlayer]
}

struct NBAPlayer: Decodable {
    let player: Player
}

但是如果我想要一个包含所有Player对象的数组,则必须这样做:

var players = [Player]()

    do {
        let decodedPlayers = try jsonDecoder.decode(AllPlayers.self, from: data)
        for player in decodedPlayers.players {
            players.append(player.player)
        }
        print(players)
    } catch {
        NSLog("Error decoding players :\(error)")
    }