JSON parsing data in SWIFTUI - json

in my very basic code I need get data from JSON. But i get only one value.
i have this input JSON:
{
"features":
[
{
"geometry":
{
"coordinates": [
14.49961,
50.03353
],
"type": "Point"
}
},
{
"geometry":
{
"coordinates": [
14.4213,
50.00144
],
"type": "Point"
}
}
],
"type": "FeatureCollection"
}
and this code in SwiftUI, where i need get all data throw JSON. Value of field "type" work, but rest of field i can't get - what i do wrong?
import Foundation
import SwiftUI
import MapKit
import Combine
class fetchResults{
func getData(completion: #escaping (RequestA) -> ()){
let url = URL(string: "https://api.golemio.cz/v2/vehiclepositions?limit=2")!
var request = URLRequest(url: url)
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("XXXXXXXXXXXXXXXX", forHTTPHeaderField: "x-access-token")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let result = try! JSONDecoder().decode(RequestA.self, from: data!)
if let response = response {
if let data = data, let body = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
completion(result)
}
}
} else {
print(error ?? "Unknown error")
}
}
task.resume()
}
}
struct RequestA: Decodable {
var features: [Features]!
var type: String
}
struct Features: Decodable {
var geometry: Geometry
}
struct Geometry: Decodable {
var coordinates: [Double]
var type: String
}
struct ContentView: View {
#State var res = RequestA()
var body: some View {
Text(res.type ?? "N/A") // <<< "res.type" work but "res.features.geometry.coordinates[0] not
.onAppear()
{
fetchResults().getData
{(res) in
self.res = res
}
}
}
}
I will be very glad, if somebody help me a show me, what i do wrong.

res.features.geometry.coordinates
res is RequestA and a "RequestA" has a property features which is of type [Features] - an array of "Features"
Array of features doesn't have a property "geometry" so you can't write an expression like res.features.geometry
Did you mean for RequestA "features" property to be of type Features not [Features]? Or did you mean to do something for each one of the features in the array, or perhaps the first one etc?
If you're interested in parsing GeoJSON, then see Swift GeoJson Parse

If someone will solve a similar problem, I solved it like this inside body View:
print(res.features![0].geometry.coordinates[0])
Karel

Related

Swift / Combine JSON decodable - decode and receive array of content only 'Invalid top-level type in JSON write'

I am receiving some JSON which looks like the below :
{
"template": "search",
"item": "2",
"contents": [
{
"title": "title 1",
"subtitle": "subtitle 1",
"imageurl": "/data/dzzxw0177014_325qv.jpg?size=small",
"fullscreenimageurl": "/data/xw0177014_325qv.jpg?size=large",
"id": "0177014",
"detaillink": "/apps/v2/details/programme/177014",
"duration": "PT2H46M"
},
{
"title": "title2",
"subtitle": "subtitle 2",
"imageurl": "/data_p//11436/origin_dzdzdzdzw0046394_43fu1.jpg?size=small",
"fullscreenimageurl": "/data/11456/w0046394_43fu1.jpg?size=large",
"id": "0046394",
"detaillink": "/apps/v2/details/programme/MYW0046394",
"duration": "PT1H40M46S"
}
]
}
and I have a corresponding model:
import Foundation
// MARK: - Welcome
struct Welcome {
let template, item: String
let contents: [Content]
}
// MARK: - Content
struct Content {
let title, subtitle, imageurl, fullscreenimageurl: String
let id, detaillink, duration: String
}
I have an API manager :
import Foundation
import Combine
class APIManager {
static let shared = APIManager()
let baseURL = "https:// ....."
func fetchShows(with query: String) -> AnyPublisher<[Content], Error > {
Future<Any, Error> { promise in
self.loadJson(withQuery: query) { (result) in
promise(.success(result))
}
}
.tryMap {
try JSONSerialization.data(withJSONObject: $0, options: .prettyPrinted)
}
.decode(type: [Content].self, decoder: jsonDecoder)
.eraseToAnyPublisher()
}
var jsonDecoder: JSONDecoder {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}
func loadJson(withQuery query: String,
completion: #escaping (Result<Data, Error>) -> Void) {
let UrlString = baseURL + (query.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "")
if let url = URL(string: UrlString) {
print (UrlString)
let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
}
if let data = data {
completion(.success(data))
}
}
urlSession.resume()
}
}
}
At the moment I have a crash with the error "Invalid top-level type in JSON write", I assume this is because the JSON that I am trying to decode isn't an array of Content. It's a Welcome Struct which contains an array of Content.
At what point can I say that I am only interested in the Contents "Array" and decode it ? Should this be defined in the model some how ?
Thanks
You can use .map operator to transform your data objects.
.decode(type: Welcome.self, decoder: jsonDecoder) // <- here
.map { $0.contents } // <- here
.eraseToAnyPublisher()
In addition, you have to confirm your data objects to Decodable.
Adding Decodable keyword is enough, Since all the files types are Decodable here,
struct Welcome: Decodable { //<- Here
let template, item: String
let contents: [Content]
}
struct Content: Decodable { //<- Here
let title, subtitle, imageurl, fullscreenimageurl: String
let id, detaillink, duration: String
}

