Swift 4 Parsing an Object Array - ios

I'm having problems transferring my parsing code over to Swift4/Xcode9. I'm parsing it through my model correctly and I know the problem is because I'm currently parsing through the root JSON object, however my REST API is structured JSON root -> data array -> further objects. I know it's probably simple but I've been struggling for ages. I have attached an image of the layout of my REST API, and just need to know how to step into objects to retrieve values.
let jsonUrlString = "HIDDEN"
guard let url = URL(string: jsonUrlString) else
{return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let show = try
JSONDecoder().decode(TvHeaderModel.self, from: data)
print(show.title)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
struct TvHeaderModel: Decodable {
var id: Int?
var title: String?
var year: Int?
var songCount: Int?
var poster: String?
init(json: [String: Any]) {
id = json["_id"] as? Int ?? -1
title = json["title"] as? String ?? ""
year = json["year"] as? Int ?? -1
songCount = json["song_count"] as? Int ?? -1
poster = json["poster_urL"] as? String ?? ""
}
}

Basically you don't need the init(json initializer when using JSONDecoder and declaring all properties as optional and assigning always a non-optional value is nonsensical anyway.
To decode also the seasons add a struct
struct Season : Decodable {
private enum CodingKeys : String, CodingKey {
case id = "_id", season
case show = "tv_show", createdDate = "created_at"
case numberOfEpisodes = "episodes_count", numberOfSongs = "songs_count"
}
let id, season, show, numberOfEpisodes, numberOfSongs : Int
let createdDate : Date
}
In TvHeaderModel add a property
let seasons : [Season]
And set the dateDecodingStrategy of the decoder to decode ISO8601 dates.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.decode(TvHeaderModel.self, from: data)

Related

The data couldn’t be read because it isn’t in the correct format. Swift 5

I am trying to decode data from https://swapi.dev/. I get json correctly with response code 200, but the decoder could not read the data because the format is incorrect. I tried it in a lot of different ways. I am trying to get info on peoples.
Here is my code:
Model File
struct people: Codable {
let count: Int
let next: String?
let previous: String?
let results: [result]
}
struct result: Codable{
let name: String
let height: Int
let mass: Int
let hair_color: String
let skin_color: String
let eye_color: String
let birth_year: String
let gender: String
let homeworld: String
let films: [String]
let species: [String]
let vehicles: [String]
let starships: [String]
let created: String
let edited: String
let url: String
}
struct APIError: Codable {
let detail: String
}
Network Services
typealias OnApiSucces = (people) -> Void
typealias OnApiError = (String) -> Void
struct ApiService {
static let shared = ApiService()
let URL_BASE = "https://swapi.dev/api"
let URL_PEOPLE = "/people"
let session = URLSession(configuration: .default)
func getResults(onSuccess: #escaping OnApiSucces, onError: #escaping OnApiError) {
let url = URL(string: "\(URL_BASE)\(URL_PEOPLE)")!
var request = URLRequest(url: url)
request.httpMethod = "GET" // GET, PUT, POST, DELETE for some different api
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request) { (data, response, error) in
if let error = error {
onError(error.localizedDescription)
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
onError("Invalid data or response")
return
}
do{
if response.statusCode == 200 {
print("Code is \(response.statusCode)")
let results = try JSONDecoder().decode(people.self, from: data)
onSuccess(results)
} else {
let err = try JSONDecoder().decode(APIError.self, from: data)
print("Code is \(response.statusCode)")
onError(err.detail)
}
}
catch {
onError(error.localizedDescription)
}
}
task.resume()
}
}
** Getting data on ViewController**
func getResults() {
ApiService.shared.getResults { (people) in
self.results = people.results
} onError: { (error) in
debugPrint(error.description)
}
}
First, your data can't be read because height and mass are represented as String in the Star Wars API, while you represent them as Int in your Codable struct.
Also, try adding CodingKeys to your Codable struct so your struct adheres to naming conventions (specifically regarding your attribute_color variants) e.g.
struct result: Codable{
let name: String
let height: String
let mass: String
let hairColor: String // changed from hair_color
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
let homeworld: String
let films: [String]
let species: [String]
let vehicles: [String]
let starships: [String]
let created: String
let edited: String
let url: String
enums CodingKeys: String, CodingKey {
case name = "name"
case height = "height"
case mass = "mass"
case hairColor = "hair_color"
case skinColor = "skin_color"
case eyeColor = "eye_color"
case birthYear = "birth_year"
case gender = "gender"
case homeworld = "homeworld"
case films = "films"
case species = "species"
case vehicles = "vehicles"
case starships = "starships"
case created = "created"
case edited = "edited"
case url = "url"
}
}

