Failing with the decode of JSON - ios

I am trying to decode a JSON response from the youtube API in swift.
The JSON information is:
I made a Decodable structure:
// Build a model object to import the JSON data.
struct PlaylistInformation: Decodable {
struct Items: Decodable {
struct VideoNumber: Decodable {
struct Snippet: Decodable {
let title: String
}
let snippet: Snippet
}
let videoNumber: VideoNumber
}
let items: Items
}
And I get the error when trying to decode:
// We decode the JSON data get from the url according to the structure we declared above.
guard let playlistInformation = try? JSONDecoder().decode(PlaylistInformation.self, from: data!) else {
print("Error: could not decode data into struct") <-- HERE IS THE ERROR
return
}
// Comparing DB Versions.
let videoTitle = playlistInformation.items.videoNumber.snippet.title as NSString
print(videoTitle)
The error I get is:
Error: could not decode data into struct
I guess it has something to do with the "items" in the struct, as it is an array... but I have no idea about how to solve that.

Given that items is an array, you have to model it as an array and not a struct:
// Build a model object to import the JSON data.
struct PlaylistInformation: Decodable {
struct Item: Decodable {
struct Snippet: Decodable {
let title: String
}
let snippet: Snippet
}
let items: [Item]
}
And then access each item using its index, e.g.
let videoTitle = playlistInformation.items[0].snippet.title as NSString
print(videoTitle)

Yes, the error was coming from the "items" in the struct as it is an array.
The correct Decodable struct is:
struct PlaylistInformation: Decodable {
struct Items: Decodable {
struct Snippet: Decodable {
struct Thumbnails: Decodable {
struct High: Decodable {
let url: String
}
let high: High
}
struct ResourceId: Decodable {
let videoId: String
}
let publishedAt: String
let title: String
let thumbnails: Thumbnails
let resourceId: ResourceId
}
let snippet: Snippet
}
let items: [Items]
}
Thank you for your help.

Related

Swift Alamofire list view state

