Parsing specific components of a JSON - json

I created two custom functions to return a JSON response as an array of custom objects. I want to display the output in a table view. The structure of my JSON is:
JSON
{
data = (
{
deviceReference = D100;
featureTypeId = 1;
latitude = "100.000000";
longitude = "100.000000";
object = Object;
owner = Owner;
street = StreetName;
},
{
deviceReference = D200;
featureTypeId = 1;
latitude = "200.000000";
longitude = "200.000000";
object = Object;
owner = Owner;
street = StreetName;
}
);
meta = {
"Heading 1" = H1;
"Heading 2" = H2;
};
}
The problem I face is, that I do not know how to implement the code, that the content I want to parse is within the data part of the JSON.
GET DATA
func getAllAEDs(completionHandler: (Result<[AED], NSError>) -> Void) {
Alamofire.request(Router.GetAllAEDs()).responseArray { (response: Response<[AED], NSError>) -> Void in
completionHandler(response.result)
}
}
PARSE JSON TO ARRAY OF OBJECTS
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let serializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .Success(let value):
print(value)
let json = SwiftyJSON.JSON(value)
var objects: [T] = []
for (_, item) in json {
if let object = T(json: item) {
objects.append(object)
}
}
return .Success(objects)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}

Related

Parsing different queries with one func using SWIFT

My problem is - I'm building Weather App that displays 20 different cities at the same time (that's the task). I can do it with one city when i put it in guard let url = URL(string: ) directly like this (London)
struct Constants {
static let API_KEY = "<api-key>"
static let baseURL = "https://api.openweathermap.org/data/2.5/weather?appid=\(API_KEY)&units=metric&q=" // + cityName
}
class APICaller {
static let shared = APICaller()
func getData(completion: #escaping(Result<[WeatherDataModel], Error>) -> Void) {
guard let url = URL(string: "\(Constants.baseURL)London") else { return } // Here is the city i've put
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _, error in
guard let data = data, error == nil else {
return
}
do {
let results = try JSONDecoder().decode(MainWeatherDataModel.self, from: data)
completion(.success(results.results))
} catch {
completion(.failure(error))
}
}
task.resume()
}
}
My project contains CollectionView inside TableView. Parsed data filling Cells
But it's only one city showing in App. I need 19 more.
So my questions are: How can I implement different queries in URL or Is there a method do to multiple parsing?
Thank you
Here is a very basic example code, to fetch the weather for a number of cities using your modified setup. It shows how to implement different queries using the URL, as per the question.
Note, you should read about (and use) Swift async/await concurrency, to fetch
all the data concurrently.
struct Constants {
static let API_KEY = "api-key"
static let baseURL = "https://api.openweathermap.org/data/2.5/weather?appid=\(API_KEY)&units=metric&q="
}
class APICaller {
static let shared = APICaller()
// -- here
func getData(cityName: String, completion: #escaping(Result<[WeatherDataModel], Error>) -> Void) {
// -- here
guard let url = URL(string: (Constants.baseURL + cityName)) else { return }
URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _, error in
guard let data = data, error == nil else { return }
do {
let results = try JSONDecoder().decode(MainWeatherDataModel.self, from: data)
// -- here
if let weather = results.weather {
completion(.success(weather))
} else {
completion(.success([]))
}
} catch {
completion(.failure(error))
}
}.resume()
}
}
struct ContentView: View {
#State var citiesWeather: [String : [WeatherDataModel]] = [String : [WeatherDataModel]]()
#State var cities = ["London", "Tokyo", "Sydney"]
var body: some View {
List(cities, id: \.self) { city in
VStack {
Text(city).foregroundColor(.blue)
Text(citiesWeather[city]?.first?.description ?? "no data")
}
}
.onAppear {
for city in cities {
fetchWeatherFor(city) // <-- no concurrency, not good
}
}
}
func fetchWeatherFor(_ name: String) {
APICaller.shared.getData(cityName: name) { result in
switch result {
case .success(let arr): citiesWeather[name] = arr
case .failure(let error): print(error) // <-- todo
}
}
}
}
struct WeatherDataModel: Identifiable, Decodable {
public let id: Int
public let main, description, icon: String
}
struct MainWeatherDataModel: Identifiable, Decodable {
let id: Int
let weather: [WeatherDataModel]?
}

