在Swift

问题描述

上下文

这是我几天前问的that question的后续问题,尽管并非必须事先阅读。

我有一个API端点/common,它以这种形式返回JSON数据:

{
    "data":
    {
        "players": [
        {
            "id": 1,"name": "John Doe"
        },{
            "id": 15,"name": "Jessica Thump"
        }],"games": [
        {
            "name": "Tic Tac Toe","playerId1": 15,"playerId2": 1
        }]
    }
}

在其他代码段中,假定此响应作为String存储在变量rawApiResponse中。

我的目的是根据Swift struct的要求对其进行解码:

struct Player: Decodable {
    var id: Int
    var name: String?
}

struct Game: Decodable {
    var name: String
    var player1: Player
    var player2: Player
    
    enum CodingKeys: String,CodingKey {
        case name
        case player1 = "playerId1"
        case player2 = "playerId2"
    }
}

多亏了我原来的问题,我现在可以成功解码PlayerGame了,但是只有当我使用的响应String是内部数组{{ 1}}:

e.g.

问题

如何从let playersResponse = """ [ { "id": 1,"name": "John Doe" },{ "id": 15,"name": "Jessica Thump" } ] """ let players = try! JSONDecoder().decode([Player].self,from: playersResponse.data(using: .utf8)!) 的API响应中仅提取JSON "players"数组,以便以后可以将其馈送到/common的JSON解码器中?

请注意,我不能使用(或至少是我认为的)制作超级Player的“常规” Decodable方法,因为我需要Structplayers之前解码(这是原始问题的主题)。因此,这不起作用:

games

到目前为止我尝试过的事情

我调查了how to convert a JSON string to a dictionary,但这只是部分帮助了

struct ApiResponse: Decodable {
    let data: ApiData
}

struct ApiData: Decodable {
    let players: [Player]
    let games: [Game]
}
let data = try! JSONDecoder().decode(ApiResponse.self,from: rawApiResponse.data(using: .utf8)!)

如果我转储let json = try JSONSerialization.jsonObject(with: rawApiResponse.data(using: .utf8)!,options: .mutableContainers) as? [String:AnyObject] let playersRaw = json!["data"]!["players"]!! ,它看起来像我想要的,但是我不知道如何将其强制转换为playersRaw并以{{1} }是Data


我觉得我没有按照应有的方式做事,所以如果您对一般问题有更“迅速”的解决方(而不是专门针对如何提取子集) JSON数据),那就更好了!

解决方法

您可以通过在ApiData中自己实现解码并在players数组中搜索每个播放器ID来实现这一目标:

struct ApiResponse: Decodable {
    let data: ApiData
}

struct ApiData: Decodable {
    let players: [Player]
    var games: [Game]
    
    enum CodingKeys: String,CodingKey {
        case players
        case games
    }
    
    enum GameCodingKeys: String,CodingKey {
        case name
        case playerId1
        case playerId2
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        players = try container.decode([Player].self,forKey: .players)
        var gamesContainer = try container.nestedUnkeyedContainer(forKey: .games)
        games = []
        while !gamesContainer.isAtEnd {
            let gameContainer = try gamesContainer.nestedContainer(keyedBy: GameCodingKeys.self)
            let playerId1 = try gameContainer.decode(Int.self,forKey: .playerId1)
            let playerId2 = try gameContainer.decode(Int.self,forKey: .playerId2)
            guard
                let player1 = players.first(where: { $0.id == playerId1 }),let player2 = players.first(where: { $0.id == playerId2 })
            else { continue }
            let game = Game(
                name: try gameContainer.decode(String.self,forKey: .name),player1: player1,player2: player2
            )
            games.append(game)
        }
    }
}

struct Player: Decodable {
    var id: Int
    var name: String?
}

struct Game: Decodable {
    var name: String
    var player1: Player
    var player2: Player
}

这有点丑陋,但最终您可以像这样使用它:

let decoder = JSONDecoder()
do {
    let response = try decoder.decode(ApiResponse.self,from: rawApiResponse.data(using: .utf8)!)
    let games = response.data.games
    print(games)
} catch {
    print(error)
}
,

您只需要提供一个根结构并获取其数据播放器即可。无需解码不需要的值:


struct ApiResponse: Decodable {
    let data: ApiData
}

struct ApiData: Decodable {
    let players: [Player]
    let games: [Game]
}

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

struct Game: Decodable {
    var name: String
    var player1: Int
    var player2: Int
    enum CodingKeys: String,CodingKey {
        case name,player1 = "playerId1",player2 = "playerId2"
    }
}

let common = """
{
    "data":
    {
        "players": [
        {
            "id": 1,"name": "John Doe"
        },{
            "id": 15,"name": "Jessica Thump"
        }],"games": [
        {
            "name": "Tic Tac Toe","playerId1": 15,"playerId2": 1
        }]
    }
}
"""

do {
    let players = try JSONDecoder().decode(ApiResponse.self,from: Data(common.utf8)).data.players
    print(players)  // [__lldb_expr_48.Player(id: 1,name: "John Doe"),__lldb_expr_48.Player(id: 15,name: "Jessica Thump")]
    let games = try JSONDecoder().decode(ApiResponse.self,from: Data(common.utf8)).data.games
    print(games)  // [__lldb_expr_52.Game(name: "Tic Tac Toe",player1: 15,player2: 1)]
    
    // or get the common data
    let commonData = try JSONDecoder().decode(ApiResponse.self,from: Data(common.utf8)).data
    print(commonData.players)
    print(commonData.games)
} catch {
    print(error)
}