ObjectMapper and arrays with unique keys - swift

Im running into an issue with ObjectMapper and the way a json response is coming back from a server.
Is there a way to have ObjectMapper parse this response and create an array of rooms?
Currently I cannot straight map the json as the keys are unique, and will change every request, as well I cannot use the ObjectMapper way of accessing nested objects with dot notation, rooms.0.key as I am unsure how many objects there are, as well as the key that will show up.
Is there a simple way of parsing this reponse.
"rooms": {
"165476": {
"id": "165476",
"area": {
"square_feet": 334,
"square_meters": 31
},
"165477": {
"id": "165477",
"area": {
"square_feet": 334,
"square_meters": 31
},

Use Codable to parse the above JSON response.
Models:
struct Rooms: Codable {
let rooms: [String:Room]
}
struct Room: Codable {
let id: String
let area: Area
}
struct Area: Codable {
let squareFeet: Int
let squareMeters: Int
}
Parse the JSON data like so,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Rooms.self, from: data)
print(response.rooms.first?.key) //for the above json it will print "165476"
} catch {
print(error)
}

Related

Decoding raw data from URLSession the right way in Swift [duplicate]

This question already has an answer here:
iOS Generic type for codable property in Swift
(1 answer)
Closed 2 years ago.
The response for most of the endpoints in my API is something like this -
{
"status":"success",
"data":[
{
"id":"1",
"employee_name":"Tiger Nixon",
"employee_salary":"320800",
"employee_age":"61",
"profile_image":""
},
{
"id":"2",
"employee_name":"Garrett Winters",
"employee_salary":"170750",
"employee_age":"63",
"profile_image":""
}
]
}
And this is what my Employee model looks like
struct Employee: Codable {
let id, employeeName, employeeSalary, employeeAge: String
let profileImage: String?
enum CodingKeys: String, CodingKey {
case id
case employeeName = "employee_name"
case employeeSalary = "employee_salary"
case employeeAge = "employee_age"
case profileImage = "profile_image"
}
}
typealias Employees = [Employee]
I just want to extract the data part of the API response using JSONDecoder and pass it to my completion handler
completionHandler(try? JSONDecoder().decode(Employees.self, from: data), response, nil)
I was able to get around by creating a struct Employees like this -
struct Employees: Codable {
let status: String
let data: [Employee]
}
But this is just a workaround and I would have to do it for every almost every model. So is there a better and less redundant way of extracting data from the response?
What I would do if I were you is just create a template-based wrapper and use it for all of your model objects.
struct ModelWrapper<T: Codable>: Codable {
let status: String
let data: [T]
}
let decoder = JSONDecoder()
let wrapper = try decoder.decode(ModelWrapper<Employee>.self, from: json)

How to combine multiple seperate json results from loop then return swift

I want to combine 3 json results into one for looping into later.
I have tried every single combination on the internet yet I'm still stuck a dead end. I am calling an API call that in response, receives json data. I want to get the first 3 results and return that data to the calling method however, I just cannot find a way to find the desired response.
var data: JSON = []
func getData(forKey url: URL, completion: #escaping ([JSON]) -> Void){
AF.request(url).responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let swiftyJsonVar = JSON(responseData.result.value!)
for loopID in 0 ... 2{
print(swiftyJsonVar["results"][loopID])
}
}
return completion([self.data])
}
}
My desired results are
[
{
"name": "james",
"location": "Mars"
},
{
"name": "james",
"location": "Mars"
},
{
"name": "james",
"location": "Mars"
}
]
When, in my loop, I receive, x 3
{
"name": "james",
"location": "Mars"
}
For starters, I'd put in a breakpoint and see what your response object looks like from whatever endpoint you're hitting. Perhaps those really are the first three elements of the array?
Moving along to the matter at hand, I would use the Codable protocol for decoding your response. I'll skip the Alamofire part and focus solely on decoding the object. Using your example, here's what the struct would look like:
struct ResponseObject: Codable {
let name: String
let location: String
}
I would avoid using the bang operator (!) in your code, as you'll throw an exception if you tell the compiler it's 100% guaranteed money in the bank the object's not nil and it actually is. Instead, unwrap optionals.
You'll need a landing spot for your decoded data, so you could declare an empty array of responses, as follows:
var arrayOfObjects: [ResponseObject] = []
Then, you'd just declare a decoder and decode your data:
let decoder = JSONDecoder()
do {
if let data = rawData {
arrayOfObjects = try decoder.decode([ResponseObject].self, from: data)
print("number of objects:", arrayOfObjects.count)
let slice = arrayOfObjects.prefix(3)
arrayOfObjects = Array(slice)
print("number of objects after slicing the array:", arrayOfObjects.count)
}
} catch {
print(error)
}
Instead of looping through the array, just grab the first three elements of the array with .prefix(3). Fiddling with this just now, I tried prefixing the first 10 elements of an array with 4 and it didn't generate an exception, so I think that should work without checking the count of the array first.
I would suggest taking a look through Swift documentation online re: Arrays or you could get a pretty sweet Mac OS app called Dash that lets you load up docsets for a bunch of languages on your machine locally.
Good luck!