I'm trying to create a list view with some data I am pulling from an API. I'm struggling to understand how to take the data from the API response and putting it into the state for my app to use. Below is the content view in my application that is pulling the data.
import SwiftUI
import Alamofire
struct ContentView: View {
#State var results = [Bottle]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
}
}.onAppear(perform: loadData)
}
func loadData() {
let request = AF.request("https://bevy-staging.herokuapp.com")
request.responseJSON { (data) in
print(data)
}
}
}
I've tried adding this to the result block
AF.request("https://bevy-staging.herokuapp.com/").responseJSON { response in
guard let data = response.data else { return }
if let response = try? JSONDecoder().decode([Bottle].self, from: data) {
DispatchQueue.main.async {
self.results = response
}
return
}
}
However nothing populates in my view and I get the following error.
nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Why am I receiving this error and how can I get my data to display in the list vew?
Here is the model I am working with.
struct Bottle: Decodable {
var id: String
var name: String
var price: String
var sku: String
var size: String
var origination: String
var varietal: String
var brand_bottle: String
}
You need to add "?" to model data that can have null data, for all model rows which can obtain "null" need to use "?" or JSONDecoder wouldn't decode data to your model. Inside the model your rows "origination, varietal, brand_bottle" have "String" data type but from the server, you obtain "null", thus JSONDecoder can't recognize data.
You can check responses use services like "http://jsonviewer.stack.hu/" or any other.
Need to modify model data like below:
struct Bottle: Decodable {
var id: String
var name: String
var price: String
var sku: String
var size: String
var origination: String?
var varietal: String?
var brand_bottle: String?
}
I did recreate your project and all work well code below:
import SwiftUI
import Alamofire
struct ContentView: View {
#State var results = [Bottle]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
}
}.onAppear(perform: loadData)
}
func loadData() {
AF.request("https://bevy-staging.herokuapp.com/").responseJSON { response in
guard let data = response.data else { return }
if let response = try? JSONDecoder().decode([Bottle].self, from: data) {
DispatchQueue.main.async {
self.results = response
}
return
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Bottle: Decodable {
var id: String
var name: String
var price: String
var sku: String
var size: String
var origination: String?
var varietal: String?
var brand_bottle: String?
}

Decoding deeply nested object in Swift using nested structs

my goal is to decode a deeply nested json response from an api and render it in a list, below is the raw response data and my attempt.
The problem is that when I attempt to initialize my data to then further populated when I make my API call it gives me an error
“ Listing(data: []) { ERROR: No exact matches in call to initializer”
Is this the correct way to declare my structs to decode a deeply nested JSON Object?
Api response data ->
https://pastebin.com/7pgswZqk
import Foundation
import SwiftUI
import Combine
struct Listing: Decodable {
var kind: String
struct data: Decodable {
var modhash: String
var dist: Int
struct children: Decodable {
var kind: String
struct data: Decodable { <--- this level is where my iterable children are
var title: String
... <---- there is more properties here but I just put title for now
}
}
}
}
class NetworkingManager : ObservableObject {
var didChange = PassthroughSubject<NetworkingManager, Never>()
var ListingList = Listing(data: []) { *ERROR: No exact matches in call to initializer*
didSet {
didChange.send(self)
}
}
init() {
guard let url = URL(string: "https://www.reddit.com/best.json?limit=25") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let List = try! JSONDecoder().decode(Listing.self, from: data)
DispatchQueue.main.async {
self.ListingList = List
}
}.resume()
}
}
Then to iterate over the list, would it be like this?
List(ListingList.data.children.data.identified(by ./title) {listItem in
Text(listItem.title)
}
You defined your custom Decodable nested structs but missing their application.
Your Listing should look something like this...
struct Listing: Decodable {
var kind: String
var data: Data // instance of custom Listing.Data struct
struct Data: Decodable { // your custom decodable struct
var modhash: String
var dist: Int
var children: [Children] // instance of your custom Listing.Data.Children struct
struct Children: Decodable { // your custom decodable struct
var kind: String
var data: Data
struct Data: Decodable {
var title: String
// ...
}
}
}
}
...but it becomes unreadable very quickly so better create separate structs instead of nesting. In the following example you can see that naming can become kindof tedious when having deeply nested responses.
struct Listing: Decodable {
var kind: String
var data: ListingData
}
struct ListingData: Decodable {
var modhash: String
var dist: Int
var children: [ListingDataChildren]
}
struct ListingDataChildren: Decodable {
var kind: String
var data: ListingDataChildrenData
}
struct ListingDataChildrenData: Decodable {
var title: String
// ...
}
The optimal solution would be to create extensions to still have that kind of hierarchy that you get via nesting.
struct Listing: Decodable {
var kind: String
var data: Data
}
extension Listing {
struct Data: Decodable {
var modhash: String
var dist: Int
var children: [Children]
}
}
extension Listing.Data {
struct Children: Decodable {
var kind: String
var data: Data
}
}
extension Listing.Data.Children {
struct Data: Decodable {
var title: String
// ...
}
}

How do I decode this JSON Data in Swift?

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.

How do i call the parsed data from the GET request that have a longer nested JSON structure in Swift?

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 ?? []

Can we reuse struct on Swift? Or is there any other way?

So I have a user JSON structure that goes like this:
- results: {
meta: {}
users: []
},
- status:
I want to get the user so the User model I implement to get the JSON is like this:
struct Response: Decodable {
let results: Result
let status: Int
}
struct Result: Decodable {
let meta: Meta
let users: [User]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
It is working, but when I have another JSON that the structure is similar, let say like this
- results: {
meta: {}
rooms: []
},
- status:
And when I create Room model, with another struct Response on it, it will cause error because it is duplicated declaration.
Is it possible to reuse struct in Swift? Or is there any convenient way to do this?
Thanks
You could use generics.
struct Response<T: Decodable>: Decodable {
let results: Result<T>
let status: Int
}
struct Result<T: Decodable>: Decodable {
let meta: Meta
let objects: [T]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
let userResponse: Response<User>?
You can try to use generics in the following way:
struct Response<ResultType> : Decodable where ResultType : Decodable {
let results: ResultType
let status: Int
}
and then use that struct via:
struct Result: Decodable {
let something: String
}
let decoder = JSONDecoder()
let data = "{\"status\":123,\"results\":{\"something\":\"hi\"}}".data(using: .utf8)!
let value = try! decoder.decode(Response<Result>.self, from: data) // will be of type Response<Result>
You have one option is to reuse Meta and Status
If you use sm
For user You can replace name
struct UserResult: Decodable {
let meta: Meta
let users: [User]
}
struct UserResponse: Decodable {
let results: UserResult
let status: Int
}
and for Room
struct RoomResult: Decodable {
let meta: Meta
let users: [Room]
}
struct RoomResponse: Decodable {
let results: RoomResult
let status: Int
}
Depending on your needs, you can use generics to compose the whole struct.
Start with your data models
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
struct Room: Decodable {
let id: Int
let name: String
}
The goal is to get nice composed types.
typealias UserResponse = Response<Result<User, Meta>>
typealias RoomResponse = Response<Result<Room, Meta>>
The generics types to build from
struct Response<ResultType: Decodable>: Decodable {
let results: ResultType
let status: Int
}
struct Result<ItemType: Decodable, MetaType: Decodable>: Decodable {
let meta: MetaType
let items: [ItemType]
}
Even Meta is a separate part of the composition.
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
Now lets say we need a custom Meta for the Room response
struct PagedMeta: Decodable {
let current_page: Int
let page_count: Int
}
Here is the new type
typealias RoomPagedResponse = Response<Result<Room, PagedMeta>>

Resources