Passing JSON result into a struct model

I am receiving a result from an API, I can iterate through the result. My understanding is I can pass the value into a model immediately.
Apple Developer article on struct models
My issue is I am not doing it properly and am receiving a nil value. Perhaps someone can see where I need to change. I am using Swift 4.2
Here is my struct model.
import Foundation
struct ProfileModel {
//MARK: Properties
var name: String
var email: String
var profileURL: String
//MARK: Initialization
}
extension ProfileModel{
init?(json: [String:AnyObject]) {
guard
let name = json["name"] as? String,
let email = json["email"] as? String,
let profileURL = json["profileURL"] as? String
else { return nil }
self.name = name
self.email = email
self.profileURL = profileURL
}
}
Here is my result code from my urlConnection. Let me know if we want to see the entire swift file
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject] {
self.onSuccess(data: json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func onSuccess(data: [String:AnyObject]){
print("onSuccess")
let myProfile = ProfileModel(json: data)
//myProfile is nil while unwrapping
let title: String = myProfile!.name
print(title)
}
I could just iterate through the strings since I am able to print 'data'. I just figured it would be cleaner to put everything into a ProfileModel and manage that object as a whole.
This json is my more simple one which is why I used it for this question. I also can't remember but I had to use "[String:AnyObject]" to get the json properly. This was pulled directly from my terminal, this was the data being passed in my JsonResponse. The output json from Xcode has [] on the outside instead.
{
'detail': 'VALID',
‘name’: ‘Carson,
'email': ‘carson.skjerdal#somethingelselabs.com',
'pic_url': None
}
EDIT:
So my problem is solved, and ultimately moving to Codable was the key. Here is my fixed code for anyone who might need a working solution.
URLSession.shared.dataTask(with: request as URLRequest) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(ProfileModel.self, from: data)
print(gitData.name)
self.onSuccess(data: gitData)
} catch let err {
print("Err", err)
}
}.resume()
}
func onSuccess(data: ProfileModel){
print("onSuccess")
print(data.email)
}
My Codable Struct - slightly simplified
import Foundation
struct ProfileModel: Codable {
let detail, name, email: String
private enum CodingKeys: String, CodingKey {
case detail, email
case name = "firstname"
//case picUrl = "pic_url"
}
}
After "Codable" has been introduced I always uses that.
You can take your JSON ans pars it in to QuickType.io, and you will get a Struct that confirms to the codadable
// To parse the JSON, add this file to your project and do:
//
// let aPIResponse = try? newJSONDecoder().decode(APIResponse.self, from: jsonData)
import Foundation
struct APIResponse: Codable {
let detail, name, email, picUrl: String
enum CodingKeys: String, CodingKey {
case detail, name, email
case picUrl = "pic_url"
}
}

Finding documentation on how to import JSON from URL in Swift 4 [duplicate]

