Decoding deeply nested object in Swift using nested structs - ios

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
// ...
}
}

Related

Swift Mapping Custom Object Using Codable

I am trying to practice mapping custom objects using swift & replace all manual mapping by using Codable. This concept is new to me but seems very worthwhile. In this project I want to fetch the user and make sure that all their data is stored in their User Document on firebase (including the custom object 'FavouriteItems'.
My User Model struct is:
import SwiftUI
import FirebaseFirestoreSwift
struct UserModel: Identifiable, Codable{
#DocumentID var id: String?
var username : String
var pic : String
var bio: String
var uid : String
var isVerified: Bool
var favouriteItems: [FavouriteItems]
}
My struct for an Item is:
import SwiftUI
import FirebaseFirestoreSwift
import Firebase
struct Item: Identifiable, Codable {
#DocumentID var id: String?
var item_name: String
var item_type: String
var item_image: String
var item_details: String
var item_uid : String
var didFavourite: Bool? = false
var isFavourite: Bool = false
}
My struct for FavouriteItems is:
import SwiftUI
import FirebaseFirestoreSwift
//MARK: Make Hashable (See Tag Example)?
struct FavouriteItems: Identifiable, Codable {
#DocumentID var id: String?
var item: Item
}
My fetch user function is (edited):
func fetchUser(uid: String, completion: #escaping (UserModel) -> ()){
let db = Firestore.firestore()
let docRef = db.collection("Users").document(uid).getDocument(as: UserModel.self) { result in
switch result {
case .success(let user):
// A `UserModel` value was successfully initialized from the DocumentSnapshot.
print("UserModel: \(user)")
DispatchQueue.main.async {
completion(user)
}
case .failure(let error):
// A `UserModel` value could not be initialized from the DocumentSnapshot.
print("Error decoding UserModel: \(error)")
// handle errors todo
}
}
}
I am getting errors in the fetchUser code including:
Cannot convert value of type 'UserModel.Type' to expected argument type 'FirestoreSource'
Contextual closure type '(DocumentSnapshot?, Error?) -> Void' expects 2 arguments, but 1 was used in closure body
Incorrect argument label in call (have 'as:_:', expected 'source:completion:')
All of these errors occur on the line:
let docRef = db.collection("Users").document(uid).getDocument(as: UserModel.self) { result in
Here is my Podfile:

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?
}

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

Failing with the decode of JSON

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.

Resources