Decode JSON Array with no Attribute Name

I have looked through other threads regarding trying to parse JSON data where a JSON array has no name. From what I have found you need to use a unkeyedContainer but I'm not entirely sure from the examples I have seen how this works with the data model.
Below is a snippet of data from open charge api:
[
{
"IsRecentlyVerified": false,
"ID": 136888,
"UUID": "254B0B07-E7FC-4B4B-A37C-899BCB9D7261",
"DataProviderID": 18,
"DataProvidersReference": "0a9fdbb17feb6ccb7ec405cfb85222c4",
"OperatorID": 3,
"UsageTypeID": 1,
"AddressInfo": {
"ID": 137234,
"Title": "Ballee Road Park & Share",
"AddressLine1": "Ballee Road",
"Town": "Ballymena",
"Postcode": "BT42 2HD",
"CountryID": 1,
"Latitude": 54.844648,
"Longitude": -6.273606,
"AccessComments": "Ballee Road Park and Share, Ballymena",
"RelatedURL": "http://pod-point.com",
"Distance": 3.81818421833416,
"DistanceUnit": 2
},
"Connections": [
{
"ID": 191571,
"ConnectionTypeID": 25,
"Reference": "1",
"StatusTypeID": 50,
"LevelID": 2,
"Amps": 32,
"Voltage": 400,
"PowerKW": 22,
"CurrentTypeID": 20
},
It looks to me that the first [ and { have no attribute names which I belive is creating the error in xcode: "Error!: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))"
Here is my data model:
import Foundation
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
Here is my code:
//Find public chargers from local coordinates
func findPublicChargers(lat: Double, long: Double) {
//Use apiurl to pull all charge points that are currently in that area by adding lat and long into the api call &latitude=***&longitude=*****
let apiurl = "https://api.openchargemap.io/v3/poi/?output=json&countrycode=UK&maxresults=100&compact=true&verbose=false"
let urlString = "\(apiurl)&latitude=\(lat)&longitude=\(long)"
//print(urlString)
performRequest(urlString: urlString)
}
//Perform API Request - (London App Brewry code)
//Create the custom url
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
//print("Called")
//Create a URL Session
let session = URLSession(configuration: .default)
//Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
//print(dataString)
self.parseJSON(data: safeData)
print("Data: \(safeData)")
}
}
//Start the task
task.resume()
}
}
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(PublicCharger.self, from: data)
print("Data: \(decodedData.AddressInfo[0].Title)")
} catch {
print("Error!: \(error)")
}
}
struct AddressInfo: Decodable {
let Title: String
}
I have seen that in the data model you would need to include an unkeyed container element. I'm just not sure how this should be carried out in the data model. Any light on this would be much appreciated.
Try to change your PublicCharger data model to
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
And your parseJSON function to
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([PublicCharger].self, from: data)
if !decodedData.isEmpty {
print("Data: \(decodedData[0].AddressInfo[0].Title)")
} else {
print("Empty result!")
}
} catch {
print("Error!: \(error)")
}
}

