Can't pull value from json data - json

I've seen a number of questions and answers addressing this. I've tried it and I can't figure it out. I've accessed my owner server's responseJSON no problem. But now I am trying to consume a 3rd party API and am having trouble. I am using Alamofire and SwiftyJSON.
let json = JSON(data: data)
this is what json looks like:
{"maxResults":50,"startAt":0,"isLast":true,"values":[{"id":1,"self":"https://stackrank.atlassian.net/rest/agile/1.0/board/1","name":"JI board","type":"scrum"},{"id":2,"self":"https://stackrank.atlassian.net/rest/agile/1.0/board/2","name":"Board2","type":"scrum"}]}
Why can't i access any of the values?
json["maxResults"].numberValue gives me '0'
json["values"].arrayValue give me an empty array []
I've seen a bunch of answers regarding the encoding etc...but I couldn't get it to work.
Here is the snippet from Alamofire showing the response format:
Alamofire.request(request).responseJSON {
response in
switch response.result {
case .success:
success(response.data!)

The object you are getting must not be a SwiftJSON object. Here's playground code that works perfectly (requires SwiftyJSON.swift to be in the Sources folder):
let jsontext = "{\"maxResults\":50,\"startAt\":0,\"isLast\":true,\"values\":[{\"id\":1,\"self\":\"https://stackrank.atlassian.net/rest/agile/1.0/board/1\",\"name\":\"JI board\",\"type\":\"scrum\"},{\"id\":2,\"self\":\"https://stackrank.atlassian.net/rest/agile/1.0/board/2\",\"name\":\"Board2\",\"type\":\"scrum\"}]}"
let data = jsontext.data(using: .utf8)!
let json = JSON(data)
print(json["maxResults"].intValue)
print(json["values"].arrayValue)
This works perfectly.
In terms of the raw data, here's what another SO question proposed:
Alamofire.request(.GET, url).validate().responseJSON { response in
switch response.result {
case .Success(let data):
let json = JSON(data)
let maxResults = json["maxResults"].intValue
let values = json["values"].arrayValue
print(maxResults)
case .Failure(let error):
print("Request failed with error: \(error)")
}
}

Related

Parsing JSON with multiple identical identifiers in Swift

Im trying to parse JSON data from an RestAPI that gives me energy data for Norway (https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview)
<ProductionConsumptionOverviewViewModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Statnett.Driftsdata.RestApi.Models">
<ConsumptionData>
...
</ConsumptionData>
<Headers>
...
</Headers>
<HydroData>
<ProdConsOverViewModelItem>
<style>hydro</style>
<textTranslationId>General.Hydro</textTranslationId>
<titleTranslationId i:nil="true"/>
<value/>
</ProdConsOverViewModelItem>
<ProdConsOverViewModelItem>
<style i:nil="true"/>
<textTranslationId i:nil="true"/>
<titleTranslationId>ProductionConsumption.HydroSEDesc</titleTranslationId>
<value>4 840</value>
I know it reads like XML but the documentation said JSON so here goes. Im having the same issues reading it as XML still so.
I manage to read the JSON response fine, but Im having trouble "digging down" to the correct spot since the identifiers are the same for the different regions. Let say I wanted the data for the Hydro production below (see screenshot). How would i get that? Ive tries setting the [titleTranslationId] == "ProductionConsumption.HydroSEDesc" but that didnt work.
It looks like XML but the documentation said JSON ? which is why im trying to treat it as JSON.
<ProductionConsumptionOverviewViewModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Statnett.Driftsdata.RestApi.Models">
<ConsumptionData>
...
</ConsumptionData>
<Headers>
...
</Headers>
<HydroData>
<ProdConsOverViewModelItem>
<style>hydro</style>
<textTranslationId>General.Hydro</textTranslationId>
<titleTranslationId i:nil="true"/>
<value/>
</ProdConsOverViewModelItem>
<ProdConsOverViewModelItem>
<style i:nil="true"/>
<textTranslationId i:nil="true"/>
<titleTranslationId>ProductionConsumption.HydroSEDesc</titleTranslationId>
<value>4 840</value>
API response Screenshot
Im using SwiftyJSON to handle the response.
I found some other treads but couldnt get it to work for me. Anyone able to help? Heres my code:
func getEnergyData(url: String){
AF.request(url, method: .get).responseJSON{ response in
switch response.result {
case .success(let json):
print("json success")
//print(json)
let energyJSON : JSON = JSON(response.result)
self.updateEnergyData(json: energyJSON)
case .failure(let error):
print(error)
}
}
}
trying to parse it:
func updateEnergyData(json : JSON){
if let results = json["ProductionConsumptionOverviewViewModel"]["HydroData"]["ProdConsOverViewModelItem"]["value"].double{
print(results)
}
else{
print("parse fail")
}
}
}
When accessing this endpoint via a browser, the API returns XML, but otherwise it returns JSON.
No need for SwiftyJSON or other libraries, instead use Swift's Codable protocol.
For example, make a struct representing the object to decode:
struct HydroData: Decodable {
let value: String
let textTranslationId: String?
let titleTranslationId: String?
let style: String?
}
And another one to get these objects from their array:
struct HydroResult: Decodable {
let HydroData: [HydroData]
}
Then once you have downloaded the data from the endpoint, decode it, and use filter to find, in the array, the object you need:
let url = URL(string: "https://driftsdata.statnett.no/restapi/ProductionConsumption/GetLatestDetailedOverview")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
print("No data")
return
}
do {
let result = try JSONDecoder().decode(HydroResult.self, from: data)
if let seDesc = result.HydroData.filter({ $0.titleTranslationId == "ProductionConsumption.HydroSEDesc" }).first {
print(seDesc.value)
} else {
print("Error: no value")
}
} catch {
print(error.localizedDescription)
}
}
task.resume()
Note that the return type for value is a String, and in it the numbers are already formatted, and because of that you cannot directly convert them to Int or Double, you would have to use a custom formatter, but this is an entirely different topic.

