How to insert JSON array model in Swift? - json

I have JSON like this but with more data:
[
{
"name": "Place 1",
"avatar": "https://sometext.it/image=1",
"id": "1",
"lng": 10.01,
"lat": 15.02
},
{
"name": "Place 2",
"avatar": "https://sometext.it/image=2",
"id": "2",
"lng": 15.02,
"lat": 15.03
}
]
I get JSON from URL and I want to insert them to array of places. I have class:
class Place {
var Avatar = ""
var Id = 0
var Lat = 0.0
var Lng = 0.0
var Name = ""
required init(avatar: String, id: Int, lat: Double, lng: Double, name: String) {
self.Avatar = avatar
self.Id = id
self.Lat = lat
self.Lng = lng
self.Name = name
}
}
And i create an Array:
var places: [Place] = []
I serialize JSON like this:
func parsingJson() {
guard let url = URL(string: "https://somelink.com") else {
return
}
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
print(data)
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
for result in json as! [[String:Any]] {
let avatar = result["avatar"] as! String
let id = result["id"] as! Int
let lat = result["lat"] as! Double
let lng = result["lng"] as! Double
let name = result["name"] as! String
let place = Place(avatar: avatar, id: id, lat: lat, lng: lng, name: name)
self.places.append(place)
print(result)
}
}catch {
print("JSON Error")
}
}
}.resume()
}
but that doesn't work, I have error like this: http://obrazki.elektroda.pl/9267167800_1497627255.png
I know that I have nil but I don't know why :( When I print the JSON when I'm serialising I see it on console.

The first, you are trying to get ID as an INT, when your JSON Object is showing that it's a string.. So you would need to do:
var id = Int()
if let someID = result["id"] as? String {
id = Int(someId)
} else {
print("ID failed as String")
}
However, i'd also recommend using a guard statement before your for loop:
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String:Any]] else {
print("Invalid json object type")
return
}
for result in json {
///your code.
}
you at least try to unwrap the variable, and get some Int instance to pass along
But obviously, the simpler solution would be to save the id field in your JSON to the type you would want on your front-end.. in this case a Number.
While it might not look as clean as your code, use unwraping methods like guards, and if-else logics as much as possible, and create failed unwrap fallbacks as much as possible as early on in your project. It helps in debugging down the line and creates a good base when your project becomes large enough.
Check up on Apple's guide and this here for some good starting points.
Late update, but re-reading on all this landed me on this neat article by Apple:
https://developer.apple.com/swift/blog/?id=37

You haven't nil, read error - Could not cast value of type NSTaggedPointerString to NSNumber This means that your id is String and you are casting it to Int so replace
let id = result["id"] as! Int
to
let id = result["id"] as! String

Related

Swift 4 JSONSerialization.jsonObject

