Store values from a JSON file into variables - json

I've been trying for hours to read values from a JSON file in the app bundle and print those values. I've been scouring several forums and SO questions trying to piece together a way to read from a .json file, but have had no luck. What I currently have is a bunch of (probably useless) code that looks like this:
//json parsing
if let path = Bundle.main.path(forResource: "POIs", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let pois = jsonResult["POI"] {
print("test ", pois)
for poi in pois as! [AnyObject] {
guard let name = poi["name"], //String
let coordinatesJSON = poi["coordinates"] as? [String: Double],
let latitudeVar = coordinatesJSON["lat"],
let longitudeVar = coordinatesJSON["lng"],
let altitude = poi["altitude"], //Int
let facebookurl = poi["facebookurl"], //String
let poiImage = poi["pinImage"] //String
else {
return
}
and my POIs.json file looks like this:
{
"POI":[
{
"name": "Boston Paintball",
"coordinates": {
"lat": 42.401848,
"lng": -71.023843
},
"altitude": 7,
"facebookurl": "https://www.facebook.com/PlayBostonPaintball/",
"pinImage": "bp"
},
{
"name": "Chilis",
"coordinates": {
"lat": 42.402314,
"lng": -71.021655
},
"altitude": 7,
"facebookurl": "https://www.facebook.com/chilis",
"pinImage": "chilis"
}
]
}
Can anyone show me the best way to read values from a json, store them into the variables I specified, and then print them? Thank you!
UPDATED CODE:
//json parsing
let file = Bundle.main.url(forResource: "POIs", withExtension: "json")
let data = try? Data(contentsOf: file!)
let root = try? JSONDecoder().decode(Root.self, from: data!)
do {
let root = try JSONDecoder().decode(Root.self, from: data!)
print(root)
} catch {
print(error)
}
let decoder = JSONDecoder()
let pois = try? decoder.decode([Root].self, from: data!)
let pinUIImage = UIImage(named: Location.pinImage)
But still giving me a Instance member 'pinImage' cannot be used on type 'ViewController.Location' error.
UPDATE 2:
for poi in (root?.pointsOfInterest)! {
if let location = root?.pointsOfInterest.??? { //I dont know how to grab the poi from the current iteration. Any tips?

You should structure your data to conform to Codable protocol:
struct Root: Codable {
let pointsOfInterest: [Location]
private enum CodingKeys: String, CodingKey {
case pointsOfInterest = "POI"
}
}
struct Location: Codable {
let name: String
let coordinates: Coordinate
let altitude: Int
let facebookurl: String
let pinImage: String
}
struct Coordinate: Codable {
let latitude: Double
let longitude: Double
private enum CodingKeys: String, CodingKey {
case latitude = "lat", longitude = "lng"
}
}
do {
let root = try JSONDecoder().decode(Root.self, from: jsonData)
print(root)
} catch {
print(error)
}
This will print
Root(pointsOfInterest: [__lldb_expr_39.Location(name: "Boston
Paintball", coordinates: __lldb_expr_39.Coordinate(latitude:
42.401848000000001, longitude: -71.023842999999999), altitude: 7, facebookurl: "https://www.facebook.com/PlayBostonPaintball/",
pinImage: "bp"), __lldb_expr_39.Location(name: "Chilis", coordinates:
__lldb_expr_39.Coordinate(latitude: 42.402313999999997, longitude: -71.021654999999996), altitude: 7, facebookurl: "https://www.facebook.com/chilis", pinImage: "chilis")])

Related

Swift Data Model from JSON Response

I am running into an issue building the correct data model for the following JSON response.
{
"resources": [
{
"courseid": 4803,
"color": "Blue",
"teeboxtype": "Championship",
"slope": 121,
"rating": 71.4
},
{
"courseid": 4803,
"color": "White",
"teeboxtype": "Men's",
"slope": 120,
"rating": 69.6
},
{
"courseid": 4803,
"color": "Red",
"teeboxtype": "Women's",
"slope": 118,
"rating": 71.2
}
]
}
Here is the current model. No matter what I do I can't seem to get the model populated. Here is also my URL session retrieving the data. I am new to Swift and SwiftUI so please be gentle. I am getting data back however I am missing something.
import Foundation
struct RatingsResources: Codable {
let golfcourserating : [GolfCourseRating]?
}
struct GolfCourseRating: Codable {
let id: UUID = UUID()
let courseID: Int?
let teeColor: String?
let teeboxtype: String?
let teeslope: Double?
let teerating: Double?
enum CodingKeysRatings: String, CodingKey {
case courseID = "courseid"
case teeColor = "color"
case teeboxtype
case teeslope = "slope"
case teerating = "rating"
}
}
func getCoureRating(courseID: String?) {
let semaphore = DispatchSemaphore (value: 0)
print("GETTING COURSE TEE RATINGS..........")
let urlString: String = "https://api.golfbert.com/v1/courses/\(courseID ?? "4800")/teeboxes"
print ("API STRING: \(urlString) ")
let url = URLComponents(string: urlString)!
let request = URLRequest(url: url.url!).signed
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let response = try? JSONDecoder().decode([RatingsResources].self, from: data) {
DispatchQueue.main.async {
self.ratingresources = response
}
return
}
print("*******Data String***********")
print(String(data: data, encoding: .utf8)!)
print("***************************")
let ratingsData: RatingsResources = try! decoder.decode(RatingsResources.self, from: data)
print("Resources count \(ratingsData.golfcourserating?.count)")
semaphore.signal()
}
task.resume()
semaphore.wait()
} //: END OF GET COURSE SCORECARD
First of all, never use try? while decoding your JSON. This will hide all errors from you. Use try and an appropriate do/catch block. In the catch block at least print the error.
Looking at your model there seem to be three issues here.
You don´t have an array of RatingsResources in your array. It is just a single instance.
let response = try JSONDecoder().decode(RatingsResources.self, from: data)
RatingsResources is not implemented correct.
let golfcourserating : [GolfCourseRating]?
should be:
let resources: [GolfCourseRating]?
Your coding keys are implemented wrong instead of:
enum CodingKeysRatings: String, CodingKey {
it should read:
enum CodingKeys: String, CodingKey {
You should add enum CodingKey with resources at struct RatingsResources
And decode:
if let response = try? JSONDecoder().decode(RatingsResources.self, from: data) {
// Your response handler
}

Faild to decode JSON, SwiftUI

I can’t seem to decode my JSON. It locates, and loads, but fails to decode.
Here is the JSON example:
[{
"name": "Hartsfield Jackson Atlanta Intl",
"city": "Atlanta",
"country": "United States",
"iata_code": "ATL",
"_geoloc": {
"lat": 33.636719,
"lng": -84.428067
},
"links_count": 1826,
"objectID": "3682"}]
Here is my Struct:
struct Airports: Codable, Identifiable {
struct GeoLoc: Codable {
let lat: Double
let lng: Double
}
var id = UUID()
let name: String
let city: String
let country: String
let iata_code: String
let _geoloc: GeoLoc
let links_count: Int
let objectID: String }
Bundle etension:
extension Bundle {
func decode<T: Codable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "y-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}}
And I call it in my content View like this:
let airports: [Airports] = Bundle.main.decode("airports.json")
I get the fatalError("Failed to decode (file) from bundle.")
So my question is, what is wrong here? Is the mistake in my Airport Struct?
I know the bundle extension is working, I have used the same with many other JSONS.
It is beacuse of property of id in your structure your json has no key id and id in structure is not optional. You have to change it with
let id : UUID?
instead of
var id = UUID()

How to read local JSON file and output JSON in swift?

import Foundation
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> JSON
{
var json: JSON
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
let data = try Data(contentsOf: fileUrl, options: .mappedIfSafe)
json = try? JSONSerialization.jsonObject(with: data)
} catch {
print("Something goes wrong when reading local json file.")
}
}
return json
}
}
I try to read the local json file and output json. But the line json = try? JSONSerialization.jsonObject(with: data) gives an error saying Cannot assign value of type 'Any?' to type 'JSON'.
My json data looks like
{
"leagues":
[
{ "name": "Hockey",
"image": "hockey",
"games":
[
{
"game_state": "Final",
"game_time": 1456662600,
"home_team_city": "Alberta",
"home_team_name": "Pigs",
"home_team_score": 1,
"home_team_logo": "pig",
"visit_team_city": "Montreal",
"visit_team_name": "Fishes",
"visit_team_score": 4,
"visit_team_logo": "fish"
}
]
}
]
}
When I change the output type to be Any? I print the output and it seems missing some elements.
{
leagues = (
{
games = (
{
"game_state" = Final;
"game_time" = 1456662600;
...
How can I fix it?
Check the solution below, I used Codable for the JSON decoding.
import Foundation
struct Sports: Codable {
let leagues: [League]
}
struct League: Codable {
let name, image: String
let games: [Game]
}
struct Game: Codable {
let gameState: String
let gameTime: Int
let homeTeamCity, homeTeamName: String
let homeTeamScore: Int
let homeTeamLogo, visitTeamCity, visitTeamName: String
let visitTeamScore: Int
let visitTeamLogo: String
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> Sports?
{
let path = Bundle.main.path(forResource: fileName, ofType: "json")
let url = URL(fileURLWithPath: path!)
let sportsData = try? Data(contentsOf: url)
guard
let data = sportsData
else { return nil }
do {
let result = try JSONDecoder().decode(Sports.self, from: data)
print(result)
return result
} catch let error {
print("Failed to Decode Object", error)
return nil
}
}
}
ReadLocalJSON.readJSONFromFile(fileName: "test")
Step 1:- first make a modal class in your project
struct Welcome: Codable {
let leagues: [League]?
}
// MARK: - League
struct League: Codable {
let name, image: String?
let games: [Game]?
}
// MARK: - Game
struct Game: Codable {
let gameState: String?
let gameTime: Int?
let homeTeamCity, homeTeamName: String?
let homeTeamScore: Int?
let homeTeamLogo, visitTeamCity, visitTeamName: String?
let visitTeamScore: Int?
let visitTeamLogo: String?
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
Step 2 : - After getting response write this line,
let decoder = JSONDecoder()
let obj = try! decoder.decode(Welcome.self, from: jsonData!)
IF you have still problem let me know

Deserialization JSON swift 4.2

I try to deserialize my JSON by using Decodable protocol, also i use enum with CodingKey, but it doesn't work. I need only nested array (start with "indicator"), and only few fields (all of them in struct). I tried a lot of different options, but unfortunately..
P.S. Also i tried to do it without CodingKey. Anyway response was: "Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "country", intValue: nil)" Ofc i read this, maybe array is a reason(i mean this strange intValue)?
JSON
[
{
"page":1,
"pages":2,
"per_page":50,
"total":59,
"sourceid":"2",
"lastupdated":"2019-03-21"
},
[
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2018",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2017",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
...
]
]
My code
struct CountryObject: Decodable{
var country: CountryInfo
var date: Int
var value: Int?
private enum RawValues: String, Decodable{
case date = "date"
case vallue = "value"
}
}
struct CountryInfo: Decodable{//Country names
var id: String?
var value: String?
private enum RawValues: String, Decodable{
case id = "id"
case value = "value"
}
}//
let urlString = "https://api.worldbank.org/v2/country/SS/indicator/IP.PAT.RESD?format=json"
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) {(data,response,error) in
guard let data = data else {return}
guard error == nil else {return}
do{
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let countryObject = try! decoder.decode([CountryObject].self, from: data)
print(countryObject)
}catch let error{
print(error)
}
}.resume()
Create a root struct and decode the array with unkeyedContainer
struct Root : Decodable {
let info : Info
let countryObjects : [CountryObject]
init(from decoder: Decoder) throws {
var arrayContrainer = try decoder.unkeyedContainer()
info = try arrayContrainer.decode(Info.self)
countryObject = try arrayContrainer.decode([CountryObject].self)
}
}
struct Info : Decodable {
let page, pages, perPage: Int
let lastupdated: String
}
struct CountryObject : Decodable {
let country: CountryInfo
let date: String
let value: Int?
}
struct CountryInfo : Decodable { //Country names
let id: String
let value: String
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(Root.self, from: data)
let countryObjects = root.countryObjects
print(countryObjects)
} catch { print(error) }
(De)serializing the JSON twice is unnecessarily expensive.

Parse and array of objects in JSON to an Object in Swift

Hey I have the following JSON:
{
"monuments": [
{
"name": "Iglesia de Tulyehualco",
"description": "No hay descripción",
"latitude": 19.2544877,
"longitude": -99.012157
},
{
"name": "Casa de Chuyin",
"description": "Casa de Jesús",
"latitude": 119.2563629,
"longitude": -99.0152632
}
]
}
I get the following code to try parse each object but I'm getting the error that type Any has no member 'x'.
func loadMonuments() {
if let path = Bundle.main.path(forResource: "monuments", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let monumentsJson = jsonResult["monuments"] as? [Any] {
for m in monumentsJson {
print(m)
}
}
} catch {
// handle error
}
}
}
I want to get each property of the monument.
Option1:(recommended)
struct Root:Decodable {
let monuments:[InnerItem]
}
struct InnerItem:Decodable {
let name:String
let description:String
let latitude:Doube
let longitude:Double
}
//
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options:[])
let content = try JSONDecoder().decode(Root.self,from:data)
print(content)
}
catch {
print(error)
}
Option2:
if let jsonResult = jsonResult as? [String:Any] , let monumentsJson = jsonResult["monuments"] as? [[String:Any]] {
for m in monumentsJson {
print(m["name"])
}
}
In Swift4 was introduced Codable for serialization, so you must try to make your objects Codable like this:
struct Monument: Codable {
let name: String
let description: String
let latitude: String
let longitude: String
}
And then you can parse the object using this:
func loadMonuments() -> [Monument] {
guard let path = Bundle.main.path(forResource: "monuments", ofType: "json"),
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) else {
return []
}
do {
return try JSONDecoder().decode([Monument].self, from: data)
} catch {
print("error: \(error)")
return []
}
}