I'm trying to parse the observed wind speed value from the following json as a float (5.586995). The code below returns the value as an array (["5.586995"]). Is this the best approach in Swift 3? Any help is much appreciated.
{
"properties":{
"platform_type_description":"Weather and Sea Surface Data",
"parameters":[
{
"id":"wind_speed",
"units":"m s-1",
"depth":{
"value":"0",
"units":"m"
},
"observations":{
"times":[
"2017-04-21T14:08:00"
],
"values":[
"5.586995"
],
"quality_levels":[
"3"
]
}
},
{
"id":"wind_speed_of_gust",
"units":"m s-1",
"depth":{
"value":"0",
"units":"m"
},
"observations":{
"times":[
"2017-04-21T14:08:00"
],
"values":[
"7.39788"
],
"quality_levels":[
"3"
]
}
}
]
}
}
func getWind() {
let url = URL(string: "http://windapp.dev/")!
URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
if error == nil {
if data != nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
guard let properties = json["properties"] as? [String:Any],
let parameters = properties["parameters"] as? [Any],
let windspeedDict = parameters[0] as? [String:Any],
let observationsWindSpeedDict = windspeedDict["observations"] as? [String:[String]]
else {
print("Error!")
return
}
for (key, value) in observationsWindSpeedDict {
if key == "values" {
print(value)
}
}
} catch {}
}
} else {
print("Error: Couldn't connect to server")
}
}.resume()
}
This is the expected result, since the JSON contains the float value as a value of an array with only one element and you make value the whole array when iterating through your dictionary.
If you know your data will always look exactly like this, you should make your parsing function more specific and don't save any values as Any. If this is a response from your own backend and you think your project will get quite complex, you could use a 3rd party service (for example Swagger, though it has quite a few bugs) enabling you to write a more abstract specification of your JSON and generate both server and client side code automatically. However, I would only recommend this for large and complex projects.
Other than that I think your method is an optimal approach for parsing this response.
Related
I am a Swift beginner and I am trying to decode JSON objects from network request. I am trying to get the products into an array to use them later for the UI. Currently I am stuck on decoding the JSON and also having them placed in an array.
Here is the simplified version of the JSON i am trying to work with (but the full JSON can be found in the url):
{
"items": [
{
"product": {
"name": "Product name"
}
}
]
}
And here is the Swift code so far.
// Models
struct Root: Codable {
let items: [Item]
}
struct Item: Codable {
let product: Product
}
struct Product: Codable {
let name: String
}
//Product array
var productArray: [Product] = []
// URL and data fetching
let urlString: String = "https://api.jsonbin.io/b/60832bec4465377a6bc6b6e6"
func fetchData() {
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data, error == nil else {
return
}
do {
let result = try JSONDecoder().decode(Root.self, from: data)
DispatchQueue.main.async {
productArray = result
}
} catch {
print(error)
}
}
task.resume()
}
fetchData()
If anyone knows how I can get past this, I greatly appreciate the help. Currently been stuck on this for a few hours, and I can't seem to figure it out. Thanks in advance!
Just map the items array
DispatchQueue.main.async {
productArray = result.items.map(\.product)
}
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.
Swift n00b here.
I'm getting something like this as a response from a server:
[
{
"foo": [],
"bar":"asdf",
...
}
]
Now I understand how to parse regular JSON, but not when it has an array as the base element.
Here is code I used so far, which would work for regular JSON:
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling POST on \(String(describing: urlRequest.url?.absoluteURL))")
print(error!)
return
}
// make sure we got the data
guard let responseData = data else {
print("Error: did not receive data")
return
}
let responseString = String(data: responseData, encoding: String.Encoding.utf8) as String!
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
... // do whatever with the response
} catch {
print("an error occurred")
return
}
}
task.resume()
The error I'm getting from that is "error trying to convert data to JSON.
The easiest way to parse that response I can think of is making a substring from 1 to length - 1 and then parsing it, but that doesn't seem particularly safe.
Is there any way I can parse that response into a [Dictionary]?
Your JSON is regular JSON. An array at the top level is just as valid (and regular) as a dictionary.
Simply update your cast accordingly:
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
as? [[String: Any]] else {
That indicates that you have an array of dictionary. Now you can iterate the array and get each dictionary as needed.
I am attempting to use Swift 3 with (ObjectMapper) to map a players array from a given JSON response on a cut-down class object called "Player"; but am finding it difficult to map it.
// Sample JSON
{
"_meta": {
...
},
"fixtures": [{
...
}],
"players": [{
"name": "Smith",
"id": "15475-9524",
}]
}
However, I am finding it hard to get it to understand how to make it map properly as it always complains that it needs more context.
I am wanting my JSON consumer to get me the list of players then map all the players using Object Mapper into an array.
When I use
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: res)
It complains
Type of expression is ambiguous without more context
My class is as follows
class Player: NSObject, Mappable {
var name: String?
required init?(map: Map) {
super.init()
}
// Mappable
func mapping(map: Map) {
name <- map["name"]
}
}
I am using AlamoFire to consume the JSON.
Alamofire.request(url).responseJSON(completionHandler: {
response in
switch response.result {
case .success(let JSON):
guard let res = JSON as? [String:Any] else {
print ("Can't do this")
return
}
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: res)
print (players)
break
case .failure(let error):
print("** Request failed with error: \(error) **")
break
}
I don't quite understand how to use the ObjectMapper on the array I'm wanting to fetch.
Any assistance on this would be good.
I think you are confusing JSON Dictionary with Player's array.
Try this:
guard let res = JSON as? [String:Any] else {
print ("res:Can't do this")
return
}
guard let json_players = res["players"] as? [[String:Any]] else {
print ("json_players:Can't do this")
return
}
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: json_players)
I have the following JSON:
{
"stores":2,
"store_data":[
{
"store_id":1,
"store_name":"Target"
"store_color":"000000"
},
{
"store_id":2,
"store_name":"Walmart"
"store_color":"FFFFFF"
}
]
}
And I am collecting it (within a function) the following way (safeguards removed for simplicity):
let task = URLSession.shared.dataTask(with: baseURL) { (data, response, error) in
if let tmpRawData: NSDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary {
process(rawData: tmpRawData)
}
}
And sending it to the helper process function defined as:
func process(rawData: NSDictionary) -> Bool {
if let storeDataArray = rawData["store_data"] {
// Here I want to loop through the store_data array
}
}
And I am having some trouble looping through the array in the function above. Initially, I had tried:
for store: Dictionary<String, String> in storeDataArray {
// Do things with store["store_id"] here
}
But I am new to swift and am having trouble deciphering between NSArray, Array, Dictionary, NSDictionary, etc. I'm working in Swift 3. Any help is much appreciated!
First of all, don't annotate types that much. The compiler will tell you if it needs an explicit annotation
Second of all it's convenient to declare a type alias for a JSON dictionary
typealias JSONObject = [String:Any]
This is the task, tmpRawData is a dictionary – represented by {} in the JSON.
let task = URLSession.shared.dataTask(with: baseURL) { (data, response, error) in
if let tmpRawData = try JSONSerialization.jsonObject(with: data, options: []) as! JSONObject {
process(rawData: tmpRawData)
}
}
and the process function, the type alias makes everything more readable.
The value of rawData is an array of dictionaries – represented by [] in the JSON.
func process(rawData: JSONObject) -> Bool {
if let storeDataArray = rawData["store_data"] as? [JSONObject] {
for store in storeDataArray {
let storeID = store["store_id"] as! Int
let storeName = store["store_name"] as! String
let storeColor = store["store_color"] as! String
print(storeID, storeName, storeColor)
}
}
}
I have no idea why all tutorials suggests the mutableContainers option. You don't need it at all in Swift when using native collection types.