I am using Xcode 9.2 and Swift 4. How can I check if the returned json data is null?
Now it gives me error Type 'Any' has no subscript members at line if json[0]["data"]
var json: NSMutableArray = []
var newsArray: NSMutableArray = []
let url = URLFactory()
var data = try! NSData(contentsOf: url.getURL()) as Data
do {
json = try JSONSerialization.jsonObject(with: data, options: []) as! NSMutableArray
if json[0]["data"] {
// data is not null
}
} catch let error as NSError {
// handle error
}
My JSON returns something like this:
{
"data":
[
{
"news_id":123,
"title":"title",
"news_date":"2017-02-08 21:46:06",
"news_url":"url",
"short_description":"description",
"category_id":4,
"category_name":"Health",
"latlng":
[
{
"lat":"43.003429",
"lng":"-78.696335"
}
]
}
{ ....
}
If you're using Swift 4, I might suggest JSONDecoder:
First, define the types to hold the parsed data:
struct ResponseObject: Codable {
let data: [NewsItem]
}
struct NewsItem: Codable {
let newsId: Int
let title: String
let newsDate: Date
let newsURL: URL
let shortDescription: String
let categoryID: Int
let categoryName: String
let coordinates: [Coordinate]
// because your json keys don't follow normal Swift naming convention, use CodingKeys to map these property names to JSON keys
enum CodingKeys: String, CodingKey {
case newsId = "news_id"
case title
case newsDate = "news_date"
case newsURL = "news_url"
case shortDescription = "short_description"
case categoryID = "category_id"
case categoryName = "category_name"
case coordinates = "latlng"
}
}
struct Coordinate: Codable {
let lat: String
let lng: String
}
And then you can parse it:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let responseObject = try decoder.decode(ResponseObject.self, from: data)
print(responseObject.data)
} catch {
print(error)
}
Clearly, if your JSON is different, you might need to change your objects accordingly (e.g. it struck me odd that latlng was an array of coordinates). Also, you can define custom init(from:) methods if you want to convert some of these strings into numbers, but I'd rather fix the JSON, instead (why are latlng returning string values rather than numeric values).
For more information, see Encoding and Decoding Custom Types.
As an aside, I'd advise against this pattern (note, this is your network request logic, but excised of NSData):
let data = try! Data(contentsOf: url.getURL())
That will retrieve the data synchronously, which can be problematic because
the app will be frozen while the data is being retrieved resulting in a poor UX;
you risk having your app killed by the watchdog process which looks for frozen apps; and
you don't have robust error handling and this will crash if the network request fails.
I'd suggest using URLSession:
let task = URLSession.shared.dataTask(with: url.getURL()) { data, _, error in
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
// now parse `data` like shown above
// if you then need to update UI or model objects, dispatch that back
// to the main queue:
DispatchQueue.main.async {
// use `responseObject.data` to update model objects and/or UI here
}
}
task.resume()

(Swift) How to parse JSON with struct?

I want to parse JSON with struct and name it.
here is the JSON data:
{
"sgList": [
{
"ID": 11113,
"Name": "soss",
"Price": "10.0000",
"BigImagesUrl": "http://192.165.1.19:886/img/1/2015/7/11/20157111429315728.png",
"SmallImagesUrl": "http://192.165.1.19:886/img/1/2015/7/11/20157111429315728.png"
},
{
"ID": 11958,
"Name": "1017p-02",
"Price": "0.0000",
"BigImagesUrl": "http://192.165.1.13:886/img/rar-upload/f82f22ce-4a33-4ba2-a31d-4bae473f5d48/pics/797_1.jpg",
"SmallImagesUrl": "http://192.165.1.13:886/img/rar-upload/f82f22ce-4a33-4ba2-a31d-4bae473f5d48/pics/797_1-[135-135].jpg"
}
]
}
I spend hours on it and get nothing!
Please help me, Thank you very much!
If you don't want an third party library and do it yourself, it's pretty easy.
Assuming that your JSON String is in a variable called jsonString
let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
Then you can access your data via subsript. For example if you want the Name of the second object in sgList
json["sgList"][1]["Name"]
You can do something like this using SwiftyJSON:
import SwiftyJSON
struct SqList {
let sqList: Array<SqElement>
init(json: JSON) {
let sqArray = json["sqList"].arrayValue.flatMap { SqElement(json: $0) }
self.sqList = sqArray
}
}
struct SqElement {
let id: String
let name: String
let price: String
let bigImagesUrl: String
let smallImagesUrl: String
init?(json: JSON) {
guard
let id = json["ID"].string,
let name = json["Name"].string,
let price = json["Price"].string,
let bigImagesUrl = json["BigImagesUrl"].string,
let smallImagesUrl = json["SmallImagesUrl"].string
else { return nil }
self.id = id
self.name = name
self.price = price
self.bigImagesUrl = bigImagesUrl
self.smallImagesUrl = smallImagesUrl
}
}
In code you just call:
let sqList = SqList(json: JSON(data: dataWithJSON))

Get data from json array swift 2

I'm trying to get data drom the json array, this is the code that i'm trying, the thing is that i would like to get only the name that is inside this json
{
"tag": "getuser",
"success": 1,
"error": 0,
"uid": "56108b7e651ad2.95653404",
"user": {
"name": "2",
"phone": "2",
"email": "2"
}
}
I tryied this
let jsonData:NSDictionary = try NSJSONSerialization.JSONObjectWithData(urlData!, options:NSJSONReadingOptions.MutableContainers ) as! NSDictionary
let name = jsonData["user"]
print("Nombre del usuarioes: \(name)")
But this prints the whole user data, name, phone and email, how can i be able to print only the name or only the email?
You don't have to use a library and you don't have to use key-value coding.
The same way you're already using subscripting for your dictionary with this:
let name = jsonData["user"]
you just have to continue subscripting to find your value.
Example:
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(urlData!, options: []) as! NSDictionary
let user = jsonData["user"]!
let name = user["name"]
print(name)
} catch {
print(error)
}
Even better with safe unwrapping:
do {
if let data = urlData, let jsonData = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? NSDictionary {
if let user = jsonData["user"] as? NSDictionary, let name = user["name"] as? String {
print(name)
}
}
} catch {
print(error)
}
Note: in JSON, a dictionary is defined by {} and an array is defined by []. What you have here is a dictionary containing a dictionary, not an array (cf your question title).
A great library to decode json is SwiftyJSON
you can get sub-scripted data from the json like so
import SwiftyJSON
if let dataFromString = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
let name = json["user"]["name"].string
print(name)
}
Use your code, then get the field from jsonData by this:
let name = jsonData.valueForKeyPath("user.name") as! String
let email = jsonData.valueForKeyPath("user.email") as! String

