Search code examples
swiftuimodelmappingviewmodelnested-json

Swiftui Mapping Nested JSON to Flat Model


I need to access the data in the nested dictionary of the Memodel struct. From both the music and image dictionary. Please any help is needed to map out correctly, i have tried using AzampSharp's example https://www.youtube.com/watch?v=b5wVIQNrI6k but i believe i am doing something wrong. Thanks.

import SwiftUI

    struct MemodelAPIResult: Codable {
        let data: [Memodel]
        
        enum CodingKeys: String, CodingKey {
            case data = "results"
        }
    }
    
    
    struct Memodel: Identifiable, Codable {
        var id: String
        var followers: String
        var following: String
        let music: [MemodelMusic]
        let images: [MemodelImages]
        
    }
    
    struct MemodelMusic: Identifiable, Codable, Hashable {
        var id: String
        var musicfile: URL
        var musicname: String
        var musicartistname: String
        var musicgenre: String
    }
    
    struct MemodelImages: Identifiable, Codable, Hashable {
        var id: String
        var albumimages: URL
        var abumlikes: String
        var albumviews: String
    }

Below is my ObservableObject in my View Model

import Foundation
import SwiftUI
import Combine
import CryptoKit

class MeViewmodel: ObservableObject {
    @Published var me: [Memodel]? = nil
    
    
    init() {
            self.fetchme()
    }
    
    func fetchme() {
        
        let url = ""
        
        let session = URLSession(configuration: .default)

        session.dataTask(with: URL(string: url)!) { (data, _, err) in

            if let error = err{
                print(error.localizedDescription)
                return
            }

            guard let APIData = data else {
                print("No Data found")
                return
            }

            do {
                let new = try JSONDecoder().decode(MemodelAPIResult.self, from: APIData)

                DispatchQueue.main.async {
                       self.me = new.data
                }
            }
            catch{
                print(error)
            }
    }
        .resume()
    }
    
}

And then the item view

struct MeMusicItemView: View {
    //E-MARK: - Properties
    var me: Memodel
    //E-MARK: - Body
    var body: some View {
            HStack {
                    VStack(alignment: .leading, spacing: 5) {
                        Text(me.music[0].musicname)
                            .font(.callout)
                            .fontWeight(.medium)
                            .foregroundColor(.white)
                        Text(me.music[0].musicartistname)
                            .font(.caption2)
                            .fontWeight(.light)
                            .foregroundColor(.white)
                        
                        Text(me.music[0].musicgenre)
                            .font(.system(size: 8))
                            .fontWeight(.light)
                            .foregroundColor(.gray)
                    }
                }
    }
}

And also the ForEach in the parent View....

if let meMusicData = meMusicData.mememe {
                    ForEach(meMusicData) { music in
                        MeMusicItemView(memusic: music)
                    }
                    } else {
                        ProgressView()
                            .padding(.top, 20)
                    }

Solution

  • There is not enough info for me to really understand what you are doing, but here is some code you can have a look at and recycle for your purpose:

    struct ContentView: View {
        @StateObject var viewModel = MeViewmodel() // <-- here your model
        
        var body: some View {
            List {
                ForEach(viewModel.me) { memod in  // <-- loop over the Memodel array
                    ForEach(memod.music) { music in  // <-- loop over the MemodelMusic array
                        MeMusicItemView(memusic: music) // <-- display 1 MemodelMusic
                    }
                }
            }
        }
    }
    
    struct MeMusicItemView: View {
        //E-MARK: - Properties
        @State var memusic: MemodelMusic // <-- here
        //E-MARK: - Body
        var body: some View {
            HStack {
                VStack(alignment: .leading, spacing: 5) {
                    Text(memusic.musicname)
                        .font(.callout)
                        .fontWeight(.medium)
                        .foregroundColor(.pink)
                    Text(memusic.musicartistname)
                        .font(.caption2)
                        .fontWeight(.light)
                        .foregroundColor(.green)
                    
                    Text(memusic.musicgenre)
                        .font(.system(size: 8))
                        .fontWeight(.light)
                        .foregroundColor(.blue)
                }
            }
        }
    }
    
    class MeViewmodel: ObservableObject {
        @Published var me: [Memodel] = [] // <--- here no optional, it is easier to deal with
    
        init() {
            self.fetchme()
        }
        func fetchme() {
            //  ......
        }
    }
    
    struct Memodel: Identifiable, Codable {
        var id: String
        var followers: String
        var following: String
        let music: [MemodelMusic]
        let images: [MemodelImages]
    }
    
    struct MemodelMusic: Identifiable, Codable, Hashable {
        var id: String
        var musicfile: URL
        var musicname: String
        var musicartistname: String
        var musicgenre: String
    }
    
    struct MemodelImages: Identifiable, Codable, Hashable {
        var id: String
        var albumimages: URL
        var abumlikes: String
        var albumviews: String
    }