Is there a way to properly deserialize a JSON response to Swift objects resp. using DTOs as containers for fixed JSON APIs?
Something similar to http://james.newtonk
Swift 2: I really like the previous post of Mohacs! To make it more object oriented, i wrote a matching Extension:
extension NSObject{
convenience init(jsonStr:String) {
self.init()
if let jsonData = jsonStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
{
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as! [String: AnyObject]
// Loop
for (key, value) in json {
let keyName = key as String
let keyValue: String = value as! String
// If property exists
if (self.respondsToSelector(NSSelectorFromString(keyName))) {
self.setValue(keyValue, forKey: keyName)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
else
{
print("json is of wrong format!")
}
}
}
custom classes:
class Person : NSObject {
var name : String?
var email : String?
var password : String?
}
class Address : NSObject {
var city : String?
var zip : String?
}
invoking custom classes with JSON string:
var jsonString = "{ \"name\":\"myUser\", \"email\":\"user@example.com\", \"password\":\"passwordHash\" }"
let aPerson = Person(jsonStr: jsonString)
print(aPerson.name!) // Output is "myUser"
jsonString = "{ \"city\":\"Berlin\", \"zip\":\"12345\" }"
let aAddress = Address(jsonStr: jsonString)
print(aAddress.city!) // Output is "Berlin"
In Swift 4, You can use the Decoding, CodingKey protocols to deserialize the JSON response:
Create the class which confirm the decodable protocol
class UserInfo: Decodable
Create members of the class
var name: String
var email: String
var password: String
Create JSON key enum which inherits from CodingKey
enum UserInfoCodingKey: String, CodingKey {
case name
case password
case emailId
}
Implement init
required init(from decoder: Decoder) throws
The whole class look like :
Call Decoder
// jsonData is JSON response and we get the userInfo object
let userInfo = try JsonDecoder().decode(UserInfo.self, from: jsonData)
I am expanding upon Mohacs and Peter Kreinz's excellent answers just a bit to cover the array of like objects case where each object contains a mixture of valid JSON data types. If the JSON data one is parsing is an array of like objects containing a mixture of JSON data types, the do loop for parsing the JSON data becomes this.
// Array of parsed objects
var parsedObjects = [ParsedObject]()
do {
let json = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as [Dictionary<String, AnyObject>]
// Loop through objects
for dict in json {
// ParsedObject is a single instance of an object inside the JSON data
// Its properties are a mixture of String, Int, Double and Bool
let parsedObject = ParsedObject()
// Loop through key/values in object parsed from JSON
for (key, value) in json {
// If property exists, set the value
if (parsedObject.respondsToSelector(NSSelectorFromString(keyName))) {
// setValue can handle AnyObject when assigning property value
parsedObject.setValue(keyValue, forKey: keyName)
}
}
parsedObjects.append(parsedObject)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
Using quicktype, I generated your model and serialization helpers from your sample:
import Foundation
struct User: Codable {
let name: String
let email: String
let password: String
}
extension User {
static func from(json: String, using encoding: String.Encoding = .utf8) -> OtherUser? {
guard let data = json.data(using: encoding) else { return nil }
return OtherUser.from(data: data)
}
static func from(data: Data) -> OtherUser? {
let decoder = JSONDecoder()
return try? decoder.decode(OtherUser.self, from: data)
}
var jsonData: Data? {
let encoder = JSONEncoder()
return try? encoder.encode(self)
}
var jsonString: String? {
guard let data = self.jsonData else { return nil }
return String(data: data, encoding: .utf8)
}
}
Then parse User
values like this:
let user = User.from(json: """{
"name": "myUser",
"email": "user@example.com",
"password": "passwordHash"
}""")!
HandyJSON
is another option to deal with JSON for you. https://github.com/alibaba/handyjson
It deserials JSON to object directly. No need to specify mapping relationship, no need to inherit from NSObject. Just define your pure-swift class/struct, and deserial JSON to it.
class Animal: HandyJSON { var name: String? var id: String? var num: Int? required init() {} } let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" if let animal = JSONDeserializer.deserializeFrom(jsonString) { print(animal) }
You do this by using NSJSONSerialization. Where data is your JSON.
First wrap it in an if statement to provide some error handling capablity
if let data = data,
json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] {
// Do stuff
} else {
// Do stuff
print("No Data :/")
}
then assign them:
let email = json["email"] as? String
let name = json["name"] as? String
let password = json["password"] as? String
Now, This will show you the result:
print("Found User iname: \(name) with email: \(email) and pass \(password)")
Taken from this Swift Parse JSON tutorial. You should check out the tutorial as it goes a lot more in depth and covers better error handling.