Ric*_*hiy 5 json ios swift codable decodable
我有以下代码来提取编码密钥中包含的JSON:
let value = try! decoder.decode([String:Applmusic].self, from: $0["applmusic"])
这成功处理了以下JSON:
{
  "applmusic":{
    "code":"AAPL",
    "quality":"good",
    "line":"She told me don't worry",
}
但是,无法使用以下编码密钥提取JSON applmusic:
{
  "applmusic":{
    "code":"AAPL",
    "quality":"good",
    "line":"She told me don't worry",
  },
  "spotify":{
    "differentcode":"SPOT",
    "music_quality":"good",
    "spotify_specific_code":"absent in apple"
  },
  "amazon":{
    "amzncode":"SPOT",
    "music_quality":"good",
    "stanley":"absent in apple"
  }
}
数据模型applmusic,spotify并且amazon是不同的.但是,我只需要提取applmusic和省略其他编码密钥.
我的Swift数据模型如下:
public struct Applmusic: Codable {
    public let code: String
    public let quality: String
    public let line: String
}
API以完整的JSON响应,我不能要求它只给我所需的字段.
如何只解码json的特定部分?看起来,这Decodable需要我先对整个json进行反序列化,所以我必须知道它的完整数据模型.
显然,其中一个解决方案是创建一个单独的Response模型来包含applmusic参数,但它看起来像一个黑客:
public struct Response: Codable {
    public struct Applmusic: Codable {
        public let code: String
        public let quality: String
        public let line: String
    }
    // The only parameter is `applmusic`, ignoring the other parts - works fine
    public let applmusic: Applmusic
}
你能提出一个更好的方法来处理这样的JSON结构吗?
多一点洞察力
我在通用扩展中使用了以下技术,它为我自动解码API响应.因此,我更倾向于概括一种处理此类情况的方法,而无需创建Root结构.如果我需要的密钥在JSON结构中是3层深度怎么办?
这是为我解码的扩展:
extension Endpoint where Response: Swift.Decodable {
  convenience init(method: Method = .get,
                   path: Path,
                   codingKey: String? = nil,
                   parameters: Parameters? = nil) {
    self.init(method: method, path: path, parameters: parameters, codingKey: codingKey) {
      if let key = codingKey {
        guard let value = try decoder.decode([String:Response].self, from: $0)[key] else {
          throw RestClientError.valueNotFound(codingKey: key)
        }
        return value
      }
      return try decoder.decode(Response.self, from: $0)
    }
  }
}
API的定义如下:
extension API {
  static func getMusic() -> Endpoint<[Applmusic]> {
    return Endpoint(method: .get,
                    path: "/api/music",
                    codingKey: "applmusic")
  }
}
更新:我JSONDecoder对这个答案做了扩展,可以在这里查看:https : //github.com/aunnnn/NestedDecodable,它允许您使用键路径对任何深度的嵌套模型进行解码。
您可以像这样使用它:
let post = try decoder.decode(Post.self, from: data, keyPath: "nested.post")
您可以制作一个Decodable包装器(例如,ModelResponse在这里),然后将所有逻辑放入其中带有键的嵌套模型中:
struct DecodingHelper {
    /// Dynamic key
    private struct Key: CodingKey {
        let stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
            self.intValue = nil
        }
        let intValue: Int?
        init?(intValue: Int) {
            return nil
        }
    }
    /// Dummy model that handles model extracting logic from a key
    private struct ModelResponse<NestedModel: Decodable>: Decodable {
        let nested: NestedModel
        public init(from decoder: Decoder) throws {
            let key = Key(stringValue: decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String)!
            let values = try decoder.container(keyedBy: Key.self)
            nested = try values.decode(NestedModel.self, forKey: key)
        }
    }
    static func decode<T: Decodable>(modelType: T.Type, fromKey key: String) throws -> T {
        // mock data, replace with network response
        let path = Bundle.main.path(forResource: "test", ofType: "json")!
        let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
        let decoder = JSONDecoder()
        // ***Pass in our key through `userInfo`
        decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!] = key
        let model = try decoder.decode(ModelResponse<T>.self, from: data).nested
        return model
    }
}
您可以通过传递您所需的关键userInfo的JSONDecoder("my_model_key")。然后将其转换为动态Key内部,ModelResponse以实际提取模型。
然后,您可以像这样使用它:
let appl = try DecodingHelper.decode(modelType: Applmusic.self, fromKey: "applmusic")
let amazon = try DecodingHelper.decode(modelType: Amazon.self, fromKey: "amazon")
let spotify = try DecodingHelper.decode(modelType: Spotify.self, fromKey: "spotify")
print(appl, amazon, spotify)
完整代码:https : //gist.github.com/aunnnn/2d6bb20b9dfab41189a2411247d04904
在玩了更多之后,我发现您可以使用此修改轻松地解码任意深度的键ModelResponse:
private struct ModelResponse<NestedModel: Decodable>: Decodable {
    let nested: NestedModel
    public init(from decoder: Decoder) throws {
        // Split nested paths with '.'
        var keyPaths = (decoder.userInfo[CodingUserInfoKey(rawValue: "my_model_key")!]! as! String).split(separator: ".")
        // Get last key to extract in the end
        let lastKey = String(keyPaths.popLast()!)
        // Loop getting container until reach final one
        var targetContainer = try decoder.container(keyedBy: Key.self)
        for k in keyPaths {
            let key = Key(stringValue: String(k))!
            targetContainer = try targetContainer.nestedContainer(keyedBy: Key.self, forKey: key)
        }
        nested = try targetContainer.decode(NestedModel.self, forKey: Key(stringValue: lastKey)!)
    }
然后,您可以像这样使用它:
let deeplyNestedModel = try DecodingHelper.decode(modelType: Amazon.self, fromKey: "nest1.nest2.nest3")
从这个json:
{
    "apple": { ... },
    "amazon": {
        "amzncode": "SPOT",
        "music_quality": "good",
        "stanley": "absent in apple"
    },
    "nest1": {
        "nest2": {
            "amzncode": "Nest works",
            "music_quality": "Great",
            "stanley": "Oh yes",
            "nest3": {
                "amzncode": "Nest works, again!!!",
                "music_quality": "Great",
                "stanley": "Oh yes"
            }
        }
    }
}
完整代码:https : //gist.github.com/aunnnn/9a6b4608ae49fe1594dbcabd9e607834
| 归档时间: | 
 | 
| 查看次数: | 3717 次 | 
| 最近记录: |