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
Related
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
}
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
Hi my url of request is:
http://MYDOMAIN/jsonrpc?request=
{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": { "filter": {"field": "playcount", "operator": "is", "value": "0"}, "limits": { "start" : 0, "end": 75 }, "properties" : ["art", "rating", "thumbnail", "playcount", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}
How can I parse result in swift?
Use Codable to parse JSON. Try with your demo input.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let someString = """
{"jsonrpc": "2.0", "method": "VideoLibrary.GetMovies", "params": { "filter": {"field": "playcount", "operator": "is", "value": "0"}, "limits": { "start" : 0, "end": 75 }, "properties" : ["art", "rating", "thumbnail", "playcount", "file"], "sort": { "order": "ascending", "method": "label", "ignorearticle": true } }, "id": "libMovies"}
"""
let data = someString.data(using: .utf8)!
do{
let jsonDataModels = try JSONDecoder().decode(JSONDataModel.self, from: data)
print(String(data: data, encoding: .utf8)!)
print("jsonDataModels: \(jsonDataModels)")
}catch {
print(error)
}
}
}
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let jSONDataModel = try? newJSONDecoder().decode(JSONDataModel.self, from: jsonData)
import Foundation
// MARK: - JSONDataModel
struct JSONDataModel: Codable {
let jsonrpc, method: String
let params: Params
let id: String
}
// MARK: - Params
struct Params: Codable {
let filter: Filter
let limits: Limits
let properties: [String]
let sort: Sort
}
// MARK: - Filter
struct Filter: Codable {
let field, filterOperator, value: String
enum CodingKeys: String, CodingKey {
case field
case filterOperator = "operator"
case value
}
}
// MARK: - Limits
struct Limits: Codable {
let start, end: Int
}
// MARK: - Sort
struct Sort: Codable {
let order, method: String
let ignorearticle: Bool
}
Another example tries with API call.
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do{
let jsonDataModels = try JSONDecoder().decode([JSONDataModel].self, from: data)
print(String(data: data, encoding: .utf8)!)
print("jsonDataModels: \(jsonDataModels)")
}catch{}
}
task.resume()
}
}
struct JSONDataModel: Codable {
let id: Int
let name, username, email: String
let address: Address
let phone, website: String
let company: Company
}
struct Address: Codable {
let street, suite, city, zipcode: String
let geo: Geo
}
struct Geo: Codable {
let lat, lng: String
}
struct Company: Codable {
let name, catchPhrase, bs: String
}
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.
JSON =
{
"html_attributions": [],
"results": [
{
"geometry": {},
"name": "Cruise Bar, Restaurant & Events",
"vicinity": "Circular Quay W, Sydney"
},
{}
],
"status": "OK"
}
How do I retrieve name if it is nested within results?
Sam Try this i write sample code in playground using your Json. root.results will give you array of dictionary, you can easily traverse and get your desired name from it.
import UIKit
struct Root: Codable {
let results: [Results]?
private enum CodingKeys: String, CodingKey {
case results = "results"
}
}
struct Results: Codable {
let name: String?
let vicinity: String?
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = NSData(contentsOf: url!)
do {
let root = try JSONDecoder().decode(Root.self, from: data as! Data)
if let name = root.results?.first?.name {
print(name)
}
} catch let error as NSError {
print(error.description)
}
Here is the json i have used.
{
"results": [{
"name": "Cruise Bar, Restaurant & Events",
"vicinity": "Circular Quay W, Sydney"
}]
}
You can do it like that:
Model:
import Foundation
struct HtmlInitial: Codable {
let results: [Result]?
let status: String
enum CodingKeys: String, CodingKey {
case results, status
}
}
struct Result: Codable {
let name, vicinity: String?
}
extension HtmlInitial {
init(data: Data) throws {
self = try JSONDecoder().decode(HtmlInitial.self, from: data)
}
}
use model Like that :
let url = Bundle.main.url(forResource: "APIResponse", withExtension: "json")!
if let data = try? Data.init(contentsOf: url) ,
let initial = try? HtmlInitial.init(data: data),
let result = initial.results?[0] {
print(result.name)
}