【问题标题】:Decoding JSON structures with minor differences into just one format将具有细微差异的 JSON 结构解码为一种格式
【发布时间】:2021-11-19 20:12:28
【问题描述】:

我有三个 API 端点,它们产生相同的最终结构,但是从 API 接收到的完整 JSON 结构在每个端点中都有点不同。

第一个 JSON 结果

{
    "tracks": [{
            "name": "Never Gonna Give You Up"
     }]
}

第二个 JSON 结果

{
    "items": [{
            "name": "Never Gonna Give You Up"
     }]
}

第三个 JSON 结果

{
    "items": [{
        "track": {
            "name": "Never Gonna Give You Up"
        }
    }]
}

我希望它们都像这样

{
    "tracks": [{
        "name": "Never Gonna Give You Up"
    }]
}

为此,我使用了三种不同的结构:


第一:

struct TopHitsTrackResponse: Decodable {
  var tracks: [Track]  
}

第二:

struct FavoritesTrackResponse: Decodable {
  var tracks: [Track]

  enum CodingKeys: String, CodingKey {
    case tracks = "items"
  }
}

第三个​​是下面的代码。

我的尝试

我已经成功地使第一个和第二个 JSON 结果看起来与想要的结果完全一样。然而,第三个对我来说有点复杂。这是我尝试过但没有成功的方法。

  struct NestedTrackResponse: Decodable {
    let tracks: [Track]

    enum CodingKeys: String, CodingKey {
      case tracks = "items"
    }

    enum TrackKeys: String, CodingKey {
      case track
    }

    init(from decoder: Decoder) throws {
      let outerContainer = try decoder.container(keyedBy: CodingKeys.self)
      let trackContainer = try outerContainer.nestedContainer(keyedBy: TrackKeys.self,
                                                              forKey: .tracks)

      self.tracks = try trackContainer.decode([Track].self, forKey: .track)
    }

    struct Track: Decodable {
      var name: String
    }
  }

我正在用这个函数调用 API

  AF.request(urlRequest)
    .validate()
    .responseDecodable(of: NestedTrackResponse.self) { response in
       // It's always resulting in fatal error
       guard let data = response.value else {
         fatalError("Error receiving tracks from API.")
       }
    }

  // - `AF` is Alamofire, but I've already tried not using it, 
  // and the error persists

  // - `urlRequest` is just the URL of the API and the API key, 
  // doesn't really matter for this problem

并收到此错误

本应解码 Dictionary,但找到了一个数组。

【问题讨论】:

    标签: json swift decoding


    【解决方案1】:

    正如我在另一个 answer 中所说,建议坚持使用从 API 收到的 JSON 结构,这样可以避免将来出现很多麻烦,并且具有定义明确的网络层。

    struct Track: Decodable {
        var name: String
    }
    
    struct TopHitsTrackResponse: Decodable {
      var tracks: [Track]  
    }
    
    struct FavoritesTrackResponse: Decodable {
      var items: [Track]
    }
    
    // though you should name this by the API name,
    // like with the other two
    struct NestedTrackResponse: Decodable {
        var items: [Item]
    
        struct Item: Decodable {
            var track: Track
        }
    }
    

    基本上上面是你的网络层。现在是业务层,它将尝试从每种响应中提取轨迹。

    您可以使用协议来实现它,但功能也可以正常工作(如果不是更好的话):

    func tracks(from response: TopHitsTrackResponse) -> [Track] {
        response.tracks
    }
    
    func tracks(from response: FavoritesTrackResponse) -> [Track] {
        response.items
    }
    
    func tracks(from response: NestedTrackResponse) -> [Track] {
        response.items.map(\.track)
    }
    

    然后,在您的 API 回调中,您只需调用 tracks(from:),即可提取最想要的结果。

    【讨论】:

    • 很好的答案。我将在未来的项目中使用它。我只是不会在我正在处理的当前项目中使用它,因为我试图只用问题的本质方面来提出问题。但是,我省略了一些特殊性,以避免使问题对我当前的问题过于具体。无论如何,在大多数情况下,您的代码肯定会非常出色。
    【解决方案2】:

    您必须使用 if let 表达式编写自定义初始化程序来区分这些情况。

    struct Response : Decodable {
        let tracks : [Track]
        
        private enum CodingKeys : String, CodingKey { case items, tracks }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            if let tracks = try? container.decode([Track].self, forKey: .items) {
                self.tracks = tracks
            } else if let tracks = try? container.decode([Track].self, forKey: .tracks) {
                self.tracks = tracks
            } else if let items = try? container.decode([Item].self, forKey: .items) {
                self.tracks = items.map(\.track)
            } else {
                throw DecodingError.dataCorruptedError(forKey: .items, in: container, debugDescription: "Unsupported JSON structure")
            }
        }
    }
    
    struct Item : Decodable {
        let track : Track
    }
    
    struct Track : Decodable {
        let name : String
    }
    

    【讨论】:

    • 出色的工作@vadian !我不仅按照我想要的方式工作,而且超越了我试图做的事情并进一步简化了它。在获得足够的声望来启用它后,我会给你一个支持。感谢您的关注和知识!
    • 只是一个小问题。你能解释一下我在我所呈现的结构上做错了什么(NestedTrackResponse)吗?
    • 您的方法不起作用,因为嵌套容器仅出现在其中一种情况下。
    猜你喜欢
    • 1970-01-01
    • 2016-06-27
    • 2015-03-17
    • 2017-03-18
    • 2019-08-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多