Swift 4 Json Decoding, Which is the best/latest method, Using Codable or Decodable?

In Swift 4.1 we can decode JSON like this:
struct Person: Decodable {
let id: Int
let name: String
let imageUrl: String
}
let people = [Person]()
func parseJSON(data: Data) {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStratergy = .convertFromSnakeCase
self.people = try decoder.decode([Person].self, from: data)
} catch let error {
print(error as? Any)
}
}
Q1. What is the difference if I use Codable instead of Decodable? Which is better?
Q2. How to use decodeIfPresent here?
Q3. How to decode if there is no key present in the JSON?
The best way to encode and decode JSON in Swift4
Here is the JSON representation of a simple object User, let’s see how to deserialise those data into an object.
{
"id": 13,
"firstname" : "John",
"lastname" : "Doe",
"email" : "john.doe#lost.com"
}
Decoding JSON into object
I use a struct type to represent my object and include the protocol Decodable to allow deserialisation.
struct User : Decodable {
let id : Int
let firstname : String
let lastname : String
let email : String
}
Now we’re ready to decode it using JSONDecoder.
// assuming our data comes from server side
let jsonString = "{ \"id\": 13, \"firstname\" : \"John\", \"lastname\" : \"Doe\", \"email\" : \"john.doe#lost.com\" }"
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(User.self, from: jsonData)
print("Hello \(user.firstname) \(user.lastname)")
} catch {
print("Unexpected error: \(error).")
}
Pretty easy right? Let’s see how to serialise it now.
Encoding object into JSON
First, we need update our struct to allow encoding. To do so, we just need to include protocol Encodable.
struct User : Encodable, Decodable {
...
}
Our object is ready to be serialised back to JSON. We follow the same process as before, using JSONEncoder this time. In that case, I’ll also convert the data into a String to be sure it’s working
// assuming we have an object to serialise
That’s still pretty easy! So what is Codable all about?
Well, Codable, is just alias of Encodable and Decodable protocols as you can see in it’s definition
public typealias Codable = Decodable & Encodable
If you don’t want your JSON keys to drive your naming, you can still customise them using CodingKeys. Described as an enum, it will automatically be picked up while encoding / decoding
struct User : Codable {
var id : Int
var firstname : String
var lastname : String
var email : String?
// keys
private enum CodingKeys: String, CodingKey {
case id = "user_id"
case firstname = "first_name"
case lastname = "family_name"
case email = "email_address"
}
}
To go further
https://medium.com/#phillfarrugia/encoding-and-decoding-json-with-swift-4-3832bf21c9a8
https://benoitpasquier.com/encoding-decoding-json-swift4/
Answer of Question 3: https://www.calhoun.io/how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided/
Please if possible try to post separated questions, it is easier to answer and to read after.
For your first question: Codable is a protocol extending Encodable and Decodable.
Decodable is to parse from data to your models. For example from JSON data to structs.
Encodable is to make data from your models.
Codable contains both.

