Count number of objects in JSON array on a URL in swift5 - swift

I have a JSON array at url = https://api.github.com/users/greenrobot/starred .
I want to count how many objects are there in that array using swift. I don't want data present, I just want count of it.

Assuming you downloaded the contents of that URL into a variable data of type Data:
if let object = try? JSONSerialization.jsonObject(with: data, options: []) as? [[String: AnyHashable]] {
let count = object[0].keys.count
}

I assume that you are using Alamofire for making a network request. In the code below we are just extracting the value object from Alamofire result. We convert the value to Array of dictionaries and then you can just get the count.
AF.request("https://api.github.com/users/greenrobot/starred",method: .get).responseJSON { apiResponse in
switch apiResponse.result{
case .success(_):
let dictionary = apiResponse.value as? [[String:Any]]
print("dictionaryCount \(dictionary?.count ?? -1)")
case .failure(_):
print("error \(apiResponse.error?.underlyingError?.localizedDescription ?? "")")
}
}

The GitHub starred API returns a maximum of 30 items by default, in the case of greenrobot with a total number of 372 it's not meaningful.
A smart way to get the actual number of starred items is to specify one item per page and to parse the Link header of the HTTP response which contains the number of the last page
Task {
do {
let url = URL(string: "https://api.github.com/users/greenrobot/starred?per_page=1")!
let (_, response) = try await URLSession.shared.data(from: url)
guard let link = (response as? HTTPURLResponse)?.value(forHTTPHeaderField: "Link") else {
throw URLError(.badServerResponse)
}
let regex = try NSRegularExpression(pattern: "page=(\\d+)")
if let lastMatch = regex.matches(in: link).last {
let range = Range(lastMatch.range(at: 1), in: link)!
let numberOfStarredItems = String(link[range])
print(numberOfStarredItems)
} else {
print("No match found")
}
} catch {
print(error)
}
}

Related

How to handle dynamic keys with single element as value in Swift

I'm having a problem figuring out how to handle dynamic keys with a single value one level deep. I've seen a few examples but they appear to handle other cases.
Here's an example.
[
{ "asdofjiodi": "asdofidj.com" },
{ "sadjlkj": "iejjol.com" },
{ "ijijwjljlijl": "adsijf.com" },
{ "jgncmkz": "mlkjaoijf.com" }
]
Any ideas on how I accomplish this with Codable or CodingKey? This is what I'd like for the end result.
["asdofidj.com", "iejjol.com", "adsijf.com", "mlkjaoijf.com"]
let url = Bundle.main.url(forResource: "file", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
// NOTE: The below line doesn't work because I'm not sure how to do the encoding/decoding
try? decoder.decode([[String: String]].self, from: data)
First of all, is not a valid JSON. A "Valid JSON" can be a JSON Array (multiple json objects) or a single JSON object (starts and ends with { and })
After cleaning this...
You are trying to make a dictionary (json object) from a data. You don't need JSONDecoder to accomplish this. Try using JSONSerialization with jsonObject static function...
let data = Data("{\"asdofjiodi\": \"asdofidj.com\",\"sadjlkj\": \"iejjol.com\",\"ijijwjljlijl\": \"adsijf.com\",\"jgncmkz\": \"mlkjaoijf.com\"}".utf8)
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let values = json?.map({ $1 })
print(values)
I Hope I helped!
JSONSerialization Apple documentation
Here is a minimal working example, given the JSON in your question:
let json = """
[
{ "asdofjiodi": "asdofidj.com" },
{ "sadjlkj": "iejjol.com" },
{ "ijijwjljlijl": "adsijf.com" },
{ "jgncmkz": "mlkjaoijf.com" }
]
"""
let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
let decodedJson = try decoder.decode([[String: String]].self, from: data)
let values = decodedJson.compactMap(\.values.first)
print(values)
} catch {
print("Error: \(error.localizedDescription)")
}
If this doesn't appear to work for you, it may be related to how you load in the JSON.

How do I read the property values of a JSON error object using Combine in Swift?

