Array from a jsonDecoder object - arrays

This might be a very easy question (sorry!).
I would like to links a mySQL database to a Quiz App in Swift 4.
Therefore I connected to a service.php and got the information with decodable.
How can I access this information to show in a label? Do I have to make a new Array and append the objects?
import UIKit
struct Question: Codable {
let id: String?
let frage: String?
let antwort1: String?
let antwort2: String?
let antwort3: String?
let antwort4: String?
let correct: String?
let notiz: String?
let lernsektorID: String?
let lerneinheitID: String?
let lernbereichID: String?
let schwierigkeitID: String?
let redakteur: String?
let createdAt: String?
enum CodingKeys: String, CodingKey {
case id = "ID"
case frage = "Frage"
case antwort1 = "Antwort1"
case antwort2 = "Antwort2"
case antwort3 = "Antwort3"
case antwort4 = "Antwort4"
case correct = "Correct"
case notiz = "Notiz"
case lernsektorID = "LernsektorID"
case lerneinheitID = "LerneinheitID"
case lernbereichID = "LernbereichID"
case schwierigkeitID = "SchwierigkeitID"
case redakteur = "Redakteur"
case createdAt = "created_at"
}
}
var fragen = [Question]()
let url = "https://redaktion.pflegonaut.de/service.php"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!) { (data, response, error) in
do {
self.fragen = try JSONDecoder().decode([Question].self, from: data!)
// can I use .append() here?
// maybe in a for loop?
} catch {
print(error)
}
}.resume()
So I can use the elements like:
//
// let randomizedQuestion = fragen.frage.randomElement()
//
// questionOutlet.text = randomizedQuestion
Thanks!

// NECESSARY?
var QuestionBankJson: [QuestionJson] {
var questionListJson = [QuestionJson]()
}
No, just declare one array and name the struct simply Question
var questions = [Question]()
and assign
do {
self.questions = try JSONDecoder().decode([Question].self, from: data!)
print(self.questions[1].Frage!)
} catch {
print(error) // never print something meaningless like "we got an error"
}
Notes:
Please conform to the naming convention that variable names start with a lowercase letter.
If you are responsible for the JSON declare also the keys lowercased otherwise use CodingKeys.
Declare the struct members as much non-optional as possible.
Never print a meaningless literal string when catching Codable errors. Print always the error instance.
Use a better date string format than this arbitrary German format. UNIX time stamp, SQL date string or ISO8601 are sortable and can be even decoded to Date.

Related

How can i add specific JSON value to another array in Swift?

I'm new in IOS programming.
I have a json array described with code below.
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as?
NSDictionary
print("json: \(String(describing: json))")
Output of code is;
json: Optional({
vendors = (
{
firm = "XXX firm";
id = 1;
"show_firm" = 1;
},
{
firm = "ZZZZZ firm";
id = 2;
"show_firm" = 1;
}
);
})
I want to add only firm values to another array like firms = ["XXX firm" , "ZZZZZ firm"]
How can I do that?
Any help would be greatly appreciated.
#witek bobrowski asked String(data: data!, encoding: .utf8) output.This output is below also. By the way json data comes from server as http post response.
json2: Optional("{\"vendors\":[{\"id\":\"1\",\"firm\":\"XXX firm\",\"show_firm\":\"1\"},{\"id\":\"2\",\"firm\":\"ZZZZZ firm\",\"show_firm\":\"1\"}]}")
I believe the best way to go is to decode the JSON and then add the firms value to an array.
struct model: Decodable{
var vendors: [decodingModel]
}
struct decodingModel: Decodable{
var firm: String
var id: Int
var show_firm: Int
}
let decoder = JSONDecoder()
do{
let result = try decoder.decode(model.self, from: jsonData)
let firmsArray = result.vendors.compactMap({$0.firm})
}catch{
print(error)
}
Since you have not posted your son structure, I can only assume you have a Json where vendor is an array of jsons. firmsArray is what you are looking for.
If this doesn't work is probably because of the wrong model and decodingModel. If you post your json structure, I will update the code so that you can properly decode your json
the best way is to create Decodable Model for your json as below:
struct Firm: Decodable {
let id: Int
let name: String
let showFirm: Int
enum CodingKeys: String, CodingKey {
case id
case name = "firm"
case showFirm = "show_firm"
}
}
I created this factory method to simulate your json response locally based on what you provided in the question
struct FirmFactory {
static func makeFirms() -> [Firm]? {
let json = [
[
"firm": "XXX firm",
"id": 1,
"show_firm": 1,
],
[
"firm": "ZZZZZ firm",
"id": 2,
"show_firm": 1,
],
]
// you should use the following code to decode and parse your real json response
do {
let data = try JSONSerialization.data(
withJSONObject: json,
options: .prettyPrinted
)
return try JSONDecoder().decode([Firm].self, from: data)
} catch {
print("error \(error.localizedDescription)")
return nil
}
}
}
now you will be able to map only the firm names as you request you can test like this
let firmNames = FirmFactory.makeFirms()?.map { $0.name }
print("firmNames \(firmNames)")
I answered my own question again. There are 2 answers given but i didn't use any of these in my code. May be these two answers are usable but because of i'm new in IOS i couldn't use any of them. At the end of long google search i solved my problem as below.
let vendors = json!["vendors"]! as! [[String : AnyObject]]
for firm in vendors {
let firm1 = firm["firm"]! as! String
self.providerArray.append(firm1)
}
I hope this answer solves someone else's problem like me.