Swift Codable: Continue parsing object after nested object error

My app is, like oh so many apps, retrieving JSON from an API and converting it using the new Codable protocol in Swift 4. Most of the time, this works fine and as expected. However, sometimes the API will send me unexpected garbage. Incorrect types, arrays with just null inside, that kind of thing.
The problem is that the objects involved can be large and complicated, and when I'm parsing a child object and it fails, the whole object fails, all the way up to the root. I'm including a very simple playground example to illustrate the concept; the actual objects involved are way more complex.
let goodJSON = """
{
"name": "Fiona Glenanne",
"vehicles": [
{
"make": "Saab",
"model": "9-3",
"color": "Black"
},
{
"make": "Hyundai",
"model": "Genesis",
"color": "Blue"
}
]
}
"""
let goodJSONData = goodJSON.data(using: .utf8)!
let badJSON = """
{
"name": "Michael Westen",
"vehicles": {
"make": "Dodge",
"model": "Charger",
"color": "Black"
}
}
"""
let badJSONData = badJSON.data(using: .utf8)!
struct Character: Codable {
let name: String
let vehicles: [Vehicle]
}
struct Vehicle: Codable {
let make: String
let model: String
let color: String
}
do {
let goodCharacter = try JSONDecoder().decode(Character.self, from: goodJSONData)
print(goodCharacter)
} catch {
print(error)
}
do {
let badCharacter = try JSONDecoder().decode(Character.self, from: badJSONData)
print(badCharacter)
} catch DecodingError.typeMismatch(let type, let context) {
print("Got \(type); \(context.debugDescription) ** Path:\(context.codingPath)")
} catch {
print("Caught a different error: \(error)")
}
Output:
Character(name: "Fiona Glenanne", vehicles: [__lldb_expr_20.Vehicle(make: "Saab", model: "9-3", color: "Black"), __lldb_expr_20.Vehicle(make: "Hyundai", model: "Genesis", color: "Blue")])
Got Array<Any>; Expected to decode Array<Any> but found a dictionary instead. ** Path:[CodingKeys(stringValue: "vehicles", intValue: nil)]
vehicles is expected to be an array of objects, but in the badJSON case, it is a single object, which causes the .typeMismatch exception and kills the parsing right there.
What I'm looking for is a way to allow errors like this one to kill the parsing for the child object only and allow parsing of the parent object to continue. I'm looking to do this in a generic fashion, so I don't have to special case each and every object in my app to specifically handle whatever bad data the API delivers. I'm not sure if there even is a solution for this, I haven't had any luck finding anything, but it would sure improve my quality of life if there is. Thanks!
you can try to custom the init(from decoder: Decoder) as suggested in comments it would be something like this,
struct Character: Codable {
let name: String
let vehicles: [Vehicle]
private enum CodingKeys: String, CodingKey { case name, vehicles }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
do {
let vehicle = try container.decode(Vehicle.self, forKey: .vehicles)
vehicles = [vehicle]
} catch DecodingError.typeMismatch {
vehicles = try container.decode([Vehicle].self, forKey: .vehicles)
}
}

Decodable returning object

