Decoding dynamic JSON structure in swift 4

后端 未结 4 1560
情深已故
情深已故 2021-01-28 16:43

I have the following issue that I\'m not sure how to handle.

My JSON response can look like this:

{ 
  \"data\": {
      \"id\": 7,
      \"         


        
相关标签:
4条回答
  • 2021-01-28 17:08

    If data can be a single object or an array write a custom initializer which decodes first an array, if a type mismatch error occurs decode a single object. data is declared as an array anyway.

    As token appears only in a single object the property is declared as optional.

    struct ApiData: Decodable {
        let data : [DataObject]
        let error : String?
    
        private enum CodingKeys : String, CodingKey { case data, error }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            do {
                data = try container.decode([DataObject].self, forKey: .data)
            } catch DecodingError.typeMismatch {
                data = [try container.decode(DataObject.self, forKey: .data)]
            }
            error = try container.decodeIfPresent(String.self, forKey: .error)
        }
    }
    
    
    struct DataObject: Decodable {
        let userId : Int
        let token : String?
    
        private enum CodingKeys: String, CodingKey { case userId = "id", token }
    }
    

    Edit: Your code to receive the data can be improved. You should add a better error handling to return also all possible errors:

    func makeDataTaskWith(with urlRequest: URLRequest, completion: @escaping(ApiData?, Error?) -> Void) {
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
    
        session.dataTask(with: urlRequest) {
            (data, response, error) in
            if let error = error { completion(nil, error); return }
    
            if let responseCode = response as? HTTPURLResponse {
                print("Response has status code: \(responseCode.statusCode)")
            }
    
            do {
                let retreived = try NetworkManager.shared.decoder.decode(ApiData.self, from: data!)
                completion(retreived, nil)
            } catch {
                print("Decoder error: ", error)
                completion(nil, error)
            }
            }.resume()
    }
    
    0 讨论(0)
  • 2021-01-28 17:12

    You can try

    struct Root: Codable {
        let data: DataUnion
        let error: String?
    }
    
    enum DataUnion: Codable {
        case dataClass(DataClass)
        case datumArray([Datum])
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let x = try? container.decode([Datum].self) {
                self = .datumArray(x)
                return
            }
            if let x = try? container.decode(DataClass.self) {
                self = .dataClass(x)
                return
            }
            throw DecodingError.typeMismatch(DataUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for DataUnion"))
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .dataClass(let x):
                try container.encode(x)
            case .datumArray(let x):
                try container.encode(x)
            }
        }
    }
    
    struct Datum: Codable {
        let id: Int
    }
    
    struct DataClass: Codable {
        let id: Int
        let token: String
    }
    

    let res = try? JSONDecoder().decode(Root.self, from:data)
    
    0 讨论(0)
  • 2021-01-28 17:13

    If you have only two possible outcomes for your data, an option would be to try and parse data to one of the expected types, if that fails you know that the data is of other type and you can then handle it accordingly.

    See this

    0 讨论(0)
  • 2021-01-28 17:14

    Using power of generic, it simple like below:

    struct ApiData<T: Decodable>: Decodable {
        var data: T?
        var error: String?
    }
    
    struct DataObject: Decodable {
        private var id: Int?
    
        var userId:Int? {
            return id
        }
    }
    

    Use

    if let obj = try? NetworkManager.shared.decoder.decode(ApiData<DataObject>.self, from: data) {
        //Do somthing
    } else if let array = try NetworkManager.shared.decoder.decode(ApiData<[DataObject]>.self, from: data) {
        // Do somthing
    }
    
    0 讨论(0)
提交回复
热议问题