All of my API endpoints return a response which looks something like this in Postman:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Missing authentication"
}
What I would like to do is make the request, and have access to these properties in Swift. There will be some cases where I use the error message property's value in the front of the app. This will be determined by the statusCode returned.
What I have right now is this:
private var cancellable: AnyCancellable?
let url = URL(string: "http://abc336699.com/create")
self.cancellable = URLSession.shared.dataTaskPublisher(for: url!)
.map { $0.data }
Prior to this, I tried tryMap, but the type of error it returned didn't give me the flexibility I wanted. I then moved on and tried Almofire, but it seemed like an over kill for what I want to do.
I wanted to check what is being returned in the response I get, but I get the following error:
Cannot assign value of type 'Publishers.Map<URLSession.DataTaskPublisher, Data>' to type 'AnyCancellable'
I want simple access to my response errors so I can integrate the API throughout the app using combine.
I am not sure from where you will be getting your data as in JSON response there is no Key for data. Before writing below code my understanding was that you want to check error and statusCode from the mentioned JSON response and then move forward with your business logic. The below code is to give you a vague idea of how we can do that.
enum CustomError: Error {
case custom(_ error: String)
case unknownStatusCode
case errorOccurred
}
let url = URL(string: "http://abc336699.com/create")
func load() -> AnyPublisher<Data,CustomError> {
URLSession.shared.dataTaskPublisher(for: url!)
.map(\.data)
.tryMap { (data) -> Data in
let genericModel = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: AnyObject]
if let statusCode = genericModel?["statusCode"] as? String {
switch statusCode {
case "200":
guard let data = genericModel?["message"] as? Data else {
throw CustomError.custom("Parsing error")
}
return data
default:
if let error = genericModel?["error"] as? String {
throw CustomError.custom(error)
} else {
throw CustomError.unknownError
}
}
}
throw CustomError.errorOccurred
}
.decode(type: YourCustomDecodableModel.self, decoder: JSONDecoder())
.mapError({ $0 as? CustomError ?? CustomError.errorOccurred })
.eraseToAnyPublisher()
}

Swift 4 Get multiples places from Google Places API

I have trouble with fetching multiples places from Google Places API. The problem is... if i fetch only one pin type like Bars, its ok, no problem. But if i trying to get Restaurants, Bars, Casino... multiples types, it gives my only first place, in our case Restaurants.
I tried make same request with Postman with link below... but for example
restaurants 1030 lines of JSON
political 83 lines
trying to get restaurants + political = 965 lines of JSON.
I use this code to get places i want:
func fetchPlacesNearCoordinate(_ coordinate: CLLocationCoordinate2D, radius: Double, types:[String], completion: #escaping PlacesCompletion) -> Void {
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=\(coordinate.latitude),\(coordinate.longitude)&radius=\(50000)&rankby=prominence&sensor=true&key=\(googleApiKey)"
let typesString = types.count > 0 ? types.joined(separator: "|") : "food"
urlString += "&types=\(typesString)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? urlString
guard let url = URL(string: urlString) else { completion([]); return }
if let task = placesTask, task.taskIdentifier > 0 && task.state == .running {
task.cancel()
}
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
placesTask = session.dataTask(with: url) { data, response, error in
if data != nil{
for el in data!{
//print(el)
}
}
var placesArray: [PlaceContent] = []
defer {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
completion(placesArray)
}
}
guard let data = data else { return }
do{
let decode = try JSONDecoder().decode(GooglePlacesAnswer.self, from: data)
placesArray = (decode.results?.map{ $0.toPlaceContent() }) ?? []
} catch let value{
print(value.localizedDescription)
}
}
placesTask?.resume()
}
You can't get places for multiple types as stated in official documentation. You have to make multiple requests and combine the results.
https://developers.google.com/maps/documentation/javascript/places
type — Restricts the results to places matching the specified type.
Only one type may be specified (if more than one type is provided, all
types following the first entry are ignored). See the list of
supported types.

String Comparison giving unexpected result with data obtained via Alamofire