I have a decodable class:
struct AuthenticationResponse : Decodable {
var status: String
var error: Error
var access_token: String? = ""
var expires_in: Double? = 0
var token_type: String? = ""
var scope: String? = ""
var refresh_token: String? = "
}
struct Error : Decodable {
var desc: String
var code: String
}
In the Error class I have:
And to decode to this class, I have:
URLSession.shared.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
if let jsonData = data{
let decoder = JSONDecoder()
print("hey")
print("response: \(String(data:jsonData, encoding:.utf8))")
completion(try! decoder.decode(AuthenticationResponse.self, from: jsonData))
}
}.resume()
As some of the responses I receive are (Success response):
{
“status”: “SUCCESS” “error”: null, "access_token":
"MWVmOWQxMDYwMjQyNDQ4NzQyNTdkZjQ3NmI4YmVjMGZjZGM5N2IyZmNkOTA1 N2M0NDUzODEwYjM5ZWQyNGNkZg",
"expires_in": 3600, "token_type": "bearer", "scope": null,
"refresh_token":
"ZGEwOGZiOWZhMzhhYjBmMzAyOGRmZTA5NjJhMjY2MTk3YzMyMmE1ZDlkNWI2N mJjYmIxMjNkMjE1NWFhNWY0Mg"
}
And then a failed response just contains an error object with desc and code in it.
What i am trying to achieve is a decodable class suitable for both scenarios (When a response is successful and failed) however im not sure how to achieve this. I'm aware i can make 2 separate decodable classes but this would make things messier as i'd have to determine if the response is an error and populate to return different classes.
Does anyone know how i should acheive this>
I will give it a try, but first we need to sort out what I consider a somewhat shoddy question. Since Error is the name of a (famous and widely used) protocol it should be renamed and since you want to be able to leave it empty in your AuthenticationResponse it must obviously be an optional there (bearing the question why it is in the Response at all, but I will leave this aside). This leaves us with the following:
struct AuthError : Decodable {
var desc: String
var code: String
}
struct AuthenticationResponse : Decodable {
var status: String
var error: AuthError?
var access_token: String? = ""
var expires_in: Double? = 0
var token_type: String? = ""
var scope: String? = ""
var refresh_token: String? = ""
}
Then we need some example data for the two relevant cases in question, I used:
let okData = """
{
"status": "SUCCESS",
"error": null,
"access_token":
"MWVmOWQxMDYwMjQyNDQ4NzQyNTdkZjQ3NmI4YmVjMGZjZGM5N2IyZmNkOTA1N2M0NDUzODEwYjM5ZWQyNGNkZg",
"expires_in": 3600,
"token_type": "bearer",
"scope": null,
"refresh_token":
"ZGEwOGZiOWZhMzhhYjBmMzAyOGRmZTA5NjJhMjY2MTk3YzMyMmE1ZDlkNWI2NmJjYmIxMjNkMjE1NWFhNWY0Mg"
}
""".data(using: .utf8)!
let errData = """
{
"desc": "username or password incorrect",
"code": "404"
}
""".data(using: .utf8)!
Now we can define a single enum return type which allows for all our cases:
enum AuthResult {
case ok(response: AuthenticationResponse)
case authError(error: AuthError)
case parseError(description: String)
case fatal
}
which finally allows us to write our parse function for the received authentication data:
func parse(_ jsonData:Data) -> AuthResult {
let decoder = JSONDecoder()
do {
let authRes = try decoder.decode(AuthenticationResponse.self, from: jsonData)
return .ok(response: authRes)
} catch {
do {
let errRes = try decoder.decode(AuthError.self, from: jsonData)
return .authError(error: errRes)
} catch let errDecode {
return .parseError(description: errDecode.localizedDescription)
}
}
}
All this in a Playground will permit usage as in
switch parse(okData) {
case let .ok(response):
print(response)
case let .authError(error):
print(error)
case let .parseError(description):
print("You threw some garbage at me and I was only able to \(description)")
default:
print("don't know what to do here")
}
That is still elegant compared to the mess you would make in most other languages, but the call is still out on wether it would not make more sense to just define AuthenticationResponse as the (regular) return type of the parse function and provide the rest by throwing some enum (conforming to Error) and some suitable payload.
Coming (mainly) from Java I still shun from using exceptions as "somewhat" regular control flow (as in a "regular" login failure), but given Swifts much more reasonable approach to exceptions this might have to be reconsidered.
Anyways, this leaves you with a function to parse either case of your services replies and a decent way to handle them in a "uniform" manner. As you might not be able to modify the behaviour of the service handling your request this might be the only viable option. However, if you are able to modify the service you should strive for a "uniform" reply that would be parseable by a single call to JSONDecoder.decode. You would still have to interpret the optionals (as you should in the above example, since they are still a pain to work with, even given Swifts brilliant compiler support forcing you to "do the right thing"), but it would make your parsing less error prone.