Decode multiple layers of a nested JSON with Swift

I have trouble decoding json data using SwiftUI, I have the following json.
{
"data": [
{
"id": "project:xxxxxx",
"project_manager": {
"employee_id": "employee:xxxxxx",
"id": "employee:xxxxxx",
"person_id": "person: xxxxxx",
"name": "Peter Post"
},
"project_status": {
"id": "projectstatus:xxxxxx",
"label": "active"
},
"created": "2019-01-08 15:39:59",
"modified": "2019-01-24 14:39:13",
"created_at": "2019-01-08 15:39:59",
"updated_at": "2019-01-24 14:39:13",
"url": "https://url.com/projects/project/view?id=000",
...
I'm decoding the json with the following code
import Foundation
struct Projects: Decodable {
let data: [Data]
}
struct Data : Decodable, Identifiable {
let id: String
let url: String
let organization: Organization?
let project_status: ProjectStatus?
}
struct Organization : Decodable, Identifiable {
let id: String?
let name: String?
}
struct ProjectStatus: Decodable, Identifiable {
let id: String?
let label: String?
}
import Foundation
import SwiftUI
import Combine
class NetworkingManager: ObservableObject {
#Published var projectList = Projects(data: [])
init() {
var request = URLRequest(url: URL(string: "https://api-url/projects")!,timeoutInterval: Double.infinity)
request.addValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", forHTTPHeaderField: "Authentication-Key")
request.addValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", forHTTPHeaderField: "Authentication-Secret")
URLSession.shared.dataTask(with: request) { (data, _, _) in
guard let data = data else { return }
let projectList = try! JSONDecoder().decode(Projects.self, from: data)
DispatchQueue.main.async {
self.projectList = projectList
print(self.projectList)
}
}.resume()
}
}
import SwiftUI
struct ContentView : View {
#ObservedObject var networkingManager = NetworkingManager()
var body: some View {
VStack {
List(networkingManager.projectList.data, id: \.id) { project in
HStack {
Text(project.id)
Text(project.url)
}
}
}
}
}
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
This results in a list of id and url string values but my question is: How can I list multiple levels of the json files. Do I have to decode each level of the json I want to use or is there a better way?
It was less complicated than I thought. I got a nil value back when I was calling project_status?.label
this was resolved when I called it like this:
project_status?.label ?? self.defaultString

Trying to parse json for public git repos in swift but receiving "Expected to decode Dictionary<String, Any> but found an array instead."

My json looks like this:
[
{
"name": "sensei",
"owner": {
"login": "linkedin",
},
"description": "distributed realtime searchable database",
"fork": false,
},
{
"name": "linkedin-utils",
"owner": {
"login": "linkedin",
},
"description": "Base utilities shared by all linkedin open source projects",
"fork": false,
}
]
The structs I built are the following:
struct LinkedinData: Codable {
var name: String
var description: String
var owner: OwnerLogin
var fork: Bool
}
struct OwnerLogin: Codable {
var login: String
}
My code for parsing is this one:
import UIKit
class ViewController: UIViewController {
var linkedinData = [LinkedinData]()
override func viewDidLoad() {
super.viewDidLoad()
let urString : String = "https://api.github.com/orgs/linkedin/repos"
if let url = URL(string: urString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return //exit out of function
}
parseJSON(json: data!)
}
task.resume()
}
func parseJSON(json: Data) {
let decoder = JSONDecoder()
if let decodedData = try? decoder.decode(LinkedinData.self, from: json) {
linkedinData = [decodedData]
}
}
}
I tried for hours bút it seems impossible to parse the json and retreive the data I am looking for (name, description, owner.login and fork) in a collection type. Could you please help?
You should decode an array of LinkedinData, instead of just one, because your JSON has an array as its root:
[ <------- this "[" indicates an array
{
"name": "sensei",
"owner": {
"login": "linkedin",
},
Therefore, you should write:
if let decodedData = try? decoder.decode([LinkedinData].self, from: json) {
linkedinData = decodedData
}
if let decodedData = try? decoder.decode(LinkedinData.self, from: json) {
linkedinData = [decodedData]
}
replace this with
if let decodedData = try? decoder.decode([LinkedinData].self, from: json) {
linkedinData = decodedData
}
as your topmost object in JSON is an Array.

Error Serialization

Why do I can't access all the properties inside of "data" struct by doing this way? Otherwise, I don't know how to parse all the data inside of "Data" array.
This is how I'm trying to serialize:
import Foundation
import Alamofire
struct Description: Decodable {
let _data: [data]
}
struct data: Decodable {
let id:Int?
let descricao:String?
let urlImagem:String?
}
func callApi() {
guard let _url = URL(string: "https://alodjinha.herokuapp.com/categoria")else{return}
Alamofire.request(_url).responseJSON { (response) in
guard let _data = response.data else{return}
//let dataString = String(data: _data, encoding: .utf8)
do{
let dataParsed = try JSONDecoder().decode([Description].self, from: _data)
print(dataParsed.id)
}catch{
print("Error serialization")}
}
}
I'm getting the error:
Error serialization: typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
The JSON model that I'm following:
{
"data":[
{
"id":1,
"descricao":"Games",
"urlImagem":"http://39ahd9aq5l9101brf3b8dq58.wpengine.netdna-cdn.com/wp-content/uploads/2013/06/3D-Gaming.png"
},
{
"id":2,
"descricao":"Livros",
"urlImagem":"http://4.bp.blogspot.com/-6Bta1H9d22g/UJAIJbqcHhI/AAAAAAAAKi4/hvgjWrlFc64/s1600/resenha-missiologia.png"
},
{
"id":3,
"descricao":"Celulares",
"urlImagem":"http://pt.seaicons.com/wp-content/uploads/2015/11/Mobile-Smartphone-icon.png"
},
{
"id":4,
"descricao":"Informática",
"urlImagem":"http://portal.ifrn.edu.br/campus/ceara-mirim/noticias/ifrn-oferece-curso-de-informatica-basica-para-pais-dos-estudantes/image_preview"
},
{
"id":5,
"descricao":"Eletrodoméstico",
"urlImagem":"http://classificados.folharegiao.com.br/files/classificados_categoria/photo/8/sm_4d5ed3beb0f31b61cb9a01e46ecd0cf9.png"
},
{
"id":6,
"descricao":"TVs",
"urlImagem":"http://i.utdstc.com/icons/256/terrarium-tv-android.png"
},
{
"id":7,
"descricao":"Filmes e Séries",
"urlImagem":"https://pbs.twimg.com/profile_images/801033586438733824/91Y_N91t_reasonably_small.jpg"
},
{
"id":8,
"descricao":"Móveis e Decorações",
"urlImagem":"https://image.flaticon.com/icons/png/128/148/148188.png"
},
{
"id":9,
"descricao":"Moda, Beleza e Perfumaria",
"urlImagem":"http://icon-icons.com/icons2/196/PNG/128/fashion_23852.png"
},
{
"id":10,
"descricao":"Papelaria",
"urlImagem":"http://esen.pt/in/images/stories/skills_256.png"
}
]
}
First, the error indicates that you only have one Description in the JSON but your code is attempting to get an array.
You need to change:
let dataParsed = try JSONDecoder().decode([Description].self, from: _data)
to:
let dataParsed = try JSONDecoder().decode(Description.self, from: _data)
Now you need to iterate the array of data.
This means you need code similar to:
for aData in dataParsed._data {
print(aData.id)
}
FYI - class, struct, and enum names should start with uppercase letters. Functions, variables, and case names should start with lowercase letters.
Also avoid using _ in variable names.