Error "Garbage at end" when serializing valid JSON data with Alamofire

My code return a Code=3840 "Garbage at end." when I try to keep my data of a request to my api ... The JSON return is a Valid JSON accorded to jsonlint (tested with Postman):
{
"error": 0,
"message": "transaction_completed"
}
this is my code :
func request(urle : url, parameters : Parameters, completion: #escaping (JSON) -> Void)
{
Alamofire.request(getUrl(urlw: urle), method: .post, parameters: parameters).responseJSON
{
response in
if response.data != nil {
do{
let json = try JSON(data: response.data!)
completion(json)
}catch{
print(error)
}
}
}
}
and this is when I called the request function:
let parameters: Parameters=[
"key" : user.key,
"uid": user.id
]
api.request(urle: .buyStack, parameters: parameters) { json in
print(json)
}
Where did I go wrong?
So apparently your JSON is not valid, it has at the end some invalid values.
First thing to do. For the sake of the keeping the logic, you can use force unwrap (using !) because it's debugging. I'm not sure that this code compile, it's just a logic presentation.
let responseString = String(data: response.data, encoding: .utf8)
print("responseString: \(responseString)")
This gives:
{"error":1,"message":"Undefined APIKey"}[]
There is extra [] at the end, and it's then not a valid JSON. You can ask the developper to fix it. If you really can't, or want to continue developing while it's in progress in their side, you can remove the extra [].
You can check this answer to remove the last two characters, and then:
let cleanResponseJSONString = //check the linked answer
let cleanResponseData = cleanResponseJSONString.data(encoding: .utf8)
let json = try JSON(data: cleanResponseData)
Side note and debugging idea if it was more complicate:
I ask for print("data: \(response.data as! NSData)") because this print the hex data. Your issue could have been due to an invisible character at the end. If you don't know them, the least you can do is according to previous answer:
let jsonString = "{\"error\":1,\"message\":\"Undefined APIKey\"}" (that's almost reponseString)
let jsonData = jsonString.data(encoding: .utf8)
print("jsonData: \(jsonData as! NSData)")
And compare what the end looks like.
A debugger tip, you can use a answer like this one to convert hexDataString into Data and debug from it. I'd recommend to add a space, "<" and ">" removal before so you can easily copy/paste it from the debugger output.
Why? If it's long (many manipulation) to go where your issue lies (login in the app, certain actions to do etc.), this could save you time to debug it on another app (Playground, at the start of your AppDelegate, etc.).
Don't forget to remove all the debug code afterwards ;)
Not related to the issue:
if response.data != nil {
do {
let json = try JSON(data: response.data!)
...
} catch {
...
}
}
Should be:
if let data = response.data {
do {
let json = try JSON(data: data)
...
} catch {
...
}
}
Use if let, guard let to unwrap, avoid using force unwrap.

Difficulty Parsing JSON With Alamofire

I have this snippet of code:
Alamofire.request("https://api.waqi.info/feed/geo:10.3;20.7/?token=demo").responseJSON { response in
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
guard let JSON = response.result.value as? [String:Any],
let data = JSON["data"] as? [[String:Any]] else {
print("Could not parse weather values")
return
}
It seemed to be working a few days ago but now when I run the application it will print the Could not parse weather values indicating that it is not parsing the JSON data correctly. I've gone back and changed it to what it used to be but it seems to be broken still.
I'm hoping someone here will be able to help me out with this as it's a crucial component of my first project that will be published to the App Store.
EDIT: Just to add, it successfully prints the JSON data at the print("JSON: \(JSON)") line
Need I post my response word for word from the last time you posted this question?
You can parse your result following the printout of your JSON
response:
guard let JSON = response.result.value as? [String:Any],
let weather = JSON["weather"] as? [[String:Any]] else {
print("Could not parse weather values")
return
}
for element in weather {
if let description = element["description"] as? String {
print(description)
}
}
If you wanted to save the result, or do something based on a certain
result as you described, you could insert something else instead of
just print(description) like:
if description == "sunny" {
//do something
}
Let me know if this makes sense. Keep in mind that ( and ) in the
Xcode console means "Array".
I really ought to be flagging this as a duplicate of Save Alamofire Result as Variable?
Edit: Based on this JSON snippet - http://pastebin.com/XiGhNA26 - it should be easy to parse out the desired information with:
guard let JSON = response.result.value as? [String:Any],
let data = JSON["data"] as? [String:Any] else
{
print("Could not parse weather values")
return
}
The difference is that in this JSON response, the "data" parameter is not an array of dictionaries, it is just a dictionary itself.
In you guard statement you are doing the same thing twice. That's not necessary. Take a look at this code and test it out. I'm not able to test your code right now, but I believe the error is around the area of where the downcasting happens. I believe improving that will help you.
Alamofire.request("https://api.waqi.info/feed/geo:10.3;20.7/?token=demo").responseJSON { response in
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
let parsed = JSON as? NSDictionary //Here is your son data ready to be used
//We can access the data content
let data = parsed["data"] //The data is here and can be access using keys
print("Data: \(parsed["data"])")
}else{
print("Values don't exists")
return
}
}

Trouble converting JSON to data with Swift

I am trying to learn Swift. One of my projects is to try to retrieve JSON data from an internal web service (a group of Python CGI scripts) and convert it into a Swift object. I can do this easily in Python, but I am having trouble doing this in Swift. Here is my playground code:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
let endpoint: String = "http://pathToCgiScript/cgiScript.py"
let url = NSURL(string: endpoint)
let urlrequest = NSMutableURLRequest(URL: url!)
let headers: NSDictionary = ["User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)",
"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"]
urlrequest.allHTTPHeaderFields = headers as? [String : String]
urlrequest.HTTPMethod = "POST"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlrequest) {
(data, response, error) in
guard data != nil else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("Error calling script!")
print(error)
return
}
do {
guard let received = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as?
[String: AnyObject] else {
print("Could not get JSON from stream")
return
}
print(received)
} catch {
print("error parsing response from POST")
}
}
task.resume()
I know making a 'POST' to retrieve data may look odd, but that is how the system is set up. I keep on getting:
Could not get data from JSON
I checked the response, and the status is 200. I then checked the data's description with:
print(data?.description)
I got an unexpected result. Here is a snippet:
Optional("<0d0a5b7b 22535441 54555322 3a202244 6f6e6522 2c202242 55535922...
I used Mirror, and apparently the type is NSData. Not sure what to make of this. I have tried to encode the data with base64EncodedDataWithOptions. I have tried different NSJSONReadingOptions as well to no avail. Any ideas?
Update:
I used Wireshark to double check the code in the Playground. Not only was the call made correctly, but the data being sent back is correct as well. In fact, Wireshark sees the data as JSON. The issue is trying to turn the JSON data into a Swift object.
I figured out what was wrong. I was casting to the wrong type. This is the new code:
guard let received = try! NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? [AnyObject]
The JSON was not returning an array of dictionaries but an array of objects.

How to convert <AnyObject> response in AnyObject after an Alamofire request with JSON in Swift?

So I want to send a request to a specific API which is supposed to return a JSON file.
I am using Alamofire in order to get the JSON object :
dataFromAPI : JSON
Alamofire.request(.GET, myURL).responseJSON { (_, _, data) -> Void in
dataFromAPI = JSON(data)
}
My problem is that data is an array of AnyObject and the JSON function needs an AnyObject type. How can I transform one into another or resolve the problem ?
Not sure if I got your question, but will try to provide you an example of how I do it.
Changed code to your case.
Alamofire.request(.GET, myURL)
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseJSON { request, response, jsonResult in
switch jsonResult {
case .Success:
if let data = jsonResult.value as? AnyObject {
self.dataFromAPI = JSON(data)
}
case .Failure(_, let error):
print(error)
}
}
Normally I wouldn't do unwrapping to AnyObject, as it makes little sense.
I usually unwrap to [String: AnyObject] as I'm expecting Dictionary from my API, and then I attempt to convert it to my custom model class.
Correct me if I miss-understood the question. :)
Alamofire returns a Result<AnyObject> object. You should check if the result is a success or a failure before extracting the JSON:
Alamofire.request(.GET, myURL)
.responseJSON { request, response, result in
switch result {
case .Success(let JSON):
print("Success with JSON: \(JSON)")
case .Failure(let data, let error):
print("Request failed with error: \(error)")
if let data = data {
print("Response data: \(NSString(data: data, encoding: NSUTF8StringEncoding)!)")
}
}
}