SwiftUI: Waiting for JSON to be decoded before

I'm having an issue with displaying a deserialised JSON object in a view. The problem seems to be that my view is trying to unwrap a value from a published variable before anything is assigned to it by the function that gets the JSON object.
Here is the code that calls the api
class ViewModel : ObservableObject {
#Published var posts : first?
init(subReddit : String){
fetch(sub: subReddit)
}
func fetch(sub : String) {
guard let url = URL(string: "https://www.reddit.com/r/" + sub + "/top.json?t=day") else {
return
}
let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let data = data, error == nil else {return}
do{
let retVal = try JSONDecoder().decode(first.self, from:data)
DispatchQueue.main.async {
self?.posts = retVal
}
}
catch{
print(error)
}
}
task.resume()
}
}
and here is the code for my view:
struct SubRedditView: View {
#StateObject var viewModel = ViewModel(subReddit: "all")
var body: some View {
NavigationView{
List{
ForEach((viewModel.posts?.data.children)!) {post in//at runtime I get a nil unwrap error here
Text(post.data.title)
Text(post.data.url_overridden_by_dest ?? "No Value")
}
}
.navigationTitle("Posts")
}
}
}
If only the object representing the children is relevant declare the published object as empty array of this type
#Published var posts = [TypeOfChildren]()
Then assign the children to the array
self?.posts = retVal.data.children
This makes the code in the view easier and safe.
ForEach(viewModel.posts) { post in
Text(post.title)
Text(post.url_overridden_by_dest ?? "No Value")

Swift add key to local JSON for saving checkmarks

Im trying to save checkmarks in my application. But cause im paring my data from an api.. I don't know how I can add like the key "checked". The thing is the JSON gets downloaded once a Week, adding new content. Is there a way to still save my checkmarks?
struct Base : Codable {
let expireDate : String
let Week : [Weeks]
}
struct Weeks : Codable {
let name : String
let items : [Items]
}
struct Items : Codable {
let Icon: String
let text : String
}
In my RootTableView I have the array Weeks, and I would like to add checkmarks to the child tableView Items.
Thanks in advance
UPDATE:
//
// Download JSON
//
enum Result<Value> {
case success(Value)
case failure(Error)
}
func getItems(for userId: Int, completion: ((Result<Base>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.jsonbin.io"
print(NSLocale.preferredLanguages[0])
let preferredLanguage = NSLocale.preferredLanguages[0]
if preferredLanguage.starts(with: "de"){
urlComponents.path = "/b/xyz"
}
else
{
urlComponents.path = "/xyz"
}
let userIdItem = URLQueryItem(name: "userId", value: "\(userId)")
urlComponents.queryItems = [userIdItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"secret-key": "xyzzy"
]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonDataItems = responseData {
let decoder = JSONDecoder()
do {
let items = try decoder.decode(Base.self, from: jsonDataItems)
completion?(.success(items))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
func loadJson() {
getItems(for: 1) { (result) in
switch result {
case .success(let item):
self.saveItemsToDisk(items: item)
self.defaults.set(item.expireDate, forKey: "LastUpdateItems")
case .failure(let error):
fatalError("error: \(error.localizedDescription)")
}
self.getItemesFromDisk()
}
}
//
// Save Json Local
//
func getDocumentsURL() -> URL {
if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
return url
} else {
fatalError("Could not retrieve documents directory")
}
}
func saveItemsToDisk(items: Base) {
// 1. Create a URL for documents-directory/items.json
let url = getDocumentsURL().appendingPathComponent("items.json")
// 2. Endcode our [Item] data to JSON Data
let encoder = JSONEncoder()
do {
let data = try encoder.encode(items)
// 3. Write this data to the url specified in step 1
try data.write(to: url, options: [])
} catch {
fatalError(error.localizedDescription)
}
}
func getItmesFromDisk(){
// 1. Create a url for documents-directory/items.json
let url = getDocumentsURL().appendingPathComponent("items.json")
let decoder = JSONDecoder()
do {
// 2. Retrieve the data on the file in this path (if there is any)
let data = try Data(contentsOf: url)
// 3. Decode an array of items from this Data
let items = try decoder.decode(Base.self, from: data)
itemsDisk = items
} catch {
}
}
I would create a wrapper class (or struct) for Items, say MyItem, that contains the original Items object and the checkmark property.
class MyItem {
let item: Items
var checkmark: Bool
//more properties...?
init(withItem item: Items {
this.item = item
this.checkmark = false
}
func isEqual(otherItem item: Items) -> Bool {
return this.item == item
}
}
The isEqual is used to check if there already exists an MyItem object for a downloaded Items object or if a new should be created. isEqual assumes that you change the Items struct to implement the Equatable protocol.
You probably also need to replace Weeks but here you don't need to include the original Weeks object.
class MyWeek {
let name: String
let items: [MyItem]
}

Handle Alamofire asynchronous request using SwiftyJSON

I am trying to parse JSON data using SwiftyJSON into an array to use in my TableView. However even though I can successfully request the data and parse it into an array, I cannot return it from the getObjects function as it is done asynchronously. I have tried to use a completion handler, and after following several tutorials it seems I am missing something.
Does anybody know how I can return the array to use in my TableViewController ?
Table View Controller
let objects = [Objects]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let urlString = "URLSTRING"
objects = dataManager.getObjects(urlString)
print("objects in view controller products array \(objects.count)")
self.tableView.reloadData
}
Request Functions
class DataManager {
func requestObjects(_ stringUrl: String, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void) {
print("Request Data")
Alamofire.request(stringUrl, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
print("responce success")
success(json)
case .failure(let error):
print(error)
} //End of switch statement
} //End of alamofire request
} //End of request function
func getObjects(_ urlString:String) -> [Object] {
var objects = [Object]()
requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse
for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}
}
You need to use completionHandler to return data to TableViewController.
func getObjects(completionHandler : #escaping ([Object]) -> (),_ urlString:String) -> [Sneaker] {
var objects = [Object]()
requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse
for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
completionHandler(objects)
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}
}
In your TableViewController
dataManager.getObject(completionHandler: { list in
self.objects = list
}, urlString)
There could be some syntax error i didnt test it

Convert a callback Swift JSON AnyObject into a NSDictionary

I have a network connection with reads the data using JSON and gives a callback;
executeRequestURL(requestURL: url, taskCallback: {(status, resp) -> Void in
if (status == true) {
if let results = resp as? NSDictionary {
print ("\(results.count) results found")
let list = results.allValues.first as! NSArray
print (list)
}
} else {
print ("Error -- \(resp)")
}
})
This calls;
private class func executeRequestURL(requestURL: NSURL, taskCallback: #escaping (Bool, AnyObject?) -> ()) {
print ("Attempting URL -- \(requestURL)")
let request: NSURLRequest = NSURLRequest(url: requestURL as URL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: kAPI_TIMEOUT)
let session: URLSession = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error)
return
}
guard let data = data else {
print("Data is empty")
return
}
let json = try! JSONSerialization.jsonObject(with: data, options: [])
//print(json)
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
taskCallback(true, json as AnyObject?)
} else {
taskCallback(false, json as AnyObject?)
}
})
task.resume()
}
The problem I have is that I want to read the results into a dictionary, loop through it and create objects.
For now, I will put my code in the executeRequestURL just to ensure it works, but I intend to seperate this code away for the required entity.
Question:
How do I read the resp as a dictionary?
Thanks
Sample response follows;
{
"objects": [
{
"uid": "coll_20ce39424470457c925f823fc150b3d4",
"title": "Popular",
"temp_image": "",
"body": "",
"active": true,
"slug": "popular",
"created": "2014-10-25T12:45:54+00:00",
"modified": "2014-10-25T12:45:54.159000+00:00",
"ends_on": "2100-01-01T00:00:00+00:00",
}
]
}
As the JSON is a dictionary, return a dictionary ([String:Any]) from the callback. In Swift 3 AnyObject has become Any. The strong type system of Swift encourages to be always as specific as possible.
Do a better error handling! You should return an error rather than just false.
The code uses the new Swift 3 structs URL and URLRequest
private class func executeRequestURL(requestURL: URL, taskCallback: #escaping (Bool, [String:Any]?) -> ()) {
print ("Attempting URL -- \(requestURL)")
let request = URLRequest(url: requestURL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: kAPI_TIMEOUT)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
guard error == nil else {
print(error)
taskCallback(false, nil)
return
}
guard let data = data else {
print("Data is empty") // <- this will never be reached. If there is no error,
taskCallback(false, nil) // data is always non-nil.
return
}
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
let json = try! JSONSerialization.jsonObject(with: data, options: []) as! [String:Any]
taskCallback(true, json)
} else {
taskCallback(false, nil)
}
})
task.resume()
}
The JSON result contains a dictionary with one key objects which contains an array of dictionaries. JSON collection types are very easy to distinguish: {} is dictionary, [] is array.
To map the JSON to objects create a struct
struct Item {
var uid : String
var title : String
var tempImage : String
var body : String
var active : Bool
var slug : String
var created : String
var modified : String
var endOn : String
}
and an array
var items = [Item]()
Then map the dictionaries to Item
if let objects = json["objects"] as? [[String:Any]] {
for object in objects {
let uid = object["uid"] as! String
var title = object["title"] as! String
var tempImage = object["temp_image"] as! String
var body = object["body"] as! String
var active = object["active"] as! Bool
var slug = object["slug"] as! String
var created = object["created"] as! String
var modified = object["modified"] as! String
var endOn = object["end_on"] as! String
let item = Item(uid: uid, title: title, tempImage:tempImage, body: body, active: active, slug: slug, created: created, modified: modified, endOn: endOn)
items.append(item)
}
The JSON values seem to come from a database which includes always all fields so the forced unwrapped values are safe.
I've done it like so:
func getHttpData(urlAddress : String)
{
// Asynchronous Http call to your api url, using NSURLSession:
guard let url = URL(string: urlAddress) else
{
print("Url conversion issue.")
return
}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
// Check if data was received successfully
if error == nil && data != nil {
do {
// Convert NSData to Dictionary where keys are of type String, and values are of any type
let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String:AnyObject]
// Call whatever function you want to do with your dictionary
useMyDictionary(dictionary: json)
} catch {
print(error)
// Something went wrong
}
}
else if error != nil
{
print(error)
}
}).resume()
}
There are many other ways but I like to do it using ObjectMapper. it looks cleaner to me. So just create a new Swift file, import ObjectMapper and write below code.
class yourDataModel: Mappable {
// MARK: - Constants & Variables
var myObjects: [yourDataModel]
required init?(_ map: Map) {
myObjects = []
}
func mapping(map: Map) {
myObjects <- map["objects"]
}
}
class YourCustomObjects: Mappable {
// MARK: - Constants & Variables
var userId:String
var title:String
var tempimage:String
var body:String
var active:Bool
var slug : String
var createdDate:String
var modifiedDate:String
var endDate:String
// MARK: - init
required init?(_ map: Map) {
userId = ""
title = ""
tempimage = ""
body = ""
active = false
slug = ""
createdDate = ""
modifiedDate = ""
endDate = ""
}
func mapping(map: Map) {
userId <- map["uid"]
title <- map["title"]
tempimage <- map["temp_image"]
body <- map["body"]
active <- map["active"]
slug <- map["slug"]
createdDate <- map["created"]
modifiedDate <- map["modified"]
endDate <- map["ends_on"]
}
}
Basically its your model class, now you just have to pass it your result in JSON which will be an AnyObject hopefully, and it will give you an array containing all your "objects" in it. You can use it like below
if let data = Mapper<yourDataModel>().map(resp){
print(data)
}
Try this, and let me know if you face any difficulty.