Array only adds single entry to Struct after for-loop despite containing more entries - Swift/Xcode

Im running a for loop to create a filtered array of results that match an array of downloaded files. The output let items contains everything i want but i can not seem to get it to map across a struct properly. If i print(items) it shows all the results but if I print(self.podepisodes) after self.podepisodes = items it only shows 1 entry.
More code below for context:
struct Episodes: Codable {
let show: String
let showHost: String
let showDescription: String
let showArtURL: String
let epNumber: String
let epTitle: String
let epAudioURL: String
let epArtURL: String
let epLength: String
let epDescription: String
let releaseDate: String
let date: String
let exclusive: String
let subscriptionAudio1: String
let youtubeURL: String
}
var podepisodes: [Episodes]?
//have omitted some code here in the interest of not posting a huge page load. Can edit and add it if anyone would like.
let JSONData = try JSONDecoder().decode([Episodes].self, from: data)
DispatchQueue.main.async {
guard let trueLocation = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {return }
let filePath = trueLocation.path
let fileList: Array = try! self.fileManager.contentsOfDirectory(atPath: filePath)
for file in fileList {
let items = JSONData.filter { (item: Episodes) -> Bool in
return URL(string: item.epAudioURL)?.lastPathComponent == file
}
self.podepisodes = items
print(items)
}
print(self.podepisodes)
}
You must append items to the array, now you are overwriting the content of self.podepisodes each time in the loop. So replace self.podepisodes = items with
self.podepisodes.append(contentsOf: items)

How to sort an array by a specific key