I am having issue with string comparison in Swift (I may wrong here).
Below is my method in which I am getting this issue.
Here is my input value.
self.callGrammerCheckAPI(ForText: """
ALSO BY STEPHEN HAWKING
A Brief History of Time
A Briefer History of Time
Black Holes and Baby Universes and Other Essays
The Illustrated A Brief History of Time
The Universe in a Nutshell
FOR CHILDREN
George's Secret Key to the Universe (with Lucy Hawking)
George's Cosmic Treasure Hunt (with Lucy Hawking)
ALSO BY LEONARD MLODINOW
A Briefer History of Time
The Drunkard's Walk: How Randomness Rules Our Lives
Euclid's Window: The Story of Geometry from Parallel Lines to Hyperspace
Feynman's Rainbow: A Search for Beauty in Physics and in Life
FOR CHILLDRAN
The Last Dinosaur (with Matt Costello)
Titanic Cat (with Matt Costello)
""")
Here is the method I am calling.
func callGrammerCheckAPI(ForText text:String) -> Void {
var valueToValidate = (text.components(separatedBy: .whitespacesAndNewlines).joined(separator: "+"))
let url = "https://montanaflynn-spellcheck.p.rapidapi.com/check/?text=\(valueToValidate)"
let headers: HTTPHeaders = [
"X-RapidAPI-Key": "3LlMwHr729mshN6RendH4kjvVp1pp1ogORZjsng5F2pBZdjsL3"
]
Alamofire.request(url, headers: headers).responseJSON { response in
print("Request: \(String(describing: response.request))") // original url request
print("Response: \(String(describing: response.response))") // http url response
print("Result: \(response.result)") // response serialization result
let attributedText = NSMutableAttributedString.init(string: text)
switch response.result{
case .success(let returnValue):
print(returnValue)
if let valueToParse = returnValue as? [String:Any]{
if let correctionsFound = valueToParse["corrections"] as? [String:Any]{
attributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.black, range: NSRange.init(location: 0, length: attributedText.string.utf16.count))
for key in correctionsFound.keys{
let unwantedCharRegex = try! NSRegularExpression.init(pattern: "([(/\')])", options: NSRegularExpression.Options.caseInsensitive)
let formatedKey = unwantedCharRegex.stringByReplacingMatches(in: key, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange.init(location: 0, length: key.count), withTemplate: "")
let expression = "(\(formatedKey))."
let regex = try! NSRegularExpression.init(pattern: expression , options: NSRegularExpression.Options.caseInsensitive)
let matches = regex.matches(in: attributedText.string, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange.init(location: 0, length: attributedText.string.utf16.count))
outerloop: for result in matches{
guard let availableOptions = correctionsFound[key] as? [String] else{
return
}
let valueToFound:NSString = NSString.init(string: attributedText.string.subString(from: result.range.location, to: result.range.location+result.range.length))
for suggestion in availableOptions{
let leftValue:NSString = NSString.init(string: suggestion)
let rightValue:NSString = valueToFound
print("\(leftValue) == \(rightValue) = \(leftValue == rightValue)")
if (leftValue == rightValue) == true {
print("Continue Done")
continue outerloop
}
}
attributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.red, range: result.range)
}
}
}
}
break
case .failure(let error):
print(error)
break
}
OperationQueue.main.addOperation {
self.tvReference.attributedText = attributedText
self.navigationItem.prompt = nil
}
if let json = response.result.value {
print("JSON: \(json)") // serialized json response
}
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)") // original server data as UTF8 string
}
}
}
Issue is when I compare leftValue and rightValue even though both the values are same it returns false. I do not know what went wrong there.

Call to firebase database

I'm attempting to make a call to the real time database that I have constructed in firebase. I have utilized the three different methods stated in the documentation, however, I still get an unacceptable access code.
Below are the current code and method that I am using.
let apiToContact = "https://gameofchats-c29ab.firebaseio.com/users.json?auth=AIzaSyBK3J4tzVQNJlhqqmYPRWXlJqnzFB1Hj4k"
let request = Alamofire.request(apiToContact, method: .get, parameters: nil, encoding: URLEncoding.queryString, headers: nil)
request.validate().responseJSON(){ response in
switch response.result {
case .success:
if let value = response.result.value {
let json = JSON(value)
let allEventData = json["events"].arrayValue
// Do what you need to with JSON here!
// print(json)
// print("\n\nlook here\n\n")
// print(allRestaurantData)
// The rest is all boiler plate code you'll use for API requests
print("\n\nlook here\n\n")
print(allEventData)
// var firstRestaurant = allRestaurantData[0]
//print(firstRestaurant)
// will populate all Restaurants Array with restaurant objects
/*
for restaurantStruct in allRestaurantData {
self.allRestaurants.append(Restaurants(json: restaurantStruct))
}
for restaurantsName in self.allRestaurants{
print(restaurantsName.restaurantApiKey)
print("\n")
}
*/
// will take the imageUrl array and populate it with the logoURL of the restaurants for later user
/* for images in self.allRestaurants{
self.imageUrl.append(images.logoUrl)
print(images.logoUrl)
print("\n")
}
*/
}
case .failure(let error):
print(error)
}