Parse twitter trend api results in swift - json

I using twitter trend api to get the name of all the trends.
I have the following setup:
let url = "\(APIConstants.Twitter.APIBaseURL)1.1/trends/place.json?id=1"
let client = TWTRAPIClient()
let statusesShowEndpoint = url
let params = ["id": "20"]
var clientError : NSError?
let request = client.urlRequest(withMethod: "GET", url: statusesShowEndpoint, parameters: params, error: &clientError)
client.sendTwitterRequest(request) { (response, data, connectionError) -> Void in
if connectionError != nil {
print("Error: \(connectionError)")
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: [])
} catch let jsonError as NSError {
print("json error: \(jsonError.localizedDescription)")
}
}
and after the call, json has the following data:
(
{
"as_of": "2012-08-24T23:25:43Z",
"created_at": "2012-08-24T23:24:14Z",
"locations": [
{
"name": "Worldwide",
"woeid": 1
}
],
"trends": [
{
"tweet_volume": 3200,
"events": null,
"name": "#GanaPuntosSi",
"promoted_content": null,
"query": "%23GanaPuntosSi",
"url": "http://twitter.com/search/?q=%23GanaPuntosSi"
},
{
"tweet_volume": 4200,
"events": null,
"name": "#WordsThatDescribeMe",
"promoted_content": null,
"query": "%23WordsThatDescribeMe",
"url": "http://twitter.com/search/?q=%23WordsThatDescribeMe"
},
{
"tweet_volume": 2200,
"events": null,
"name": "Sweet Dreams",
"promoted_content": null,
"query": "%22Sweet%20Dreams%22",
"url": "http://twitter.com/search/?q=%22Sweet%20Dreams%22"
}
]
}
)
From the above json data, i want to store all the name inside trends in an array in swift.

There is some data checking and casting you need to do to make sure you're getting back the data in the structure you'd expect, and then from there its simple iteration. Something along these lines should get you going:
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String:Any]
var names = [String]()
if let trends = json?["trends"] as? [[String:Any]] {
for trend in trends {
if let name = trend["name"] as? String {
names.append(name)
}
}
}
Note the various as? type checks to iterate over the structure safely. For a more in-depth review of cleanly working with JSON in Swift including reading the data in to type-safe structs, check out this official blog post from Apple.

Related

Decoder not decoding json at keypath

Im trying to decode some JSON, but it's not parsing it. I think it may have something to to with either an incorrect KeyPath or the object itself. But I cannot figure it out.
This is the JSON that I want to decode (I want the array inside the docs path):
{
"status": 200,
"data": {
"docs": [
{
"_id": "60418a6ce349d03b9ae0669e",
"title": "Note title",
"date": "2015-03-25T00:00:00.000Z",
"body": "this is the body of my note.....",
"userEmail": "myemail#gmail.com"
}
],
"total": 1,
"limit": 20,
"page": 1,
"pages": 1
},
"message": "Notes succesfully Recieved"
}
Here's my decode function:
extension JSONDecoder {
func decode<T: Decodable>(_ type: T.Type, from data: Data, keyPath: String) throws -> T {
let toplevel = try JSONSerialization.jsonObject(with: data)
if let nestedJson = (toplevel as AnyObject).value(forKeyPath: keyPath) {
let nestedJsonData = try JSONSerialization.data(withJSONObject: nestedJson)
return try decode(type, from: nestedJsonData)
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Nested json not found for key path \"\(keyPath)\""))
}
}
}
And i'm calling it like this:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let notes = try decoder.decode([Note].self, from: data, keyPath: "data.docs")
Finally, this is my Note Struct:
struct Note: Codable {
var title: String?
let date: Date?
var body: String?
let userEmail: String?
}
The problem was that I was trying to decode date as a Date object instead of a String as is shown on the JSON.
Thanks #vadian!

HTTP Post Requests Swift 5 with List of JSON objects

Hello I am using Swift 5 to make an HTTP POST request, the request returns a list of JSON Objects which I believe is causing my error, and I can't seem to find a solution as I have scoured almost every example I could find on stackoverflow and youtube, and Apples documentation, even though this seems like a basic concept.
Here is what my response would look like:
[
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx"
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
},
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx"
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
}
]
The code I am using to make my HTTP Request is:
let url = URL(string: "URLString")!
let json: [String:Any] = ["phone_number":phoneNumber]
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do{
request.httpBody = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
}catch let error {
print(error.localizedDescription)
}
//HTTP Headers
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//URL data task
let task = session.dataTask(with: request) { (data, response, error) in
guard error == nil else{
print("Error!")
return
}
guard let data = data else{
print("Error fetching data")
return
}
do {
guard let responseJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] else {
print("Unable to serialize response!") //FAILS HERE EVERY TIME
return
}
print(responseJSON)
print("WOO made it!")
}catch let error {
print("Error with response!")
}
}
task.resume()
I am not sure what the issue is exactly but I have indicated where it fails, and I have tried printing out the data before it converts it just to make sure it isn't empty and it says like 2000 bytes so I know its not empty. My guess is that there is a problem because it is in the format of a list of objects, rather than just one object as most of the examples have shown. I also am having some issues with my HTTP body being nil on my express server end sometimes, so Not sure if I am doing that part wrong. But as of Now I just want to be able to parse my response So I can begin debugging the rest. Thanks for any help.
You need [[String:Any]] instead of [String:Any]
guard let responseJSON = try JSONSerialization.jsonObject(with: data) as? [[String:Any]] else {
print("Unable to serialize response!") //FAILS HERE EVERY TIME
return
}
But it's better using JSONDecoder
do {
let res = try JSONdecoder().decode([Root].self,from:data)
}
catch {
print(error)
}
Correct json
[
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx",
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
},
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx",
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
}
]
struct Root: Codable {
let id, phoneNumber, firstName, lastName: String
let gender: String
let verified: Bool
enum CodingKeys: String, CodingKey {
case id = "_id"
case phoneNumber = "phone_number"
case firstName = "first_name"
case lastName = "last_name"
case gender, verified
}
}

