After receiving a JSON payload from a web request, I need to parse out the data into variables in an NSObject subclass.
My variables are declared in the object:
var name:String?
var email:String?
var aboutMe:String?
Then I start parsing through all the possible data the JSON may return:
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
This is becoming rather long as we have a lot of data.
I was wanting to shorted it by using a Dictionary containing the JSON key and the variable like this:
let varMap:[String:Any?] = [
"Name": self.name,
"Email": self.email,
"AboutMe": self.aboutMe
]
Then iterating over them like this:
for (key, var variable) in varMap
{
if let string = json[key]
{
variable = string
}
}
The problem here is that the Dictionary copies the value from the variable and not a pointer to it, so setting it to a new value has no effect on the variables of the overall object.
Is there a way to achieve what I am trying? If not, what other pattern would be a better way to do this instead of having tons of if statements?
For JSON parsing, you can simply use Codable types.
Let's assume your JSON looks like,
{
"name": "Alex",
"email": "alex#gmail.com",
"about_Me": "My name is Alex"
}
Models:
class Root: Decodable {
let name: String?
let email: String?
let aboutMe: String?
enum CodingKeys: String, CodingKey {
case name, email
case aboutMe = "about_Me"
}
}
Parse the JSON data like so,
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response) //use response here...
} catch {
print(error)
}
You can use updateValue function for that, it finds the property and changes it.
if let oldValue = varMap.updateValue(self.name, forKey: "Name") {
print("The old value of \(oldValue) was replaced with a new one.")
}
So you for iteration is;
for (key, var variable) in varMap
{
varMap.updateValue(string, forKey: key )
//if let string = json[key]
//{
// variable = string
//}
}
After you update the dictionary you can call that part;
if let name = json["Name"] as? String
{
self.name = name
}
if let email = json["Email"] as? String
{
self.email = email
}
if let aboutMe = json["AboutMe"] as? String
{
self.aboutMe = aboutMe
}
I believe that part in a function, if its not, you can refactor it.
https://developer.apple.com/documentation/swift/dictionary/3127179-updatevalue
I have been searching every where, but cannot get to the right answer. I receive a Json result with the following structure
{
result = {
"2ab5a003-0120-4c01-80f2-a237dcf4ba14" = {
icon = garden;
id = "2ab5a003-0120-4c01-80f2-a237dcf4ba14";
index = 1;
name = "Tafel hoek";
parent = "855113f1-f488-4223-b675-2f01270f573e";
};
"2afd6093-ca6d-4e52-aaca-336ab76ea454" = {
icon = default;
id = "2afd6093-ca6d-4e52-aaca-336ab76ea454";
index = 11;
name = Badkamer;
parent = "9919ee1e-ffbc-480b-bc4b-77fb047e9e68";
};
};
status = 200;
}
Because I don't know the 1st key in the result I am lost to get the separate items. Is there any one who can help me ? Thanks in advance
my code is:
{ print("Error: ")
print(error!)
} else { // no error
if let urlContent = data { // 3
do { // 4
let json = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(json)
} catch {
print("JSON processing failed")
} // end 4 do
} // end 3 let urlContent
} // end 2 if error}
Since you have the dictionary for key result, you can enumerate it as usual:
if let result = json["result"] as? [String:[String:Any]] {
for (key, value) in result {
let name = value["name"] as! String
let index = value["index"] as! Int
print (key, name, index)
}
}
Can you post your code ,i think that is not a valid json format and tell me where you are struggling to parse.ignore that key and try as usual
So I'm trying to populate multiple tableviews with the json code below:
{ "company":[
{ "company_id":"0",
"name": "Company Name",
"phone_number":"0123978978",
"website":"http:\/\/www.Company.co.uk\/",
"email":"Company#hotmail.co.uk",
"address":"123 Alm Street...",
"employees":[
{
"company_id": "0",
"name":"Steve",
"age":"25",
"description":"desc"},
{
"company_id": "0",
"name":"Paul",
"age":"35",
"description":"desc"}]
}
]
}
below is the model of fetching the json and formatting it into something I can use
class Company: NSObject {
var name: String
var phoneNumber: String
var website: String
var email: String
var address: String
var employees: [Employee]
override init() {
}
init(name: String, phoneNumber: String, website: String, email: String, address: String, employees: [Employee]) {
self.name = name
self.phoneNumber = phoneNumber
self.website = website
self.email = email
self.address = address
self.employees = employees
}
}
class Employee : NSObject {
var name:String
var age:Int
var information:String
override init() {
}
init(name: String, age: Int, information: String) {
self.name = name
self.age = age
self.information = information
}
}
func getJSON(completion: (array: [Company])->()) {
var companyArray = [Company]()
let requestURL: NSURL = NSURL(string: urlString)!
let urlRequest: NSMutableURLRequest = NSMutableURLRequest(URL: requestURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(urlRequest) {
data, response, error -> Void in
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
if (statusCode == 200) {
print("Everything is fine, file downloaded successfully.")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments)
if let companies = json["companies"] as? NSArray {
for companyDict in companies {
let company = Company()
let employee = Employee()
var employeeArray = [Employee]()
if let name = companyDict["name"] as? String,
let phoneNumber = companyDict["phone_number"] as? String,
let website = companyDict["website"] as? String,
let email = companyDict["email"] as? String,
let address = companyDict["address"] as? String {
company.name = name
company.phoneNumber = phoneNumber
company.website = website
company.email = email
company.address = address
}
if let employees = companyDict["employees"] as? NSArray {
for employeesDict in employees {
if let name = employeesDict["name"] as? String,
let age = employeesDict["age"] as? Int,
let information = employeesDict["information"] as? String {
var employeeArray = [Employee]()
let employee = Employee()
employee.name = name
employee.information = information
employee.age = age
employeeArray.append(employee)
company.employees = employeeArray
}
}
}
companyArray.append(company)
}
dispatch_async(dispatch_get_main_queue()) {
completion(array: companyArray)
}
}
} catch {
print("Error with Json: \(error)")
}
}
}
task.resume()
}
This is where the problem arrises, I can populate my first tableview fine. Each cell displays the data fine. The problem occurs when I try to populate my second tableview based on selection of a cell in my first tableview.
let selectedCompany:Company?
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.selectedCompany!.employees!.count //ignore force unwrap
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let employee = self.selectedCompany!.employees![indexPath.row] //ignore force unwrap
cell.employeeName.text = employee.name
return cell
}
The line let employee = self.selectedCompany!.employees![indexPath.row] displays a compiler error of type has no subscript members. I think I know why - I need to populate the second table view as an array. At the moment, Employee is not an array. I've tried several different methods to make it an array but I can't seem to do it. What I've tried usually returns nil and the app crashes or i get compiler errors
You have to declare employees in Company as array
var employees = [Employee]()
and also in the initializer
init(name ... , employees: [Employee]) {
and – very very important – you have to create the Employee instance in the repeat loop, otherwise you have employees.count times the same object (due to reference semantics of classes)
...
for employeesDict in employees {
if let name = employeesDict["name"] as? String,
let age = employeesDict["age"] as? Int,
let information = employeesDict["information"] as? String {
let employee = Employee(name:name, age:age, information:information)
employeeArray.append(employee)
}
}
}
company.employees = employeeArray
...
You have to use the initializer anyway. And you have to assign the array after the repeat loop.
i am trying to parse this json response.
account = (
{
accountName = "Complete Access";
accountNumber = "062005 1709 5888";
available = "226.76";
balance = "246.76";
}
);
var account : NSArray = jsonResult.valueForKey("account") as NSArray
Now, this array contains all value for key “account”. Inside this array how we can check if it contains the key “balance” or not.I tried to check it as below :
if account .containsObject(account(idx).valueForKey(“balance"))
{
transactions.balance = account(idx).valueForKey(“balance”) as? String
}
But, it never got inside the if condition. Please sugget the correct way to achieve this.
something like this may help on you:
let keyAccount: String = "account"
let keyBalance: String = "balance"
var jsonResult : Dictionary<String, AnyObject> = ["account":[["accountName":"Complete Access", "accountNumber":"062005 1709 5888", "available":"226.76", "balance":"246.76"]]]
var account : AnyObject? = jsonResult[keyAccount]
if let accountArray: Array<AnyObject>? = account as? Array {
if accountArray!.count > 0 {
if let accountDictionary: Dictionary<String, String>? = accountArray![0] as? Dictionary {
if let balanceValue: String? = accountDictionary![keyBalance] {
println("\(balanceValue)")
}
}
}
}
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.newtonking.com/json or something like this example from Java
User user = jsonResponse.readEntity(User.class);
whereby jsonResponse.toString() is something like
{
"name": "myUser",
"email": "user#example.com",
"password": "passwordHash"
}
SWIFT 4 Update
Since you give a very simple JSON object the code prepared for to handle that model. If you need more complicated JSON models you need to improve this sample.
Your Custom Object
class Person : NSObject {
var name : String = ""
var email : String = ""
var password : String = ""
init(JSONString: String) {
super.init()
var error : NSError?
let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let JSONDictionary: Dictionary = NSJSONSerialization.JSONObjectWithData(JSONData, options: nil, error: &error) as NSDictionary
// Loop
for (key, value) in JSONDictionary {
let keyName = key as String
let keyValue: String = value as String
// If property exists
if (self.respondsToSelector(NSSelectorFromString(keyName))) {
self.setValue(keyValue, forKey: keyName)
}
}
// Or you can do it with using
// self.setValuesForKeysWithDictionary(JSONDictionary)
// instead of loop method above
}
}
And this is how you invoke your custom class with JSON string.
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = "{ \"name\":\"myUser\", \"email\":\"user#example.com\", \"password\":\"passwordHash\" }"
var aPerson : Person = Person(JSONString: jsonString)
println(aPerson.name) // Output is "myUser"
}
I recommend that you use code generation (http://www.json4swift.com) to create native models out of the json response, this will save your time of parsing by hand and reduce the risk of errors due to mistaken keys, all elements will be accessible by model properties, this will be purely native and the models will make more sense rather checking the keys.
Your conversion will be as simple as:
let userObject = UserClass(userDictionary)
print(userObject!.name)
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"
Yet another JSON handler I wrote:
https://github.com/dankogai/swift-json
With it you can go like this:
let obj:[String:AnyObject] = [
"array": [JSON.null, false, 0, "", [], [:]],
"object":[
"null": JSON.null,
"bool": true,
"int": 42,
"double": 3.141592653589793,
"string": "a α\t弾\n𪚲",
"array": [],
"object": [:]
],
"url":"http://blog.livedoor.com/dankogai/"
]
let json = JSON(obj)
json.toString()
json["object"]["null"].asNull // NSNull()
json["object"]["bool"].asBool // true
json["object"]["int"].asInt // 42
json["object"]["double"].asDouble // 3.141592653589793
json["object"]["string"].asString // "a α\t弾\n𪚲"
json["array"][0].asNull // NSNull()
json["array"][1].asBool // false
json["array"][2].asInt // 0
json["array"][3].asString // ""
As you see no !? needed between subscripts.
In addition to that you can apply your own schema like this:
//// schema by subclassing
class MyJSON : JSON {
override init(_ obj:AnyObject){ super.init(obj) }
override init(_ json:JSON) { super.init(json) }
var null :NSNull? { return self["null"].asNull }
var bool :Bool? { return self["bool"].asBool }
var int :Int? { return self["int"].asInt }
var double:Double? { return self["double"].asDouble }
var string:String? { return self["string"].asString }
var url: String? { return self["url"].asString }
var array :MyJSON { return MyJSON(self["array"]) }
var object:MyJSON { return MyJSON(self["object"]) }
}
let myjson = MyJSON(obj)
myjson.object.null // NSNull?
myjson.object.bool // Bool?
myjson.object.int // Int?
myjson.object.double // Double?
myjson.object.string // String?
myjson.url // String?
There's a great example by Apple for deserializing JSON with Swift 2.0
The trick is to use the guard keyword and chain the assignments like so:
init?(attributes: [String : AnyObject]) {
guard let name = attributes["name"] as? String,
let coordinates = attributes["coordinates"] as? [String: Double],
let latitude = coordinates["lat"],
let longitude = coordinates["lng"],
else {
return nil
}
self.name = name
self.coordinates = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
I personally prefer native parsing vs any 3rd party, as it is transparent and magic-less. (and bug less?)
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"
}""")!
I wrote this small open-source library recently that lets you quickly and easily deserialize dictionaries into Swift objects: https://github.com/isair/JSONHelper
Using it, deserializing data becomes as easy as this:
var myInstance = MyClass(data: jsonDictionary)
or
myInstance <-- jsonDictionary
And models need to look only like this:
struct SomeObjectType: Deserializable {
var someProperty: Int?
var someOtherProperty: AnotherObjectType?
var yetAnotherProperty: [YetAnotherObjectType]?
init(data: [String: AnyObject]) {
someProperty <-- data["some_key"]
someOtherProperty <-- data["some_other_key"]
yetAnotherProperty <-- data["yet_another_key"]
}
}
Which, in your case, would be:
struct Person: Deserializable {
var name: String?
var email: String?
var password: String?
init(data: [String: AnyObject]) {
name <-- data["name"]
email <-- data["email"]
password <-- data["password"]
}
}
If you would like parse from and to json without the need to manually map keys and fields, then you could also use EVReflection. You can then use code like:
var user:User = User(json:jsonString)
or
var jsonString:String = user.toJsonString()
The only thing you need to do is to use EVObject as your data objects base class.
See the GitHub page for more detailed sample code
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)")
}
This way lets you get the user from a URL. It's parse the NSData to a NSDictionary and then to your NSObject.
let urlS = "http://api.localhost:3000/"
func getUser(username: Strung) -> User {
var user = User()
let url = NSURL(string: "\(urlS)\(username)")
if let data = NSData(contentsOfURL: url!) {
setKeysAndValues(user, dictionary: parseData(data))
}
return user
}
func setKeysAndValues (object : AnyObject, dictionary : NSDictionary) -> AnyObject {
for (key, value) in dictionary {
if let key = key as? String, let value = value as? String {
if (object.respondsToSelector(NSSelectorFromString(key))) {
object.setValue(value, forKey: key)
}
}
}
return object
}
func parseData (data : NSData) -> NSDictionary {
var error: NSError?
return NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
}
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)
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.