How do I decode this JSON Data?
I've done it with the "drilling down" method where I kept calling each key and printing the value. I also tried the data model but it never worked. I probably did something wrong but I don't know what.
Thanks in advance, If you need more information, just ask, I'm fairly new to Swift and Stackoverflow.
{
"items":[
{
"id":16000014,
"name":"BO",
"starPowers":[
{
"id":23000090,
"name":"CIRCLING EAGLE"
},
{
"id":23000148,
"name":"SNARE A BEAR"
}
],
"gadgets":[
{
"id":23000263,
"name":"SUPER TOTEM"
},
{
"id":23000289,
"name":"TRIPWIRE"
}
]
},
{
"id":16000015,
"name":"PIPER",
"starPowers":[
{
"id":23000091,
"name":"AMBUSH"
},
{
"id":23000152,
"name":"SNAPPY SNIPING"
}
],
"gadgets":[
{
"id":23000268,
"name":"AUTO AIMER"
},
{
"id":23000291,
"name":"HOMEMADE RECIPE"
}
]
}
],
"paging":{
"cursors":{
}
}
}
Decoding JSON in swift is insanely easy. Just use the JSONDecoder class. Firstly, create a Codable class for your json response like this
struct Items: Codable {
let items: [Item]
let paging: Paging
}
struct Item: Codable {
let id: Int
let name: String
let starPowers, gadgets: [Gadget]
}
struct Gadget: Codable {
let id: Int
let name: String
}
struct Paging: Codable {
let cursors: Cursors
}
struct Cursors: Codable {
}
And then use it to parse your JSON like this
let decoder = JSONDecoder()
do {
let items = try decoder.decode(Items.self, from: jsonData)
print(items)
// Do something with the items here
} catch {
print(error.localizedDescription)
}
struct Name {
var id:Int
var name:String
}
struct Cursor {
// Your cursor model
}
struct Paging {
var cursors: Cursor
}
struct Items {
var id:Int
var name:String
var starPowers:[Name]
var gadgets:[Name]
}
struct MainModel {
var items : [Items]
var paging : Paging
}
You can decode that data using let yourData = try! JSONDecoder().decode(MainModel.self, from: jsonData) to get your desired JSON data.
Related
I'm learning iOS development and I'm trying to modify my app to use MVVM model. Below I'm pasting json structure that I'm using. I'm able to access categories, but I encountered an issue when I tried to iterate through Items. How my view model should look like? Do I need 2 view models one for Category and another one for Item? Also how to combine View Model with AppStorage?
[
{
"id": "8DC6D7CB-C8E6-4654-BAFE-E89ED7B0AF94",
"name": "Category",
"items": [
{
"id": "59B88932-EBDD-4CFE-AE8B-D47358856B93",
"name": "Item1",
"isOn": false
},
{
"id": "E124AA01-B66F-42D0-B09C-B248624AD228",
"name": "Item2",
"isOn": false
}
}
]
View
struct ContentView: View {
#ObservedObject var viewModel = MyModel()
var body: some View {
List {
ForEach(viewModel.items, id: \.self) { id in
Text(id.name)
//how to iterate through items?
}
}
}
}
ViewModel
class MyModel: ObservableObject {
#Published var items: [ItemsSection] = [ItemsSection]()
init(){
loadData()
}
func loadData() {
guard let url = Bundle.main.url(forResource: "items", withExtension: "json")
else {
print("Json file not found")
return
}
let data = try? Data(contentsOf: url)
let items = try? JSONDecoder().decode([ItemsSection].self, from: data!)
self.items = items!
}
func getSelectedItemsCount() -> Int{
var i: Int = 0
for itemSection in items {
let filteredItems = itemSection.items.filter { item in
return item.isOn
}
i = i + filteredItems.count
}
return i
}
}
Model:
struct ItemSection: Codable, Identifiable, Hashable {
var id: UUID = UUID()
var name: String
var items: [Item]
}
struct Item: Codable, Equatable, Identifiable,Hashable {
var id: UUID = UUID()
var name: String
var isOn: Bool = false
}
To iterate over the your items array you can do something like this:
struct ContentView: View {
// This should be #StateObject as this View owns the viewmodel
#StateObject var viewModel = MyModel()
var body: some View {
List {
//ItemSection is Identifiable so no need for `id: \.self` here.
ForEach(viewModel.sections) { section in
//Section is a View provided by Apple that can help you laying
// out your View. You don´t have to, you can use your own
Section(section.name){
ForEach(section.items){ item in
Text(item.name)
}
}
}
}
}
}
I´ve changed the naming in thew Viewmodel as I think the naming of items var should really be sections.
class MyModel: ObservableObject {
#Published var sections: [ItemsSection] = [ItemsSection]()
init(){
loadData()
}
func loadData() {
guard let url = Bundle.main.url(forResource: "items", withExtension: "json")
else {
print("Json file not found")
return
}
do {
let data = try Data(contentsOf: url)
let sections = try JSONDecoder().decode([ItemsSection].self, from: data)
self.sections = sections
} catch {
print("failed loading or decoding with error: ", error)
}
}
func getSelectedItemsCount() -> Int{
var i: Int = 0
for itemSection in sections {
let filteredItems = itemSection.items.filter { item in
return item.isOn
}
i = i + filteredItems.count
}
return i
}
}
And never use try? use a proper do / catch block and print the error. This will help you in future to identify problems better. For example the example JSON you provided is malformatted. Without proper do / catch it will just crash while force unwrap.
As a follow-up to this question, I now want to iterate through an array of Codable structs in SwiftUI and render them in my ContentView{} as Text or List items.
I have tried implementing a variable, geoDataArray, in the .task section then iterating over it with a ForEach in my ContentView but received a lot of errors about types and unwrapping values.
Any help is appreciated! I am still new to SwiftUI.
Below is my code:
struct GeoService: Codable {
var status: String
var results: [GeoResult]
}
struct GeoResult: Codable {
struct Geometry: Codable {
struct Location: Codable {
let lat: Float
let lng: Float
init() {
lat = 32
lng = 30
}
}
let location: Location
}
let formatted_address: String
let geometry: Geometry
}
struct ContentView: View {
// #State private var results: Any ?????????
var body: some View {
NavigationView {
Text("Test")
.navigationTitle("Quotes")
.task {
await handleData()
}
}
}
func handleData() async {
let geoResult="""
{
"results": [
{
"formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA",
"geometry": {
"location": {
"lat": 37.4224764,
"lng": -122.0842499
}
}
},
{
"formatted_address": "Test addresss",
"geometry": {
"location": {
"lat": 120.32132145,
"lng": -43.90235469
}
}
}
],
"status": "OK"
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
print("executing handleData()")
do {
let obj = try decoder.decode(GeoService.self, from: geoResult)
for result in obj.results {
print("Address: \(result.formatted_address)")
print("Lat/long: (\(result.geometry.location.lat), \(result.geometry.location.lng))")
}
} catch {
print("Did not work :(")
}
}
}
Your code works fine the way it is for printing to the console, but ForEach requires that GeoResult conforms to either Identifiable (preferred) or at least Hashable. Given that you didn't include the property id in your code, let's have that struct conforming to Hashable.
So, assuming that each GeoResult is different because formatted_address is never the same (you must check if that's true), you can add two functions to ensure conformance. You will get the following:
struct GeoResult: Codable, Hashable { // <- Conform to Hashable
// Differentiating
static func == (lhs: GeoResult, rhs: GeoResult) -> Bool {
lhs.formatted_address == rhs.formatted_address
}
// Hashing
func hash(into hasher: inout Hasher) {
hasher.combine(formatted_address)
}
struct Geometry: Codable {
struct Location: Codable {
let lat: Float
let lng: Float
init() {
lat = 32
lng = 30
}
}
let location: Location
}
let formatted_address: String
let geometry: Geometry
}
In the view, add an array of GeoResult, that will be the #State variable to iterate over. Place the .task() modifier on the outermost view.
// This is the list
#State private var geoArray: [GeoResult] = []
var body: some View {
NavigationView {
VStack {
// GeoResult is not Identifiable, so it is necessary to include id: \.self
ForEach(geoArray, id: \.self) { result in
NavigationLink {
Text("Lat/long: (\(result.geometry.location.lat), \(result.geometry.location.lng))")
} label: {
Text("Address: \(result.formatted_address)")
}
}
.navigationTitle("Quotes")
}
}
// Attach the task to the outermost view, in this case the NavigationView
.task {
await handleData()
}
}
Finally, change the #State variable in your function, after decoding:
func handleData() async {
// ...
let decoder = JSONDecoder()
do {
let obj = try decoder.decode(GeoService.self, from: geoResult)
// Add this
geoArray = obj.results
} catch {
print("Did not work :(\n\(error)")
}
}
NOTE: Forgive my cluelessness, i am still new in regards to this. The full code is posted at the bottom.
ISSUE: It seems that when i have a short nest, i am able to call it for my #Published property however when i try an api request with a longer nest, like this. and type Decodable structs that follows the structure of the GET request
struct TripScheduleTest: Codable {
let TripList: InitialNest
}
struct InitialNest: Codable {
var Trip: [TravelDetail]
}
struct TravelDetail: Codable {
var Leg: [TripTest]
}
struct TripTest: Codable, Hashable {
var name: String
var type: String
}
I am not able to call it for the #Published var dataSet1 = [TripTest]()
self.dataSet1 = tripJSON.TripList.Trip.Leg
I get an error message, that says "Value of type '[TravelDetail]' has no member 'Leg'
I am not sure why, however it works when i use [TravelDetail]() instead of [TripTest]() in the #Published var and stop at Trip before Leg for the dataSet1, then it seems to at least build successfully. But now i am not able to get the name and type information from the request
Full code
import SwiftUI
struct TripScheduleTest: Codable {
let TripList: InitialNest
}
struct InitialNest: Codable {
var Trip: [TravelDetail]
}
struct TravelDetail: Codable {
var Leg: [TripTest]
}
struct TripTest: Codable, Hashable {
var name: String
var type: String
}
class TripViewModel: ObservableObject {
#Published var dataSet1 = [TripTest]()
init() {
let urlString = "http://xmlopen.rejseplanen.dk/bin/rest.exe/trip?originId=8600790&destId=6553&format=json"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, resp, err) in
guard let data = data else { return }
do {
let tripJSON = try
JSONDecoder().decode(TripScheduleTest.self, from: data)
print(data)
DispatchQueue.main.async {
self.dataSet1 = tripJSON.TripList.Trip.Leg
}
} catch {
print("JSON Decode error: ", error)
}
}.resume()
}
}
struct TripView: View {
#ObservedObject var vm = TripViewModel()
var body: some View {
List(vm.dataSet1, id: \.self) { day in
Text("Test")
.font(.system(size: 12, weight: .bold))
Text(" \(day.name)")
.font(.system(size: 12))
}
}
}
Trip is an array (note the [])
You need to get one item of the array by index for example
tripJSON.TripList.Trip.first?.Leg
To assign the value to a non-optional array write
self.dataSet1 = tripJSON.TripList.Trip.first?.Leg ?? []
first of all I made this NetworkManager class to made networking with json api. like this below.
import Foundation
import SwiftUI
import Combine
class NetworkManager: ObservableObject {
#Published var posts = [Post]()
func fetchData() {
var urlComponents = URLComponents()
urlComponents.scheme = "http"
urlComponents.host = "183.111.148.229"
urlComponents.path = "/mob_json/mob_json.aspx"
urlComponents.queryItems = [
URLQueryItem(name: "nm_sp", value: "UP_MOB_CHECK_LOGIN"),
URLQueryItem(name: "param", value: "1000|1000|1")
]
if let url = urlComponents.url {
print(url)
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(Results.self, from: safeData)
DispatchQueue.main.async {
self.posts = results.Table
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
And this is my data Model which is structure for my json data. I also made this by refer code on the internet.
import Foundation
struct Results: Decodable {
let Table: [Post]
}
struct Post: Decodable, Identifiable {
var id: String {
return CD_FIRM
}
let CD_FIRM: String
let NM_FIRM: String
let CD_USER: String
let NM_USER: String
}
On the view, This is work fine I can see my result. But this is not what I want.
I want to see each single text value.
import SwiftUI
struct SwiftUIView: View {
#ObservedObject var networkManager = NetworkManager()
var body: some View {
NavigationView {
List(networkManager.posts) { post in
Text(post.NM_FIRM)
}
}.onAppear {
self.networkManager.fetchData()
}
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
** I want to extract single text value from results like this**
with JUST single text.
I can extract all datas from json, but I don't know how to extract each values from result.
I tried like this below
import SwiftUI
struct SwiftUIView: View {
#ObservedObject var networkManager = NetworkManager()
var body: some View {
VStack {
Text(networkManager.posts.NM_FIRM)
}.onAppear {
self.networkManager.fetchData()
}
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
But this one didn't work. where do I have to fix this? Please help me.
Add more thing
import Foundation
struct Results: Decodable {
let Table: [Post]
}
struct Post: Decodable, Identifiable {
var id: String {
return name
}
let name: String
let cellPhone: String
}
// I want to get value like this but this didn't work
var data1 = name
var data2 = cellPhone
I'm trying to parse Youtube api in using Alamofire in Swift 4
so far i did get the results and all fine but i'm having trouble accessing the "items"
so basically i want to access title, description, medium thumbnails url and resultsPerPage.
i tried a lot of solutions but non if them worked for me specially i'm using Swift4 and Alamofire
JSON:
{
"kind":"youtube#searchListResponse",
"etag":"\"XI7nbFXulYBIpL0ayR_gDh3eu1k/N6oV8CScLhAtqc_fDnA3Nw4U3RA\"",
"nextPageToken":"CBkQAA",
"regionCode":"US",
"pageInfo":{
"totalResults":922098,
"resultsPerPage":25
},
"items":[
{
"kind":"youtube#searchResult",
"etag":"\"XI7nbFXulYBIpL0ayR_gDh3eu1k/Oxu5v7t2PHcDK4wvSo-xsIp3Raw\"",
"id":{ },
"snippet":{
"publishedAt":"2011-03-21T08:32:25.000Z",
"channelId":"UC1r4VtVE__5K6c_L_3Vlxxg",
"title":"fouseyTUBE",
"description":"",
"thumbnails":{
"default":{
"url":"https://yt3.ggpht.com/-oBs78-0JLws/AAAAAAAAAAI/AAAAAAAAAAA/zKWHSghRD3U/s88-c-k-no-mo-rj-c0xffffff/photo.jpg"
},
"medium":{
"url":"https://yt3.ggpht.com/-oBs78-0JLws/AAAAAAAAAAI/AAAAAAAAAAA/zKWHSghRD3U/s240-c-k-no-mo-rj-c0xffffff/photo.jpg"
},
"high":{
"url":"https://yt3.ggpht.com/-oBs78-0JLws/AAAAAAAAAAI/AAAAAAAAAAA/zKWHSghRD3U/s800-c-k-no-mo-rj-c0xffffff/photo.jpg"
}
},
"channelTitle":"fouseyTUBE",
"liveBroadcastContent":"none"
}
}
]
}
My Code :
let url = "https://www.googleapis.com/youtube/v3/search"
let parameters = ["q": searchText, "maxResults": 25, "part": "snippet", "type":"video", "key": "MY_YOUTUBE_API_KEY"] as [String : Any]
Alamofire.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: nil).responseData { (dataResponse) in
if let err = dataResponse.error {
print("Failed to contact server", err)
return
}
guard let data = dataResponse.data else {return}
do{
let searchResult = try
JSONDecoder().decode(SearchResults.self, from: data)
print("Results Count:", searchResult.kind)
searchResult.items.forEach({ (data) in
print(searchResult.items["data"]["items"])
})
self.musics = searchResult.items
self.tableView.reloadData()
}catch let decodeErr {
print("Failed to Descode: ", decodeErr)
}
}
}
struct SearchResults: Decodable{
let kind: String
let items: [Music]
}
Music.Swift file
struct Music: Decodable {
var etag: String?
var kind: String?
//let time: String
}
I would suggest creating decodable struct for each nested elements like so,
struct PageInfo: Decodable {
var totalResults = 0
var resultsPerPage = 0
}
struct Snippet: Decodable {
var channelId = ""
var title = ""
var description = ""
var channelTitle = ""
var thumbnails: Thumbnail
}
struct ChannelURL: Decodable {
var url = ""
}
struct Thumbnail: Decodable {
var medium: ChannelURL
var high: ChannelURL
}
struct Item: Decodable {
var kind = ""
var etag = ""
var snippet: Snippet
}
struct Result: Decodable {
var kind = ""
var etag = ""
var pageInfo: PageInfo
var items: [Item]
}
And then proceed with the decoding. The following decoding works with your response above.
do {
let decoded = try JSONDecoder().decode(Result.self, from: data)
debugPrint(decoded)
//Now access the data
print(decoded.pageInfo.resultsPerPage) // 25
//since the items is array we take first for now
if let firstItem = decoded.items.first {
//to print the first one
print(firstItem.snippet.channelTitle) // "fouseyTUBE"
//same for URL
print(firstItem.snippet.thumbnails.medium.url) // https://yt3.ggpht.com/-oBs78-0JLws/AAAAAAAAAAI/AAAAAAAAAAA/zKWHSghRD3U/s240-c-k-no-mo-rj-c0xffffff/photo.jpg
}
} catch {
debugPrint("\(error.localizedDescription)")
}
This is the best case scenario were all data is present. So you will have to modify your structure accordingly making some values ``