4

I am new to Swift, and am not able to figure out how to deserialize a JSON array to an array of Swift objects. I'm able to deserialize a single JSON user to a Swift user object fine, but just not sure how to do it with a JSON array of users.

Here is my User.swift class:

class User {
    var id: Int
    var firstName: String?
    var lastName: String?
    var email: String
    var password: String?

    init (){
        id = 0
        email = ""
    }

    init(user: NSDictionary) {
        id = (user["id"] as? Int)!
        email = (user["email"] as? String)!

        if let firstName = user["first_name"] {
            self.firstName = firstName as? String
        }

        if let lastName = user["last_name"] {
            self.lastName = lastName as? String
        }

        if let password = user["password"] {
            self.password = password as? String
        }
     }
}

Here's the class where I'm trying to deserialize the JSON:

//single user works.
Alamofire.request(.GET, muURL/user)
         .responseJSON { response in
                if let user = response.result.value {
                    var swiftUser = User(user: user as! NSDictionary)
                }
          }

//array of users -- not sure how to do it. Do I need to loop?
Alamofire.request(.GET, muURL/users)
         .responseJSON { response in
                if let users = response.result.value {
                    var swiftUsers = //how to get [swiftUsers]?
                }
          }
3
  • 1
    Why are you not using AlamofireObjectMapper library? Commented May 10, 2016 at 21:59
  • oh, I had no idea I could use that. I thought it was just for REST. I'm going to give it a try. Commented May 10, 2016 at 22:00
  • Also, email is a required field for User. Is it best practice to leave it as required field in Swift, or would it be better to make it an Optional--in case the JSON does not have the value for whatever reason? Commented May 10, 2016 at 22:03

4 Answers 4

2

The best approach is the use Generic Response Object Serialization provided by Alamofire here is an example :

1) Add the extension in your API Manager or on a separate file

    public protocol ResponseObjectSerializable {
        init?(response: NSHTTPURLResponse, representation: AnyObject)
    }

    extension Request {
        public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
            let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
                guard error == nil else { return .Failure(error!) }

                let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
                let result = JSONResponseSerializer.serializeResponse(request, response, data, error)

                switch result {
                case .Success(let value):
                    if let
                        response = response,
                        responseObject = T(response: response, representation: value)
                    {
                        return .Success(responseObject)
                    } else {
                        let failureReason = "JSON could not be serialized into response object: \(value)"
                        let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
                        return .Failure(error)
                    }
                case .Failure(let error):
                    return .Failure(error)
                }
            }

            return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
        }
    }

public protocol ResponseCollectionSerializable {
    static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}

extension Alamofire.Request {
    public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
        let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
            guard error == nil else { return .Failure(error!) }

            let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let result = JSONSerializer.serializeResponse(request, response, data, error)

            switch result {
            case .Success(let value):
                if let response = response {
                    return .Success(T.collection(response: response, representation: value))
                } else {
                    let failureReason = "Response collection could not be serialized due to nil response"
                    let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
                    return .Failure(error)
                }
            case .Failure(let error):
                return .Failure(error)
            }
        }

        return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}

2) update your model object like this:

final class User: ResponseObjectSerializable, ResponseCollectionSerializable {
    let username: String
    let name: String

    init?(response: NSHTTPURLResponse, representation: AnyObject) {
        self.username = response.URL!.lastPathComponent!
        self.name = representation.valueForKeyPath("name") as! String
    }

    static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
        var users: [User] = []

        if let representation = representation as? [[String: AnyObject]] {
            for userRepresentation in representation {
                if let user = User(response: response, representation: userRepresentation) {
                    users.append(user)
                }
            }
        }

        return users
    }
}

3) then you can use it like that :

Alamofire.request(.GET, "http://example.com/users")
         .responseCollection { (response: Response<[User], NSError>) in
             debugPrint(response)
         }

Source: Generic Response Object Serialization

Useful Link: Alamofire JSON Serialization of Objects and Collections

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, will give this a try!
I used that in my last project and I find it an elegant solution, because mapping is already a feature in Almofire, and it's better to not delay to other third party library to avoid any compatibility issues in the future
How do I deal with an optional field? Say it was: let name: String?
Binding from JSON shouldn't be a part of Model. Using protocol extension of model's classes like this 'protocol ResponseObjectSerializable' is not a good idea because in this case you cannot use different variants of parsing to User object. There is new library for Swift JSON binding and validating - Bender github.com/ptiz/bender which excludes such mistakes.
@Prabhu you can use self.name = representation.valueForKeyPath("name") as? String
2

Since you are using Alamofire to make your requests why don't you give a chance to Hearst-DD ObjectMapper it has an Alamofire extension AlamofireObjectMapper. I think it'll save you time!

1 Comment

Looks interesting.
1

I would loop through them then add each user to an array (preferably a property of the VC and not an instance variable) but here is an example.

    Alamofire.request(.GET, "YourURL/users")
        .responseJSON { response in

            if let users = response.result.value {

                for user in users {

                    var swiftUser = User(user: user as! NSDictionary)

                    //should ideally be a property of the VC
                    var userArray : [User]

                    userArray.append(swiftUser)
                }
            }
    }

4 Comments

I'm not exactly sure how the JSON looks from the response so you might need to tweak it a bit but this should give you an idea at least.
With this method I get an error saying Type AnyObject does not conform to protocol SequenceType. Any idea what that is?
Try changing the for loop to "for user in users as! [AnyObject]" instead of just "for user in users"
Using as! [AnyObject] or user as! NSDictionary is not so safe. if let users = response.result.value as? [String: AnyObject] { is more Swifty way.
1

You could also try EVReflection https://github.com/evermeer/EVReflection It's even more simple, i.e. to parse JSON (code snippet taken from EVReflection link):

let json:String = "{
    \"id\": 24, 
    \"name\": \"Bob Jefferson\",
    \"friends\": [{
        \"id\": 29, 
        \"name\": 
        \"Jen Jackson\"}]}"

you can use this class:

class User: EVObject {
    var id: Int = 0
    var name: String = ""
    var friends: [User]? = []
}

in this way:

let user = User(json: json)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.