I am using swapi.co as my source of json data and my response looks like the following: https://swapi.co/api/people/
My array of "characters" has the structure
// MARK: - CharactersResult
struct CharactersResult: Codable {
let name, height, mass, hairColor: String
let skinColor, eyeColor, birthYear: String
let gender: Gender
let homeworld: String
let films, species, vehicles, starships: [String]
let created, edited: String
let url: String
enum CodingKeys: String, CodingKey {
case name, height, mass
case hairColor = "hair_color"
case skinColor = "skin_color"
case eyeColor = "eye_color"
case birthYear = "birth_year"
case gender, homeworld, films, species, vehicles, starships, created, edited, url
}
}
I would like to get the smallest character and the largest out of my array. My function is:
func getCharacters(urlToGet: String){
do{
if let url = URL(string: urlToGet) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let jsonCharacters = try JSONDecoder().decode(Characters.self, from: data)
self.characters = self.characters + jsonCharacters.results
self.nextPageUrlForCharacters = jsonCharacters.next
self.updatePicker()
} catch let error {
print(error)
}
}
}.resume()
}
}
}
My main question is where to do the sorting and what is the most efficient way to get the smallest and the largest character.
Assuming smallest and largest is related to the height of the character (then shortest and tallest are more appropriate), sort the array by that struct member. As the value is String you have to add the .numeric option.
The descending order starts with the largest value
let sortedCharacters = self.characters.sorted{$0.height.compare($1.height, options: .numeric) == .orderedDescending}
let tallestCharacter = sortedCharacters.first
let shortestCharacter = sortedCharacters.last
Side note: You can get rid of the CodingKeys if you add the convertFromSnakeCase key decoding strategy.
This simplified version of your code illustrates how to sort by an arbitrary key, as long as that key is Comparable (so I've made your height and mass properties both Ints rather than String):
struct MovieCharacter: Codable {
let name: String
let height: Int
let mass: Int
}
let fred = MovieCharacter(name: "Fred", height: 123, mass: 99)
let wilma = MovieCharacter(name: "Wilma", height: 158, mass: 47)
let chars = [fred, wilma]
let heaviestToLightest = chars.sorted { $0.mass > $1.mass }
// Prints "Fred, Wilma"
print(heaviestToLightest.map { $0.name }.joined(separator: ", "))
let tallestToShortest = chars.sorted { $0.height > $1.height }
// prints "Wilma, Fred"
print(tallestToShortest.map { $0.name }.joined(separator: ", "))
To change the sort order, reverse the >' in the comparison closure. If you want to know the "most" and "least" of a particular order, use.firstand.last` on the result.
Also, to save yourself from having to maintain the CodingKeys enum, use .keyDecodingStrategy of a JSONDecoder:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let characters = try decoder.decode(Characters.self, from: jsonData)

parsing array of objects to array of string elements in swift JSON object

I am trying to parse the JSON object received from Web service which gives the result as JSON object of status and data.data again is a array of objects, from this object I want to take one element on the basis of which I have to fill a tableview.
web Service results comes as
{"status":1,"data":[{"service_id":"1","service_name":"Painter"},{"service_id":"2","service_name":"Plumber"},{"service_id":"3","service_name":"Electrician"},{"service_id":"4","service_name":"Handyman"},{"service_id":"5","service_name":"Carpenter"},{"service_id":"6","service_name":"Mason"}]}
parsing in swift I did as:--
created one class
class ABC: NSObject {
var service_name:String?
var service_id : Int?
init(service_name:String,service_id:Int) {
self.service_name = service_name
self.service_id = service_id
}
let myUrl = URL(string: "services.php");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json
{
let status=parseJSON["status"] as? Int
let newdata : NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
self.model=(newdata.value(forKey: "data") as? [ABC])!
My problem is I am getting an array of objects in self.model as service_name and service_id keys.Now I want to take out one array of strings that contains all the service_name object values.Its saying not able to convert NSArray to swift array.
As already mentioned by others use (De)codable. It's easy to use and very comfortable. No type cast and no literal keys.
Create two structs, declare the members as non-optional constants with camelCased names and omit the initializer.
struct Root : Decodable {
let status : Int
let data : [Service]
}
struct Service : Decodable {
let serviceName : String
let serviceId : String
}
Then decode the JSON in another class or struct
let myUrl = URL(string: "services.php")
var request = URLRequest(url: myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data!)
let status = json.status
let newdata = json.data
} catch { print(error))
}
task.resume()
I would recommend to drop JSONSerialization and use Codable protocol instead with CodingKeys.
Here is a simple example to see how it works.
struct Service : Codable {
let id : Int
let name : String
// keys
private enum CodingKeys: String, CodingKey {
case id = "service_id"
case name = "service_name"
}
}
...
// assuming our data comes from server side
let jsonString = "..."
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(Service.self, from: jsonData)
print("Getting service: \(service.id) \(service.name)")
} catch {
print("Unexpected error: \(error).")
}
More documentation here.
Use native Swift type Dictionary everywhere you use NSDictionary now
Then get certain value for key by specifing key in dictionary's subscript
if let model = newData["data"] as? [ABC] {
self.model = model
}
Anyway, I would suggest you to start using Codable instead of JSONSerialization
struct Response: Decodable {
let status: Int
let data: [ABC]
}
struct ABC: Decodable {
let serviceName: String
let serviceId: String // note that `serviceId` isn’t `Int` But `String`
}
and then decode your data using JSONDecoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(Response.self, from: data!)
self.model = response.data
} catch { print(error) }