How to parse this specific JSON data in Swift 2.0

I'm trying to parse Json Data from an API :
{
"title": "Mr. Robot",
"first_aired": "2015-06-24",
"network": "USA Network",
"channels": [
{
"id": 12,
"name": "USA",
"short_name": "usa",
"channel_type": "television"
}
],
The Code I'm use is:
var TVArray : [TVInfo] = []
var task : NSURLSessionTask?
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateJSON(data)
}
else {
}
}
}
task!.resume()
}
func updateJSON (data: NSData!) {
let JSONData = (try! NSJSONSerialization.JSONObjectWithData(data, options: []))
TVArray.removeAll(keepCapacity: true)
if let jsonArray = JSONData {
for j in jsonArray {
let title = jsonResult["title"] as! String
let firstAired = jsonResult["first_aired"] as! String
let network = jsonResult["network"] as! String
let channelName = JsonResult["channels"][0]["name"] as! String
let TV = TVInfo(title: title, firstAired: firstAired, network: network, channelName: channelName)
TVArray.append(TV)
}
}
collectionview.reloadData()
}
}
When I use the above code I get an error 'Initializer for conditional binding must have Optional type, not 'AnyObject'' in front of the line 'if let jsonArray = JsonData'. I've tried some methods I've seen on StackOverflow like the method in the link :
[Parsing JSON in swift 2.0
but it didn't work for me. I'm still a bit new to Swift, I really don't want to use SwiftyJSON. Is this the best way to parse this JSON data or is there a better way of doing it?
Since you've used NSJSONSerialization with try! (note the !, meaning it was forced) the value of JSONData is not an optional: you don't have to unwrap it with if let jsonArray = JSONData.
If you still want an optional value, use try? instead.
Otherwise you could also use try inside a do catch block to handle possible errors.
The type of JSONData is unknown, it needs to be known to be an Array for the following for loop.
use:
let JSONData = try! NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! NSArray
You do not need:
if let jsonArray = JSONData {
because you have already crashed if JSONData is nil from the preceding try!
You are better with:
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! NSArray
for j in jsonArray {
// ...
}
} catch {
// handle error
}
Because you have no control over the JSON you receive and crashing because of a server change is not a good idea.
Also really put some time into naming variables, JSONData is not data, it is an array obtained by parsing a JSON string.

Deserialize JSON / NSDictionary to Swift objects

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.