I'm trying to fetch a JSON response and store the results in a variable. I've had versions of this code work in previous releases of Swift, until the GM version of Xcode 8 was released. I had a look at a few similar posts on StackOverflow: Swift 2 Parsing JSON - Cannot subscript a value of type 'AnyObject' and JSON Parsing in Swift 3.
However, it seems the ideas conveyed there do not apply in this scenario.
How do I correctly parse the JSON reponse in Swift 3?
Has something changed in the way JSON is read in Swift 3?
Below is the code in question (it can be run in a playground):
import Cocoa
let url = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
if let url = NSURL(string: url) {
if let data = try? Data(contentsOf: url as URL) {
do {
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments)
//Store response in NSDictionary for easy access
let dict = parsedData as? NSDictionary
let currentConditions = "\(dict!["currently"]!)"
//This produces an error, Type 'Any' has no subscript members
let currentTemperatureF = ("\(dict!["currently"]!["temperature"]!!)" as NSString).doubleValue
//Display all current conditions from API
print(currentConditions)
//Output the current temperature in Fahrenheit
print(currentTemperatureF)
}
//else throw an error detailing what went wrong
catch let error as NSError {
print("Details of JSON parsing error:\n \(error)")
}
}
}
Edit: Here is a sample of the results from the API call after print(currentConditions)
["icon": partly-cloudy-night, "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precipIntensity": 0, "windSpeed": 6.04, "summary": Partly Cloudy, "ozone": 321.13, "temperature": 49.45, "dewPoint": 41.75, "apparentTemperature": 47, "windBearing": 332, "cloudCover": 0.28, "time": 1480846460]
First of all never load data synchronously from a remote URL, use always asynchronous methods like URLSession.
'Any' has no subscript members
occurs because the compiler has no idea of what type the intermediate objects are (for example currently in ["currently"]!["temperature"]) and since you are using Foundation collection types like NSDictionary the compiler has no idea at all about the type.
Additionally in Swift 3 it's required to inform the compiler about the type of all subscripted objects.
You have to cast the result of the JSON serialization to the actual type.
This code uses URLSession and exclusively Swift native types
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print(error)
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let currentConditions = parsedData["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}
}.resume()
To print all key / value pairs of currentConditions you could write
let currentConditions = parsedData["currently"] as! [String:Any]
for (key, value) in currentConditions {
print("\(key) - \(value) ")
}
A note regarding jsonObject(with data:
Many (it seems all) tutorials suggest .mutableContainers or .mutableLeaves options which is completely nonsense in Swift. The two options are legacy Objective-C options to assign the result to NSMutable... objects. In Swift any variable is mutable by default and passing any of those options and assigning the result to a let constant has no effect at all. Further most of the implementations are never mutating the deserialized JSON anyway.
The only (rare) option which is useful in Swift is .allowFragments which is required if if the JSON root object could be a value type(String, Number, Bool or null) rather than one of the collection types (array or dictionary). But normally omit the options parameter which means No options.
===========================================================================
Some general considerations to parse JSON
JSON is a well-arranged text format. It's very easy to read a JSON string. Read the string carefully. There are only six different types – two collection types and four value types.
The collection types are
Array - JSON: objects in square brackets [] - Swift: [Any] but in most cases [[String:Any]]
Dictionary - JSON: objects in curly braces {} - Swift: [String:Any]
The value types are
String - JSON: any value in double quotes "Foo", even "123"or "false" – Swift: String
Number - JSON: numeric values not in double quotes 123 or 123.0 – Swift: Int or Double
Bool - JSON: true or false not in double quotes – Swift: true or false
null - JSON: null – Swift: NSNull
According to the JSON specification all keys in dictionaries are required to be String.
Basically it's always recommeded to use optional bindings to unwrap optionals safely
If the root object is a dictionary ({}) cast the type to [String:Any]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any] { ...
and retrieve values by keys with (OneOfSupportedJSONTypes is either JSON collection or value type as described above.)
if let foo = parsedData["foo"] as? OneOfSupportedJSONTypes {
print(foo)
}
If the root object is an array ([]) cast the type to [[String:Any]]
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] { ...
and iterate through the array with
for item in parsedData {
print(item)
}
If you need an item at specific index check also if the index exists
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]], parsedData.count > 2,
let item = parsedData[2] as? OneOfSupportedJSONTypes {
print(item)
}
}
In the rare case that the JSON is simply one of the value types – rather than a collection type – you have to pass the .allowFragments option and cast the result to the appropriate value type for example
if let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? String { ...
Apple has published a comprehensive article in the Swift Blog: Working with JSON in Swift
===========================================================================
In Swift 4+ the Codable protocol provides a more convenient way to parse JSON directly into structs / classes.
For example the given JSON sample in the question (slightly modified)
let jsonString = """
{"icon": "partly-cloudy-night", "precipProbability": 0, "pressure": 1015.39, "humidity": 0.75, "precip_intensity": 0, "wind_speed": 6.04, "summary": "Partly Cloudy", "ozone": 321.13, "temperature": 49.45, "dew_point": 41.75, "apparent_temperature": 47, "wind_bearing": 332, "cloud_cover": 0.28, "time": 1480846460}
"""
can be decoded into the struct Weather. The Swift types are the same as described above. There are a few additional options:
Strings representing an URL can be decoded directly as URL.
The time integer can be decoded as Date with the dateDecodingStrategy .secondsSince1970.
snaked_cased JSON keys can be converted to camelCase with the keyDecodingStrategy .convertFromSnakeCase
struct Weather: Decodable {
let icon, summary: String
let pressure: Double, humidity, windSpeed : Double
let ozone, temperature, dewPoint, cloudCover: Double
let precipProbability, precipIntensity, apparentTemperature, windBearing : Int
let time: Date
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Weather.self, from: data)
print(result)
} catch {
print(error)
}
Other Codable sources:
Apple: Encoding and Decoding Custom Types
HackingWithSwift: Codable Cheat Sheet
Ray Wenderlich: Encoding and Decoding in Swift
A big change that happened with Xcode 8 Beta 6 for Swift 3 was that id now imports as Any rather than AnyObject.
This means that parsedData is returned as a dictionary of most likely with the type [Any:Any]. Without using a debugger I could not tell you exactly what your cast to NSDictionary will do but the error you are seeing is because dict!["currently"]! has type Any
So, how do you solve this? From the way you've referenced it, I assume dict!["currently"]! is a dictionary and so you have many options:
First you could do something like this:
let currentConditionsDictionary: [String: AnyObject] = dict!["currently"]! as! [String: AnyObject]
This will give you a dictionary object that you can then query for values and so you can get your temperature like this:
let currentTemperatureF = currentConditionsDictionary["temperature"] as! Double
Or if you would prefer you can do it in line:
let currentTemperatureF = (dict!["currently"]! as! [String: AnyObject])["temperature"]! as! Double
Hopefully this helps, I'm afraid I have not had time to write a sample app to test it.
One final note: the easiest thing to do, might be to simply cast the JSON payload into [String: AnyObject] right at the start.
let parsedData = try JSONSerialization.jsonObject(with: data as Data, options: .allowFragments) as! Dictionary<String, AnyObject>
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String]
{
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
I built quicktype exactly for this purpose. Just paste your sample JSON and quicktype generates this type hierarchy for your API data:
struct Forecast {
let hourly: Hourly
let daily: Daily
let currently: Currently
let flags: Flags
let longitude: Double
let latitude: Double
let offset: Int
let timezone: String
}
struct Hourly {
let icon: String
let data: [Currently]
let summary: String
}
struct Daily {
let icon: String
let data: [Datum]
let summary: String
}
struct Datum {
let precipIntensityMax: Double
let apparentTemperatureMinTime: Int
let apparentTemperatureLowTime: Int
let apparentTemperatureHighTime: Int
let apparentTemperatureHigh: Double
let apparentTemperatureLow: Double
let apparentTemperatureMaxTime: Int
let apparentTemperatureMax: Double
let apparentTemperatureMin: Double
let icon: String
let dewPoint: Double
let cloudCover: Double
let humidity: Double
let ozone: Double
let moonPhase: Double
let precipIntensity: Double
let temperatureHigh: Double
let pressure: Double
let precipProbability: Double
let precipIntensityMaxTime: Int
let precipType: String?
let sunriseTime: Int
let summary: String
let sunsetTime: Int
let temperatureMax: Double
let time: Int
let temperatureLow: Double
let temperatureHighTime: Int
let temperatureLowTime: Int
let temperatureMin: Double
let temperatureMaxTime: Int
let temperatureMinTime: Int
let uvIndexTime: Int
let windGust: Double
let uvIndex: Int
let windBearing: Int
let windGustTime: Int
let windSpeed: Double
}
struct Currently {
let precipProbability: Double
let humidity: Double
let cloudCover: Double
let apparentTemperature: Double
let dewPoint: Double
let ozone: Double
let icon: String
let precipIntensity: Double
let temperature: Double
let pressure: Double
let precipType: String?
let summary: String
let uvIndex: Int
let windGust: Double
let time: Int
let windBearing: Int
let windSpeed: Double
}
struct Flags {
let sources: [String]
let isdStations: [String]
let units: String
}
It also generates dependency-free marshaling code to coax the return value of JSONSerialization.jsonObject into a Forecast, including a convenience constructor that takes a JSON string so you can quickly parse a strongly typed Forecast value and access its fields:
let forecast = Forecast.from(json: jsonString)!
print(forecast.daily.data[0].windGustTime)
You can install quicktype from npm with npm i -g quicktype or use the web UI to get the complete generated code to paste into your playground.
Updated the isConnectToNetwork-Function afterwards, thanks to this post.
I wrote an extra method for it:
import SystemConfiguration
func loadingJSON(_ link:String, postString:String, completionHandler: #escaping (_ JSONObject: AnyObject) -> ()) {
if(isConnectedToNetwork() == false){
completionHandler("-1" as AnyObject)
return
}
let request = NSMutableURLRequest(url: URL(string: link)!)
request.httpMethod = "POST"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse , httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
//JSON successfull
do {
let parseJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
DispatchQueue.main.async(execute: {
completionHandler(parseJSON as AnyObject)
});
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
task.resume()
}
func isConnectedToNetwork() -> Bool {
var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}
var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
return false
}
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
let ret = (isReachable && !needsConnection)
return ret
}
So now you can easily call this in your app wherever you want
loadingJSON("yourDomain.com/login.php", postString:"email=\(userEmail!)&password=\(password!)") { parseJSON in
if(String(describing: parseJSON) == "-1"){
print("No Internet")
} else {
if let loginSuccessfull = parseJSON["loginSuccessfull"] as? Bool {
//... do stuff
}
}
This is an other way to solve your problem. So please check out below solution. Hope it will help you.
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
let data = str.data(using: String.Encoding.utf8, allowLossyConversion: false)!
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String: AnyObject]
if let names = json["names"] as? [String] {
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
The problem is with the API interaction method. The JSON parsing is changed only in syntax. The main problem is with the way of fetching data. What you are using is a synchronous way of getting data. This doesn't work in every case. What you should be using is an asynchronous way to fetch data. In this way, you have to request data through the API and wait for it to respond with data. You can achieve this with URL session and third party libraries like Alamofire. Below is the code for URL Session method.
let urlString = "https://api.forecast.io/forecast/apiKey/37.5673776,122.048951"
let url = URL.init(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
guard error == nil else {
print(error)
}
do {
let Data = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
// Note if your data is coming in Array you should be using [Any]()
//Now your data is parsed in Data variable and you can use it normally
let currentConditions = Data["currently"] as! [String:Any]
print(currentConditions)
let currentTemperatureF = currentConditions["temperature"] as! Double
print(currentTemperatureF)
} catch let error as NSError {
print(error)
}
}.resume()
{
"User":[
{
"FirstUser":{
"name":"John"
},
"Information":"XY",
"SecondUser":{
"name":"Tom"
}
}
]
}
If I create model using previous json
Using this link [blog]: http://www.jsoncafe.com to generate Codable structure or Any Format
Model
import Foundation
struct RootClass : Codable {
let user : [Users]?
enum CodingKeys: String, CodingKey {
case user = "User"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
user = try? values?.decodeIfPresent([Users].self, forKey: .user)
}
}
struct Users : Codable {
let firstUser : FirstUser?
let information : String?
let secondUser : SecondUser?
enum CodingKeys: String, CodingKey {
case firstUser = "FirstUser"
case information = "Information"
case secondUser = "SecondUser"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
firstUser = try? FirstUser(from: decoder)
information = try? values?.decodeIfPresent(String.self, forKey: .information)
secondUser = try? SecondUser(from: decoder)
}
}
struct SecondUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
struct FirstUser : Codable {
let name : String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try? decoder.container(keyedBy: CodingKeys.self)
name = try? values?.decodeIfPresent(String.self, forKey: .name)
}
}
Parse
do {
let res = try JSONDecoder().decode(RootClass.self, from: data)
print(res?.user?.first?.firstUser?.name ?? "Yours optional value")
} catch {
print(error)
}
Swift has a powerful type inference. Lets get rid of "if let" or "guard let" boilerplate and force unwraps using functional approach:
Here is our JSON. We can use optional JSON or usual. I'm using optional in our example:
let json: Dictionary<String, Any>? = ["current": ["temperature": 10]]
Helper functions. We need to write them only once and then reuse with any dictionary:
/// Curry
public func curry<A, B, C>(_ f: #escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in
{ f(a, $0) }
}
}
/// Function that takes key and optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key, _ json: Dictionary<Key, Any>?) -> Value? {
return json.flatMap {
cast($0[key])
}
}
/// Function that takes key and return function that takes optional dictionary and returns optional value
public func extract<Key, Value>(_ key: Key) -> (Dictionary<Key, Any>?) -> Value? {
return curry(extract)(key)
}
/// Precedence group for our operator
precedencegroup RightApplyPrecedence {
associativity: right
higherThan: AssignmentPrecedence
lowerThan: TernaryPrecedence
}
/// Apply. g § f § a === g(f(a))
infix operator § : RightApplyPrecedence
public func §<A, B>(_ f: (A) -> B, _ a: A) -> B {
return f(a)
}
/// Wrapper around operator "as".
public func cast<A, B>(_ a: A) -> B? {
return a as? B
}
And here is our magic - extract the value:
let temperature = (extract("temperature") § extract("current") § json) ?? NSNotFound
Just one line of code and no force unwraps or manual type casting. This code works in playground, so you can copy and check it. Here is an implementation on GitHub.
Swift 5
Cant fetch data from your api.
Easiest way to parse json is Use Decodable protocol. Or Codable (Encodable & Decodable).
For ex:
let json = """
{
"dueDate": {
"year": 2021,
"month": 2,
"day": 17
}
}
"""
struct WrapperModel: Codable {
var dueDate: DueDate
}
struct DueDate: Codable {
var year: Int
var month: Int
var day: Int
}
let jsonData = Data(json.utf8)
let decoder = JSONDecoder()
do {
let model = try decoder.decode(WrapperModel.self, from: jsonData)
print(model)
} catch {
print(error.localizedDescription)
}