Weird results with parsing array from json in Swift3

Hi guys I don't know why the array Places returns weird values like 0x6080004b3aa0 instead of displaying my title, coordinate and subtitle out of my JSON url. Thanks for your Help!
import MapKit
#objc class Place: NSObject {
var title: String?
var coordinate: CLLocationCoordinate2D
var subtitle: String?
init(title:String,subtitle:String, coordinate:CLLocationCoordinate2D){
self.title = title
self.coordinate = coordinate
self.subtitle = subtitle
}
static func getPlaces() -> [Place] {
guard let url = NSURL(string: "https://script.googleusercontent.com/macros/echo?user_content_key=Z-LfTMdhgAg_6SRd-iMucSyWu-LFBQO8MLxJZ6DPcL05Rtr3joCCypWD2l46qaegSpVpVINc1DLl5inoDOgGx3p3ANpY1AkGOJmA1Yb3SEsKFZqtv3DaNYcMrmhZHmUMWojr9NvTBuBLhyHCd5hHa1ZsYSbt7G4nMhEEDL32U4DxjO7V7yvmJPXJTBuCiTGh3rUPjpYM_V0PJJG7TIaKp4bydEiKBUZP6fpOyGJIhkmEGneM7ZIlWloTVbXmkjs15vHn8T7HCelqi-5f3gf3-sKiW3k6MDkf31SIMZH6H4k&lib=MbpKbbfePtAVndrs259dhPT7ROjQYJ8yx") else { return [] }
let request = NSMutableURLRequest(url: url as URL!)
var places = [Place]()
let task = URLSession.shared.dataTask(with: request as URLRequest) {data,response,error in
guard error == nil && data != nil else {
print ("Error:",error)
return
}
let httpStatus = response as? HTTPURLResponse
if httpStatus?.statusCode == 200
{ if data?.count != 0
{
let responseString = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! NSDictionary
let contacts = responseString["Sheet1"] as? [AnyObject]
for contact in contacts!{
var places = [Place]()
let title = contact["name"] as! String
let subtitle = contact["description"] as? String
let latitude = contact["latitude"] as? Double ?? 0, longitude = contact["longitude"] as? Double ?? 0
let place = Place(title:title,subtitle:subtitle!,coordinate: CLLocationCoordinate2DMake(latitude, longitude))
places.append(place)
print(latitude)
print(place)
}
}
else {
print("No data got from url")
}
} else {
print("error httpsatus code is:", httpStatus!.statusCode)
}
}
task.resume()
return places as [Place]
}
}
I think the problem is this:
let place = Place(title:title,subtitle:subtitle!,coordinate: CLLocationCoordinate2DMake(latitude, longitude))
When I print(place) it returns the weird results
When you make a class that subclasses from NSObject you're creating a object that is backed by an Objective-c class -- which in some circumstances can be really useful (most common use is when you want to take your object and archive it as a blob of binary data).
I'm guessing that in your case, you probably don't want/need to subclass NSObject.
Here's a simplified example to show what's happening:
Here's a class backed by NSObject:
#objc class ObjCPlace: NSObject {
let name: String
init(name: String) {
self.name = name
}
}
If you create an instance of this object and try to print contents - like you've found, you get the objects location in memory:
let testObjcPlace = ObjCPlace(name: "New York")
print(testObjcPlace)
// prints:
// <__lldb_expr_310.ObjCPlace: 0x600000055060>
On alternative to using print could be to use dump that provides a more detailed look at your object:
let testObjcPlace = ObjCPlace(name: "New York")
dump(testObjcPlace)
// Prints:
// ▿ <__lldb_expr_310.ObjCPlace: 0x600000055060> #0
// - super: NSObject
// - name: "New York"
But instead of making an NSObject subclass, you probably just want to make a Swift class (or in this example a struct take a look at this question and answers for explanations of the differences)
struct Place {
let name: String
init(name: String) {
self.name = name
}
}
Because this object isn't an Objective-c object you can use print and get the internal properties printed as well:
let testPlace = Place(name: "New York")
print(testPlace)
// Prints:
// Place(name: "New York")
p/s welcome to StackOverflow!

Resources