【问题标题】:Swift JSON decoding with dependent types具有依赖类型的 Swift JSON 解码
【发布时间】:2023-03-11 18:50:01
【问题描述】:

我正在尝试在 Swift 中解码“依赖”的 JSON API 响应。让我们想象一个具有两个端点的虚构 API:

  • /players,返回具有以下属性的对象数组:
    • id,代表玩家ID的整数
    • name,代表玩家姓名的字符串
  • /games,返回具有以下属性的对象数组:
    • name,代表游戏名称的字符串
    • playerId1,一个整数,代表第一个玩家的ID
    • playerId2,一个整数,代表第二个玩家的ID

我用 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"
    }
}

我想将来自/games 的响应解码为具有正确Players 属性的Game 对象数组,因此我使用自定义初始化程序扩展了Game,但我不知道如何检索所有玩家属性:

extension Game {
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        name = try values.decode(String.self, forKey: .name)
        
        //                                             HOW SHOULD I RETRIEVE THE PLAYER'S NAME GIVEN THEIR ID HERE?
        //                                                                         |
        //                                                                         |
        //                                                                         V
        player1 = Player(id: try values.decode(Int.self, forKey: .player1), name: nil)
        player2 = Player(id: try values.decode(Int.self, forKey: .player2), name: nil)
    }
}

总而言之,来自/games 的 API 响应不包含我完全初始化所需的所有信息,所以我应该如何继续:

  • 我可以/应该进行两次 API 调用,一次调用/games,另一次调用players,并在解码前以某种方式合并它们?
  • 我是否应该只初始化我的Players 部分(将未知的东西留给nil)并稍后填写详细信息? (这听起来既危险又麻烦。)
  • 还有什么?

如果你想试验一下,可以找一个完整的例子here

【问题讨论】:

  • Graphql 在这里可能会有所帮助graphql.org
  • @Cristik 感谢您的建议,但我宁愿避免使用 GraphQL,因为我无法控制 API,并且希望避免运行我自己的服务器来模仿 GraphQL 中的原始 API :)跨度>

标签: json swift decodable


【解决方案1】:

我的建议是添加两个惰性实例化属性以从数组中获取 Player 实例。

惰性属性相对于计算属性的好处是该值只计算一次,直到第一次被访问。并且不需要自定义的init(from:) 方法。

struct Game: Decodable {
    let name: String
    let playerId1: Int
    let playerId2: Int

    enum CodingKeys: String, CodingKey { case name, playerId1, playerId2 }

    lazy var player1 : Player? = players.first{ $0.id == playerId1 }
    lazy var player2 : Player? = players.first{ $0.id == playerId2 }
   
}

或者创建一个CodingUserInfoKey

extension CodingUserInfoKey {
    static let players = CodingUserInfoKey(rawValue: "players")!
}

JSONDecoder的扩展名

extension JSONDecoder {
    convenience init(players: [Player]) {
        self.init()
        self.userInfo[.players] = players
    }
}

并在 JSON 解码器的 userInfo 对象中传递 players 数组

let decoder = JSONDecoder(players: players)
let games = try! decoder.decode([Game].self, from: Data(gamesResponse.utf8))
dump(games[0].player1)

现在您可以在init(from: 方法中获取实际玩家。

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

extension Game {
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        guard let players = decoder.userInfo[.players] as? [Player] else { fatalError("No players array available") }
        name = try values.decode(String.self, forKey: .name)
        let playerId1 = try values.decode(Int.self, forKey: .playerId1)
        let playerId2 = try values.decode(Int.self, forKey: .playerId2)
        player1 = players.first{ $0.id == playerId1 }!
        player2 = players.first{ $0.id == playerId2 }!
    }
}

代码假定players 数组包含与playerId 值对应的所有Player 实例。如果不是,则必须将player1player2 声明为可选并删除感叹号。

【讨论】:

  • players 是一些共享的可变全局变量?
  • @vadian 谢谢你的好回答。在没有全局变量的情况下,我怎么能做到这一点, playersgames 将由我的 ViewModel 实例化,那么如何在没有全局变量的情况下定义模型呢? (抱歉,我的示例没有捕捉到这一点)
  • @vadian 非常感谢您的更新,这正是我想要的,我自己永远不会想出这样的结构!最后一个问题:您为什么决定扩展JSONDecoder 而不是子类?将非常具体的init 添加到像JSONDecoder 这样的泛型类中是否不被认为是“不好的做法”?
  • 不,这不是坏习惯。我通过添加便利初始化程序扩展 JSONDecoder 的功能。
猜你喜欢
  • 2020-10-14
  • 1970-01-01
  • 1970-01-01
  • 2020-04-09
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
  • 2018-06-05
  • 1970-01-01
相关资源
最近更新 更多