how yo access item key in swift 4

if let url = URL(string: "https://mysit.com") {
URLSession.shared.dataTask(with: url) {
data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let data = data, error == nil,
let valueEncoding = response?.textEncodingName,
let getContent = String(data: data, encoding: valueEncoding.textEncodingToStringEncoding)
else { return }
print(getContent)
}.resume()
}
my Data
{"Regions":null,"Cities":[{"Id":"9605","Name":"YANBAA AS SENAYAH"},{"Id":"15","Name":"ABHA"},{"Id":"13","Name":"AD DAMMAM"},{"Id":"1542","Name":"AL BAHA"},{"Id":"14","Name":"AL MADINAH AL MUNAWWARAH"},{"Id":"2213","Name":"AR'AR"},{"Id":"11","Name":"BURAYDAH"},{"Id":"10","Name":"HAIL"},{"Id":"17","Name":"JAZAN"},{"Id":"6","Name":"MAKKAH AL MUKARRAMAH"},{"Id":"3417","Name":"NAJRAN"},{"Id":"3","Name":"RIYADH"},{"Id":"2237","Name":"SAKAKA"},{"Id":"1","Name":"TABUK"},
how to get an array list of values "Name" ,can you help me?
You can try
struct Root :Decodable{
let Cities:[InnerItem]
}
struct InnerItem :Decodable{
let Id:String
let Name:String
}
do {
let arr = try JSONDecoder().decode(Root.self, from: data)
print(arr.Cities)
}
catch {
print(error)
}
//
Note : This is the correct json structure
{"Regions":null,"Cities":[{"Id":"9605","Name":"YANBAA AS SENAYAH"},{"Id":"15","Name":"ABHA"},{"Id":"13","Name":"AD DAMMAM"},{"Id":"1542","Name":"AL BAHA"},{"Id":"14","Name":"AL MADINAH AL MUNAWWARAH"},{"Id":"2213","Name":"AR'AR"},{"Id":"11","Name":"BURAYDAH"},{"Id":"10","Name":"HAIL"},{"Id":"17","Name":"JAZAN"},{"Id":"6","Name":"MAKKAH AL MUKARRAMAH"},{"Id":"3417","Name":"NAJRAN"},{"Id":"3","Name":"RIYADH"},{"Id":"2237","Name":"SAKAKA"},{"Id":"1","Name":"TABUK"}]}
let responseData = try JSONSerialization.jsonObject(with: (response["Cities"] as! String).data(using: String.Encoding.utf8)!, options: []) as! [[String: Any]]
for item in responseData{
let name = item["Name"] as! String
}
Together with the decoding step. I added several guards to print an error if one comes up. It is generally good practice to throw the error and handle it on the appropriate level.
func work() {
guard let url = URL(string: "https://mysit.com") else {
fatalError("url is nil.")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard error == nil else {
fatalError("\(error!)")
}
guard let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
fatalError("Response is nil.")
}
guard let data = data else {
fatalError("data is nil.")
}
decode(data: data)
}.resume()
}
func decode(data: Data) {
let decoder = JSONDecoder.init()
let welcome = try! decoder.decode(Welcome.self, from: data)
print(welcome.cities.first!)
}
The decoding helpers. enum CodingKeys are used to convert the lowercase attributes to the uppercase JSON attributes and back.
struct Welcome: Codable {
var regions: [Region]?
let cities: [City]
enum CodingKeys: String, CodingKey {
case regions = "Regions"
case cities = "Cities"
}
}
struct City: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
struct Region: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
Some use services like Quicktype to convert JSON strings to the specific programming language. It makes things faster and simpler.

struct for nested dictionary API in swift

i'm trying to import JSON data from the v2 of coinmarketcap API. I had it working with v1 as it was an array, however the new version is a dictionary and i cant quite get my struct correct.
The API im using is : https://api.coinmarketcap.com/v2/ticker/?convert=AUD
My struct is set up as below:
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
var id: String
var symbol : String
var name : String
var priceAUD : quoteStruct
}
struct quoteStruct{
let aud : priceStruct
}
struct priceStruct{
let price : String
}
My code for fetching the data is:
var coins = [Coin]()
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
My code for fetching the data i have used the same as previously which worked with v1 of the API, however i don't think i made my struct correctly.
Thanks in advance!
Your response Changed i try to configure it by converting it to array of dictionary you will need to change quotes to be [String:priceStruct]
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id,rank,symbol, name, priceAUD = "quotes"
}
var id: Int
var rank: Int
var symbol : String
var name : String
var priceAUD : [String: priceStruct]
}
struct priceStruct : Decodable{
let price : Double
}
func getCoinData() {
var coins = [Coin]()
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], let resultData = json["data"] as? [String:Any] {
let dataObject = try JSONSerialization.data(withJSONObject: resultData.values.map({$0}) , options: .prettyPrinted)
coins = try JSONDecoder().decode([Coin].self, from: dataObject)
print(coins.count)
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
You response in data parameter should be an array rather than a dictionary. You will not be able to iterate a dictionary over undefined keys. It would be good to get your response of API updated first.
But, If you wish to continue with the existing API response, first you need to convert your response in an array and use your Decodable structs as:
struct Coin: Decodable {
var id: String
var symbol : String
var name : String
var priceAUD : QuoteStruct
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
}
struct QuoteStruct: Decodable {
let aud : PriceStruct
}
struct PriceStruct: Decodable {
let price : String
}
Update your data parsing in API block as:
guard let responseData = data else { return }
do {
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
if let jsonData = json as? [String: Any], let dataObject = jsonData["data"] as? [Int: Any] {
let coinArray = dataObject.map { $0.1 }
if let jsonData = try? JSONSerialization.data(withJSONObject: coinArray, options: .prettyPrinted) {
coins = try JSONDecoder().decode([Coin].self, from: jsonData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
} catch {
print("Error is : \n\(error)")
}

Resources