Parsing a specific content out of a dictionary in a json file

I'm getting the error "Cannot cast value of type dictionary to array" when I run my code. I'm tryna parse from a json file but its not working. Please help.
Here's the json file and i'm specifically tryna print out just the name values:
{
"data": [
{
"id": "2f4628f7ef6dce33d856121f",
"name": "Top products",
"description": "This category contains all the top products on"
},
{
"id": "3f4628f7ef6dce33d856121d",
"name": "Trending products",
"description": "This category contains all the trending products"
}
],
"page": 0,
"size": 2,
"count": 2,
"status": 200,
"isSuccess": true
}
func fetchFrontPageSections() {
let urlString = APIConstants.baseurl + APIConstants.frontpageSections
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
do {
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
for dictionary in jsonData as! [[String: AnyObject]] {
print(dictionary["name"])
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
When I print the dictionary I'm getting that whole son file in my console which is fine but Im expecting to get a result like:
name: Top Products
name: Trending Products
In your constant jsonData you don't have an array, it's a dictionary as your json file explicitly show it. It contains multiple keys such as data, page, size, ...
You shoud access first the value related to the key data that contains an at this point the array of dictionary that you are looking for.
Parsing Json in Swift has never been easier since Codable.
here's an Excellent tutorial from the great guys at Ray Wenderlich: https://www.raywenderlich.com/3418439-encoding-and-decoding-in-swift
for your specific problem try this:
struct Info: Decodable {
let id: String
let name: String
let description: String
}
struct Dictionary: Decodable {
let data: [Info]
let page: Int
let size: Int
let count: Int
let status: Int
let isSuccess: Bool
}
let decoder = JSONDecoder()
let dictionary = try decoder.decode(Dictionary.self, from: json)

Dictionary to JSON being serialised twice in swift 3

I'm trying to make a post request with the following dictionary which is converted to JSON
let store = [
"newTask" : [
"project_name": "iOS",
"total_time":0,
"color":"blue"
]
]
I am serialising this using the following code and then making a http-POST request using the following options:
let jsonData = try? JSONSerialization.data(withJSONObject: store)
var request = URLRequest(url: URL(string: "http://localhost:3000/store")!)
request.httpMethod = "POST"
request.httpBody = jsonData
I am also running a json-server https://github.com/typicode/json-server with the following db.json file
{
"store": [
{
"id": 0,
"ios": {
"project_name": "iOS",
"total_time": 0,
"color": "blue"
}
},
{
"id": 1,
"elm": {
"project_name": "elm",
"total_time": 0,
"color": "blue"
}
}
]
}
The problem I am having is that the newly added item in the db looks incorrect with the following format:
{
"{\"newTask\":{\"project_name\":\"iOS\",\"total_time\":0,\"color\":\"blue\"}}": "",
"id": 10
},
I am unsure as to why it is serialising the whole dictionary as the key and then an empty string as the value.
Update
Here is the code that posts this to the server:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == 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)")
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
}
} catch let error {
print(error.localizedDescription)
}
}
task.resume()
As a side note, I've tried pining this via postman and it all works ok. Attaching a screenshot of it below.
Any help will be appreciated, Thanks!
When you construct your URLRequest would this line fix it for you?
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
In Postman you are sending it as application/json so I would expect you need to do the same in your swift code.

Parsing JSON response in Swift 3

I've got an API endpoint that returns JSON in the following format:
[
{
"id": "1",
"name": "John"
},
{
"id": "2",
"name": "Jane"
},
{
"id": "3",
"name": "Nick"
}
]
I am trying to parse this in Swift 3, but I can only find examples to parse JSON formatted like so:
{
"blogs": [
{
"needspassword": true,
"id": 73,
"url": "http://remote.bloxus.com/",
"name": "Bloxus test"
},
{
"needspassword": false,
"id": 74,
"url": "http://flickrtest1.userland.com/",
"name": "Manila Test"
}
],
"stat": "ok"
}
, which has an extra level above what mine does.
So, where examples I've seen are simply parsing their data like jsonResponse["blogs"], I can't do that as my format is different.
How can I parse the format I've got, or how can I return a format that is easier to parse?
Any suggestions appreciated, thanks!
You can just do the following :
let data = // Data received from WS
do {
let json = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions()) as? [[String : String]]
//json is now an array from dictionary matching your model
}
catch {
//handle error
}
This will parse it when placed in the network call.
do {
let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as! [[String : AnyObject]]
let firstPerson = json[0]
print(firstPerson)
let id = firstPerson["id"] as! String
print(id)
let name = firstPerson["name"] as! String
print(name)
} catch {
//handle error
}
Also, I tend to be against advising third party libraries, but SwiftyJSON is an exception I make. If you want to try it, add this to your pod file:
pod SwiftyJSON', '3.0.0'
Documentation: https://github.com/SwiftyJSON/SwiftyJSON
EDIT - Answering Comment:
Replacement line:
if let id = firstPerson["id"] as? String {
print(id)
}
Replacement line (if you need to hold on to the value):
var thisId: String?
if let id = firstPerson["id"] as? String {
thisId = id
}
print(thisId ?? "")
i don't really know swift but there might be the equivalent of ajax json encoding (server side you json_encode your array and client side you json_decode the response)
the idea is to have the same formatter that encodes and decodes the data