0

My company's API is.... unique. The only thing that is 100% is that all responses are encapsulated in a myCompany object. And then either in a Data object or an Errors object. After that it's pretty much fair game. I am working with our head API developer, but it's a slow change because the code was written 10 years ago etc.. etc..

{ "myCompany": {
        "Errors": [{
            //Various error message key/values based upon who ever created it
        }]
    }
}


{ "myCompany": {
        "Data": {
            //any key/value of arrays and objects that I want to turn into a Codable
        }
    }
}

Is there a way for me to test if a root level key exists before I try to JSON Decode into a Codeable Struct? I hope that makes sense.

For example if the json data has the root level key of jsonData["myCompany"]["Data"] and MySpecialClass is what requested it, I could just send the value of jsonData["myCompany"]["Data"] to MySpecialClass so it can json decode

let mydata = try? JSONDecoder().decode(MySpecialClassData.self, from: jsonData["myCompany"]["Data"])

It use to be in Objective-C I could just test for the keys in a dictionary to accomplish this. I don't know to this in Swift yet.

do {
    guard let jsonData = try JSONSerialization.jsonObject(with: urlSessionDataTaskResponse, options: [.allowFragments]) as? [[String:Any]] else  {
    print("Error jsonData was not in the correct format. Surprise. Surprise.");
    if let str = String(data: dataResponse, encoding: String.Encoding.utf8) {
        print("Raw Data: \(str)")
    }
        return
    }

    // Something like this
    // if   jsonData["myCompany"]["Data"]     
    // else if  jsonData["myCompany"]["Errors"]     
    // else who knows throw an error            

  ///I have tried But this doesn't seem to work
 guard let myCompany = jsonData["myCompany"] as? [String:Any] else { return }

} catch {
    print("ERROR: \(error)")
}

All of the tutorials I have seen require me to put the data into a Codable before doing any.

Any thoughts?

user-44651
  • 3,924
  • 6
  • 41
  • 87
  • https://stackoverflow.com/questions/46740160/swift-decodable-optional-key You can have `struct MyCompanyCodableStruct { errors: [MySpecialStructError]?; data: MySpecialStructData?}`. – Larme Oct 17 '18 at 13:34
  • do you have all the sub json decoded correctly ? in case its an error or not ? – Mohmmad S Oct 17 '18 at 13:34

4 Answers4

2

In case that you have constructed the Error correctly and the Data correctly you can use both of them inside a parent struct as optionals and decode that, your code could be looking like this

struct ParentResponse: Codable {
data: MySpecialClassData?
error: MySpecialErrorData?  // or an array of it would be like this [MySpecialErrorData]?

}

Now you can decode this ParentResponse and check if the data was decoded or not by checking if its nil value simply optional chaining.

Mohmmad S
  • 5,001
  • 4
  • 18
  • 50
  • So in your case one of them has to be nil, simply check if error was nil then use data, else check out what is the error – Mohmmad S Oct 17 '18 at 13:39
0

Your response is a dictionary which is [String:Any], but you try to decode it with a dictionary array [[String:Any]]. Inside your response there is another dictionary, with a key of "Errors" or "Data" depending of the situation.

guard let jsonData = try JSONSerialization.jsonObject(with: urlSessionDataTaskResponse, options: [.allowFragments]) as? [String:Any] else  {
print("Error jsonData was not in the correct format. Surprise. Surprise.");
if let str = String(data: dataResponse, encoding: String.Encoding.utf8) {
        print("Raw Data: \(str)")
    }
    return
}
if let myCompany = jsonData["myCompany"] as? [String : Any]{
    if let data = myCompany["Data"] as? [Any]{
        //you get your data
    }
    else if let errors = myCompany["Errors"] as? [Any]{
        //you get your errors
    }
}
} catch {
print("ERROR: \(error)")
}

This should work.

0

encode the data to dictionary:

extension Encodable {
    var dictionary: [String: Any]? {
        guard let data = try? JSONEncoder().encode(self) else { return nil}
        return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap{ $0 as? [String: Any]}
    }
}

get our object reflection:

protocol DataProtocol: Codable {
    func getMirror() -> Mirror
}
extension DataProtocol {
    func getMirror() -> Mirror {
        return Mirror(reflecting: self)
    }
}

// some object:
struct dateModal: DataProtocol {
    var name: String?
    var age: Int?
}

//return false if key not found in json:
private func keyValidation<T: DataProtocol>(with dataModel: T?) -> Bool {
    guard let dictionaryModel = dataModel.dictionary, let mirror = dataModel?.getMirror() else { return false }
    for attr in mirror.children {
        guard dictionaryModel[attr.label ?? ""] != nil else { return false }
    }
    return true
}

//decode the data:

.....
let dataModel = try JSONDecoder().decode(AnyClass, from: data)

//then check for the key:

guard keyValidation(with: dataModel) else { return }
0

I like when API is structured (like described here for example: https://medium.com/makingtuenti/indeterminate-types-with-codable-in-swift-5a1af0aa9f3d), however, some APIs are not, but you can make an intermediate class that include every field and then cast it to an enum:

public final class MyCompanyResponseUnion: Decodable {
    public let error: Error?
    public let data: Data?
    public let otherData: Data?

    public func toEnum() -> MyCompanyResponse {
        if let error = error {
            return MyCompanyResponse.failure(
                MyCompanyResponse.Failure(
                    error: error
                )
            )
        } else if let data = data { // e.g. if `otherData` is optional and `data` is not
            return MyCompanyResponse.success(
                MyCompanyResponse.Success(
                    data: data,
                    otherData: otherData
                )
            )
        }
    }
}
artyom.razinov
  • 610
  • 3
  • 17