将 JSON 对象解码为 Swift 中的纯字符串
Decode JSON object to plain String in Swift
上下文
这是我几天前问的后续问题,不过事先阅读并不是绝对必要的。
我有一个 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
s:
对其进行解码
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"
}
}
多亏了我原来问题的答案,我现在可以成功解码 Player
s 和 Game
s,但只有当我使用的响应 String
是内部数组时, 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)!)
问题
如何从 /common
的 API 响应中仅提取 JSON "players"
数组,以便之后我可以将其提供给我的 Player
的 JSON 解码器?
请注意,我不能使用(或者至少我是这么认为的)制作超级Struct
的“常规”Decodable
方法,因为我需要players
在 games
之前解码(这是原始问题的主题)。所以,这不起作用:
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
,它看起来像我想要的,但我不知道如何将其转换为 Data
以将其传递给我的 JSONDecoder
,因为 type(of: playersRaw)
是 __NSArrayM
.
我觉得我没有按照他们应该做的方式做事,所以如果你对一般问题有更“Swifty”的解决方案 (而不是具体如何提取 JSON 数据的子集),它会更好!
您只需要提供一个根结构并获取它的数据播放器。无需解码您不想要的值:
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)
}
您可以通过在 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: { [=10=].id == playerId1 }),
let player2 = players.first(where: { [=10=].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)
}
上下文
这是
我有一个 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
s:
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"
}
}
多亏了我原来问题的答案,我现在可以成功解码 Player
s 和 Game
s,但只有当我使用的响应 String
是内部数组时, 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)!)
问题
如何从 /common
的 API 响应中仅提取 JSON "players"
数组,以便之后我可以将其提供给我的 Player
的 JSON 解码器?
请注意,我不能使用(或者至少我是这么认为的)制作超级Struct
的“常规”Decodable
方法,因为我需要players
在 games
之前解码(这是原始问题的主题)。所以,这不起作用:
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
,它看起来像我想要的,但我不知道如何将其转换为 Data
以将其传递给我的 JSONDecoder
,因为 type(of: playersRaw)
是 __NSArrayM
.
我觉得我没有按照他们应该做的方式做事,所以如果你对一般问题有更“Swifty”的解决方案 (而不是具体如何提取 JSON 数据的子集),它会更好!
您只需要提供一个根结构并获取它的数据播放器。无需解码您不想要的值:
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)
}
您可以通过在 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: { [=10=].id == playerId1 }),
let player2 = players.first(where: { [=10=].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)
}