I am new to SwiftUI and have encountered problems parsing data from JSON files in SwiftUI. At now, I can pass all the data in the JSON file but what I want is to parse a list of data based on their id number.
Here is my json file:
{
"client": {
"id": 200,
"first_name": "Luke",
"last_name": "",
"email": "luke#abc.com"
},
"business_segments": [
{
"id": 1,
"name": "Segment A",
"status": "Open",
"companies": [
5,
6,
7
]
},
{
"id": 2,
"name": "Segment B",
"status": "Open",
"companies": [
1,
2,
4
]
},
{
"id": 3,
"name": "Segment C",
"status": "Open",
"companies": [
3,
8,
12
]
},
{
"id": 4,
"name": "Segment D",
"status": "Open",
"companies": [
9,
10,
15
]
},
{
"id": 5,
"name": "Segment E",
"status": "Open",
"companies": [
11,
13,
14,
16
]
},
{
"id": 6,
"name": "Segment F",
"status": "Open",
"companies": [
17,
18
]
}
],
"companies": [
{
"id": 1,
"name": "COMPANY A",
"status": "Open"
},
{
"id": 2,
"name": "COMPANY B",
"status": "Open"
},
{
"id": 3,
"name": "COMPANY C",
"status": "Open"
},
{
"id": 4,
"name": "COMPANY D",
"status": "Open"
},
{
"id": 5,
"name": "COMPANY E",
"status": "Open"
},
{
"id": 6,
"name": "COMPANY F",
"status": "Open"
},
{
"id": 7,
"name": "COMPANY G",
"status": "Open"
},
{
"id": 8,
"name": "COMPANY H",
"status": "Open"
},
{
"id": 9,
"name": "COMPANY I",
"status": "Open"
},
{
"id": 10,
"name": "COMPANY J",
"status": "Open"
},
{
"id": 11,
"name": "COMPANY K",
"status": "Open"
},
{
"id": 12,
"name": "COMPANY L",
"status": "Open"
},
{
"id": 13,
"name": "COMPANY M",
"status": "Open"
},
{
"id": 14,
"name": "COMPANY N",
"status": "Open"
},
{
"id": 15,
"name": "COMPANY O",
"status": "Open"
},
{
"id": 16,
"name": "COMPANY P",
"status": "Open"
},
{
"id": 17,
"name": "COMPANY Q",
"status": "Open"
},
{
"id": 18,
"name": "COMPANY R",
"status": "Open"
}
]
}
As you can see from the JSON file, in my business segments section, I listed down a list of numbers. These numbers are the id number associated with the specific company in the company section. What I wish to do is have six buttons in the contentView. Each button represents one business segment. As I click on each button, it will jump to CompanyName View, where all the companies related to each business segment are shown. However, what I could do and what I have tired is to list all the companies in the CompanyName View.
Hence, I need some help. I will show you different files in my project.
CompanyViewModel.swift, where handle all the parsing from json:
import Foundation
// MARK: - CompanyData
struct CompanyData: Codable,Hashable {
let client: Client
let businessSegments, companies: [BusinessSegment]
enum CodingKeys: String, CodingKey {
case client
case businessSegments = "business_segments"
case companies
}
}
// MARK: - BusinessSegment
struct BusinessSegment: Codable,Hashable {
let id: Int
let name: String
let status: Status
let companies: [Int]?
}
enum Status: String, Codable {
case statusOpen = "Open"
}
// MARK: - Client
struct Client: Codable,Hashable {
let id: Int
let firstName, lastName, email: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
case email
}
}
class CompanyViewModel:ObservableObject{
#Published var company_data: [CompanyData] = []
func getUserData(){
guard let url = Bundle.main.url(forResource:"Company", withExtension:"json") else {
print("Invalid URL")
return
}
let task = URLSession.shared.dataTask(with:url){ data, response, error in
let decoder = JSONDecoder()
if let data = data {
do {
let company_data = try decoder.decode(CompanyData.self, from:data)
DispatchQueue.main.async{
self.company_data = [company_data]
}
print(company_data.businessSegments)
print()
print(company_data.companies)
print()
} catch {
print("cannot decode")
print(String(data:data, encoding:.utf8) as Any)
print(error)
}
}
}
task.resume()
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#StateObject private var companyVM = CompanyViewModel()
var body: some View {
NavigationView{
VStack {
ForEach(companyVM.company_data,id:\.self){ item in
ForEach(item.businessSegments,id:\.self){ segment in
/*Text(segment.name)
.multilineTextAlignment(.center)
.bold()
.font(.custom("Montserrat-Bold", size: 24)) */
NavigationLink(destination:
CompanyName(segment: segment.name)){
Text(segment.name)
.frame(width:240,height:50)
.bold()
.foregroundColor(.white)
.background(Color.orange)
.clipShape(Capsule())
}
}
}
}
.padding()
.onAppear(){
companyVM.getUserData()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
CompanyName.swift
import SwiftUI
struct CompanyName: View {
let segment: String
#StateObject private var companyVM = CompanyViewModel()
var body: some View {
NavigationView{
VStack{
Text(segment)
.multilineTextAlignment(.center)
.bold()
.font(.custom("Montserrat-Bold", size: 24))
ForEach(companyVM.company_data,id:\.self){ item in
ForEach(item.companies,id:\.self){ company in
Text(company.name)
.multilineTextAlignment(.center)
.bold()
.font(.custom("Montserrat-Bold", size: 24))
}
}
}.padding()
.onAppear(){
companyVM.getUserData()
}
}
}
}
struct CompanyName_Previews: PreviewProvider {
static var previews: some View {
CompanyName(segment: "Business Segments")
}
}
Here is the code that displays the companies related to the selected segment.
As you tap a segment, the companies for that particular segment are retrieved (based on id)
from; a particular CompanyData or from all [CompanyData]. Adjust the code to
select one or the other, both functions are presented in the CompanyViewModel.
Note, I left the #Published var company_data: [CompanyData],
although you said you only have one CompanyData in it.
Have a look at this link, it gives you examples of how to use ObservableObject and manage data in your app https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
class CompanyViewModel: ObservableObject {
#Published var company_data: [CompanyData] = []
func getUserData(){
guard let url = Bundle.main.url(forResource:"Company", withExtension:"json") else {
print("Invalid URL")
return
}
let task = URLSession.shared.dataTask(with:url){ data, response, error in
let decoder = JSONDecoder()
if let data = data {
do {
let company_data = try decoder.decode(CompanyData.self, from:data)
DispatchQueue.main.async{
self.company_data = [company_data]
}
print(company_data.businessSegments)
print()
print(company_data.companies)
print()
} catch {
print("cannot decode")
print(String(data:data, encoding:.utf8) as Any)
print(error)
}
}
}
task.resume()
}
// get all companies for a particular segment and from a particular CompanyData
func getCompaniesFor(seg: BusinessSegment, item: CompanyData) -> [BusinessSegment] {
var companies = [BusinessSegment]()
if let segCompanies = seg.companies {
for id in segCompanies {
for company in item.companies {
if company.id == id {
companies.append(company)
}
}
}
}
return companies
}
// get all companies for a particular segment from all [CompanyData]
func getAllCompaniesFor(seg: BusinessSegment) -> [BusinessSegment] {
var companies = [BusinessSegment]()
if let segCompanies = seg.companies {
company_data.forEach { company_data in
for company in company_data.companies {
for id in segCompanies {
if company.id == id {
companies.append(company)
}
}
}
}
}
return companies
}
}
struct ContentView: View {
#StateObject var companyVM = CompanyViewModel() // <-- here
var body: some View {
NavigationView{
VStack {
ForEach(companyVM.company_data){ item in
ForEach(item.businessSegments){ segment in
/*Text(segment.name)
.multilineTextAlignment(.center)
.bold()
.font(.custom("Montserrat-Bold", size: 24)) */
// --- here
NavigationLink(destination:
CompanyName(segment: segment, companyVM: companyVM)){
Text(segment.name)
.frame(width:240,height:50)
.bold()
.foregroundColor(.white)
.background(Color.orange)
.clipShape(Capsule())
}
}
}
}
.padding()
.onAppear {
companyVM.getUserData()
}
}
}
}
struct CompanyName: View {
let segment: BusinessSegment // <-- here
#ObservedObject var companyVM: CompanyViewModel // <-- here
var body: some View {
NavigationView{
VStack{
Text(segment.name).foregroundColor(.blue)
.multilineTextAlignment(.center)
.bold()
.font(.custom("Montserrat-Bold", size: 24))
// --- here
ForEach(companyVM.getAllCompaniesFor(seg: segment)){ company in
Text(company.name)
.multilineTextAlignment(.center)
.bold()
.font(.custom("Montserrat-Bold", size: 24))
}
}.padding()
}
}
}
// MARK: - CompanyData
struct CompanyData: Identifiable, Codable,Hashable { // <-- here
let id = UUID() // <-- here
let client: Client
let businessSegments, companies: [BusinessSegment]
enum CodingKeys: String, CodingKey {
case client, companies
case businessSegments = "business_segments"
}
}
// MARK: - BusinessSegment
struct BusinessSegment: Identifiable, Codable,Hashable { // <-- here
let id: Int
let name: String
let status: String
let companies: [Int]?
}
// MARK: - Client
struct Client: Identifiable, Codable,Hashable { // <-- here
let id: Int
let firstName, lastName, email: String
enum CodingKeys: String, CodingKey {
case id, email
case firstName = "first_name"
case lastName = "last_name"
}
}
Based on the previous answer, I have already figured out my following question as I waited for a new response:
the code is like this
func getAllCompaniesFor(seg: BusinessSegment, com: [Companies]) -> [Companies] {
var companies = [Companies]()
print(companies)
if let segCompanies = seg.companies {
company_data.forEach { company_data in
for company in company_data.companies {
for id in segCompanies {
if company.id == id {
companies.append(company)
print(companies)
}
}
}
}
}
return companies
}
Related
I was making this app which would show certain information about upcoming animes, am using jikan API for this and it doesn't require any Authentication or any key
here is how the API look -
{
"request_hash": "request:top:3506eaba6445f7ad5cc2f78417bf6ed916b6aaad",
"request_cached": true,
"request_cache_expiry": 43675,
"top": [
{
"mal_id": 40356,
"rank": 1,
"title": "Tate no Yuusha no Nariagari Season 2",
"url": "https://myanimelist.net/anime/40356/Tate_no_Yuusha_no_Nariagari_Season_2",
"image_url": "https://cdn.myanimelist.net/images/anime/1245/111800.jpg?s=7302aaeb3bc4e1433b32d094e9d6f6f0",
"type": "TV",
"episodes": {},
"start_date": "Apr 2022",
"end_date": {},
"members": 300837,
"score": 0
},
{
"mal_id": 48583,
"rank": 2,
"title": "Shingeki no Kyojin: The Final Season Part 2",
"url": "https://myanimelist.net/anime/48583/Shingeki_no_Kyojin__The_Final_Season_Part_2",
"image_url": "https://cdn.myanimelist.net/images/anime/1989/116577.jpg?s=f6312bda2e67f86595936d0264696a91",
"type": "TV",
"episodes": {},
"start_date": "Jan 2022",
"end_date": {},
"members": 253849,
"score": 0
},
this is how I have written my code and for some reason when I run the app am just getting a plain blank white screen and also it prints out the last print statement that I have added saying " Fetch Failed: Unknown error "
please help me with this
import SwiftUI
struct Response: Codable{
var top: [Result]
}
struct Result: Codable {
var mal_id: Int
var rank: Int
var title: String
var type: String
var start_date: String
}
struct ContentView: View {
func loadData() {
guard let url = URL(string: "https://api.jikan.moe/v3/top/anime/1/upcoming") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.top = decodedResponse.top
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
#State private var top = [Result]()
var body: some View {
ScrollView {
List(top, id: \.mal_id) { item in
VStack(alignment: .leading) {
Text(item.title)
.font(.headline)
Text(String("\(item.rank)"))
.font(.headline)
Text(item.type)
.font(.headline)
Text(item.start_date)
.font(.headline)
}
}
}
.onAppear(perform: loadData)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
First, The problem is encountered when decoding your data, it looks like some fields are coming as null, so you should decode them as optional, in this case I found that var start_date: String field is the one causing the problem, so just make sure you make it optional: var start_date: String?
And Second, You don't need to put the List inside the ScrollView because by default a List is a type of ScrollView.
Model: (make some files optional)
struct Result: Codable {
var mal_id: Int
var rank: Int
var title: String
var type: String
var start_date: String?
}
Body Content: (removed the scrollview)
var body: some View {
List(top, id: \.mal_id) { item in
VStack(alignment: .leading) {
Text(item.title)
.font(.headline)
Text(String("\(item.rank)"))
.font(.headline)
Text(item.type)
.font(.headline)
Text(item.start_date ?? "")
.font(.headline)
}
}
.onAppear(perform: loadData)
}
I'm trying to filter data from two JSON fields at the same time depending on the system language: at this point, system language recognition seems to work (because different elements appears according to the system language), but for some reasons I can't find the exact code to do what I want to do...
As you can see from the code, I first tried to filter the field "data" for a specific word ("italiano" or "english"), then the field "autore" to categorize the various informations...but the results are not good: sometimes I see the same information repeated in the list, other times I only see the informations filtered from the "data" field but not from the "autore" field...
So, in conclusion, the result I would like to obtain is the following: if the system language is Italian, all the JSON data containing the keyword "italian" in the "data" field must be filtered first, and then filtered further based on the specific keyword contained in the "autore" field; if, on the other hand, the system language is not Italian (but any other language), all the JSON data containing the keyword "english" in the "data" field must be filtered first, and then - as in the previous case - filtered further based on the specific keyword contained in the "autore" field.
Do you have any advice? Because I think I'm not very far from the exact code...or maybe not :)
Thank you!
Here's the SwiftUI code:
import SwiftUI
import URLImage
struct HistoryView: View {
#ObservedObject var dm: DownloadManager
let sysLanguage = NSLocale.current.languageCode
var body: some View {
if sysLanguage == "it" {
List {
ForEach(dm.JSON.filter {
$0.data == "italiano"
}) { busso in
ForEach(dm.JSON.filter {
$0.autore == "storia"
}) { busso in
NavigationLink(
destination: DetailView(busso: busso)) {
HStack {
URLImage(URL(string: busso.fotoUrl) ?? furl)
.resizable()
.aspectRatio(contentMode: .fit)
Text(busso.titolo)
.font(.headline)
Spacer().layoutPriority(-0.1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 50)
}
}
}
}
.navigationTitle(Text("Storia di Busso"))
.navigationBarTitleDisplayMode(.large)
} else {
List {
ForEach(dm.JSON.filter {
$0.autore == "storia"
}) { busso in
ForEach(dm.JSON.filter {
$0.data == "english"
}) { busso in
NavigationLink(
destination: DetailView(busso: busso)) {
HStack {
URLImage(URL(string: busso.fotoUrl) ?? furl)
.resizable()
.aspectRatio(contentMode: .fit)
Text(busso.titolo)
.font(.headline)
Spacer().layoutPriority(-0.1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 50)
}
}
}
}
.navigationTitle(Text("Storia di Busso"))
.navigationBarTitleDisplayMode(.large)
}
}
}
struct HistoryView_Previews: PreviewProvider {
static var previews: some View {
HistoryView(dm: DownloadManager())
}
}
Here's the JSON file:
[
{
"id": "8",
"titolo": "View",
"autore": "galleria",
"testo": "",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-13 22:55:57",
"foto": "foto\/WP_20161110_001.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/WP_20161110_001.jpg"
},
{
"id": "7",
"titolo": "Storia di Busso",
"autore": "storia",
"testo": "Testo di prova",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-10 21:11:03",
"foto": "foto\/1a3e733334ec8948b0328af4e5b7288a.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/1a3e733334ec8948b0328af4e5b7288a.jpg"
},
{
"id": "6",
"titolo": "Test 2",
"autore": "ricette",
"testo": "",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-08 10:49:56",
"foto": "foto\/test_2.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/test_2.jpg"
},
{
"id": "5",
"titolo": "Test",
"autore": "eventi",
"testo": "",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-08 10:47:53",
"foto": "foto\/coastal-wash-web.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/coastal-wash-web.jpg"
},
{
"id": "4",
"titolo": "Immagine di prova",
"autore": "luoghi",
"testo": "",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-08 10:24:46",
"foto": "foto\/unnamed.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/unnamed.jpg"
},
{
"id": "3",
"titolo": "Panorama",
"autore": "galleria",
"testo": "",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-07 11:21:53",
"foto": "foto\/WP_20161110_001.jpg",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/WP_20161110_001.jpg"
},
{
"id": "2",
"titolo": "Comune di Busso",
"autore": "contatti",
"testo": "Indirizzo, telefono, mail, altri dati da inserire",
"data": "italiano",
"extra1": "",
"extra2": "",
"creazione": "2021-01-01 19:33:56",
"foto": "foto\/DSCN0914.JPG",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/DSCN0914.JPG"
},
{
"id": "1",
"titolo": "Chiesa",
"autore": "commercio",
"testo": "Testo di prova, abbastanza lungo per verificare l'impaginazione e correggere eventuali errori.",
"data": "english",
"extra1": "",
"extra2": "",
"creazione": "2021-01-01 19:32:02",
"foto": "foto\/CAM_0044.JPG",
"fotoUrl": "http:\/\/geniuspointfrezza.altervista.org\/foto\/CAM_0044.JPG"
}
]
Here's the DownloadManager code:
import SwiftUI
import Combine
class DownloadManager: ObservableObject {
#Published var JSON: [BussoModel] = []
#Published var searchText: String = "" {
didSet {
self.searchResults = self.JSON.filter { $0.titolo.contains(self.searchText) }
}
}
#Published var searchResults: [BussoModel] = []
init() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([BussoModel].self, from: bussoData)
DispatchQueue.main.async {
self.JSON = decodedData
}
} else {
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
Based on your comments, I believe this is what you're trying to do (unless I misunderstood something). I removed the URL image from the code, so you'll have to add it back.
A couple notes:
Try to do all filtering and data management within the DownloadManager. I added a filter function, which is called when the data gets downloaded and also when the view gets initialized.
Try to avoid hard coding strings into your code. I created a Language enum that will handle the "english" and "italian" filter.
If you ever run into a situation in your code where you're duplicating a whole section (like in your post you rewrote the view for "it" and "else"), then there's definitely a better way to do it.
.
import SwiftUI
//import URLImage
struct HistoryView: View {
#ObservedObject var dm: DownloadManager
let title: String
init(dm: DownloadManager, autore: String) {
self.dm = dm
dm.filter(autore: autore)
self.title = "\(autore)".capitalized + " di Busso"
}
var body: some View {
List {
if !dm.searchResults.isEmpty && !dm.isLoading {
ForEach(dm.searchResults) { busso in
NavigationLink(
destination: Text(busso.titolo)) {
HStack {
Text(busso.fotoUrl)
Text(busso.titolo)
.font(.headline)
Spacer(minLength: 0)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
}
}
} else if dm.isLoading {
ProgressView()
} else {
Text("No results.")
}
}
.navigationTitle(title)
}
}
struct HistoryView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
HistoryView(dm: DownloadManager(), autore: "galleria")
}
}
}
import SwiftUI
import Combine
class DownloadManager: ObservableObject {
#Published private(set) var JSON: [BussoModel] = []
#Published private(set) var isLoading: Bool = true
#Published private(set) var searchAutore: String?
#Published private(set) var searchResults: [BussoModel] = []
let language: Language
enum Language: String {
case italian
case english
}
init() {
language = NSLocale.current.languageCode == "it" ? .italian : .english
getData()
}
private func getData() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([BussoModel].self, from: bussoData)
DispatchQueue.main.async {
self.JSON = decodedData
self.isLoading = false
self.filter(autore: self.searchAutore)
}
} else {
print("No data")
self.isLoading = false
}
} catch {
print(error)
self.isLoading = false
}
}.resume()
}
func filter(autore: String?) {
searchAutore = autore
searchResults = JSON.filter({ (bussoModel) -> Bool in
return bussoModel.data == language.rawValue && (bussoModel.autore == searchAutore)
})
}
}
struct BussoModel: Codable, Identifiable {
let id: String
let titolo: String
let autore: String
let testo: String
let data: String
let extra1: String
let extra2: String
let creazione: String
let foto: String
let fotoUrl: String
}
I have everything correct up to the point where I am trying to access the type in a list. This the view:
import SwiftUI
struct ContentView: View {
var body: some View {
List {
ForEach(pokedex) { section in
Section(header: Text(section.name.english)) {
HStack {
Text("HP:")
Text("\(section.base.hp)")
Text("Attack:")
Text("\(section.base.attack)")
Text(section.type.bug)
}
}
}
}
}
}
This is the model:
// MARK: - PokeDexElement
struct PokeDexElement: Codable, Identifiable {
var id: Int
var name: Name
var type: [TypeElement]
var base: Base
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case type = "type"
case base = "base"
}
}
// MARK: - Base
struct Base: Codable, Identifiable {
var id = UUID()
var hp: Int
var attack: Int
var defense: Int
var spAttack: Int
var spDefense: Int
var speed: Int
enum CodingKeys: String, CodingKey {
case hp = "HP"
case attack = "Attack"
case defense = "Defense"
case spAttack = "Sp. Attack"
case spDefense = "Sp. Defense"
case speed = "Speed"
}
}
// MARK: - Name
struct Name: Codable, Identifiable {
var id = UUID()
var english: String
var japanese: String
var chinese: String
var french: String
enum CodingKeys: String, CodingKey {
case english = "english"
case japanese = "japanese"
case chinese = "chinese"
case french = "french"
}
}
enum TypeElement: String, Codable {
case bug = "Bug"
case dark = "Dark"
case dragon = "Dragon"
case electric = "Electric"
case fairy = "Fairy"
case fighting = "Fighting"
case fire = "Fire"
case flying = "Flying"
case ghost = "Ghost"
case grass = "Grass"
case ground = "Ground"
case ice = "Ice"
case normal = "Normal"
case poison = "Poison"
case psychic = "Psychic"
case rock = "Rock"
case steel = "Steel"
case water = "Water"
}
typealias PokeDex = [PokeDexElement]
This is the JSON:
[{
"id": 1,
"name": {
"english": "Bulbasaur",
"japanese": "フシギダネ",
"chinese": "妙蛙种子",
"french": "Bulbizarre"
},
"type": [
"Grass",
"Poison"
],
"base": {
"HP": 45,
"Attack": 49,
"Defense": 49,
"Sp. Attack": 65,
"Sp. Defense": 65,
"Speed": 45
}
},
{
"id": 2,
"name": {
"english": "Ivysaur",
"japanese": "フシギソウ",
"chinese": "妙蛙草",
"french": "Herbizarre"
},
"type": [
"Grass",
"Poison"
],
"base": {
"HP": 60,
"Attack": 62,
"Defense": 63,
"Sp. Attack": 80,
"Sp. Defense": 80,
"Speed": 60
}
},
{
"id": 3,
"name": {
"english": "Venusaur",
"japanese": "フシギバナ",
"chinese": "妙蛙花",
"french": "Florizarre"
},
This is the json
[{
"id": 1,
"name": {
"english": "Bulbasaur",
"japanese": "フシギダネ",
"chinese": "妙蛙种子",
"french": "Bulbizarre"
},
"type": [
"Grass",
"Poison"
],
"base": {
"HP": 45,
"Attack": 49,
"Defense": 49,
"Sp. Attack": 65,
"Sp. Defense": 65,
"Speed": 45
}
},
{
"id": 2,
"name": {
"english": "Ivysaur",
"japanese": "フシギソウ",
"chinese": "妙蛙草",
"french": "Herbizarre"
},
"type": [
"Grass",
"Poison"
],
"base": {
"HP": 60,
"Attack": 62,
"Defense": 63,
"Sp. Attack": 80,
"Sp. Defense": 80,
"Speed": 60
}
},
{
"id": 3,
"name": {
"english": "Venusaur",
"japanese": "フシギバナ",
"chinese": "妙蛙花",
"french": "Florizarre"
},
I writing because I need to search a json-key passed in a function like a string. Do you have any suggestion on how I could implement it? Once I find the key I also need to edit the value. Here there is the code I wrote until now:
JSON:
{
"JSONRoot": {
"version": 1,
"Town": {
"hour": 0,
"latitude": "",
"longitude": 0,
"latitudine": 0
},
"MeasurePoints": {
"MeasurePoint": [
{
"code": "",
"codelocation": "",
}
]
},
"Wakeup": {
"startH": 6,
"startM": 0,
"maxAttempts": 3,
"maxRetry": 10
},
"Config": {
"port": 12345,
"A": {
"writable": true,
"value": 12
},
"B": {
"writable": true,
"value": 8
},
},
"Sales": {
"Stores": {
"Store": [
{
"description": "A description",
"type": "1",
"Floors": {
"basement": true,
"number": 2
},
"Doors": {
"type": "",
"number": 7
},
"Lights": {
"number": 20
}
},
{
"description": "A description",
"type": "4",
"Floors": {
"basement": none,
"number": 1
},
"Doors": {
"type": "",
"number": 4
},
"Lights": {
"number": 8
}
}
]
}
}
}
}
Structs with codable:
// MARK: - JSONConfig
struct JsonConfig: Codable {
let jsonRoot: JSONRoot?
enum CodingKeys: String, CodingKey {
case jsonRoot = "JSONRoot"
}
}
// MARK: - JSONRoot
struct JSONRoot: Codable {
let version: Int?
let measurePoints: MeasurePoints?
let wakeup: Wakeup?
let config: Config?
let sale: Sale?
enum CodingKeys: String, CodingKey {
case version
case measurePoints = "MeasurePoints"
case wakeup = "Wakeup"
case config = "Config"
case sale = "Sale"
}
}
// MARK: - Stores
struct Stores: Codable {
let stores: [Store]?
enum CodingKeys: String, CodingKey {
case stores = "Stores"
}
}
// MARK: - Store
struct Store: Codable {
let storeDescription: String?
let type: Int?
let floors: Floors?
let doors: Doors?
let lights: Lights?
enum CodingKeys: String, CodingKey {
case storeDescription = "description"
case type
case floors = "Floors"
case doors = "Doors"
case lights = "Lights"
}
}
// MARK: - Floors
struct Floors: Codable {
let basement: Bool?
let number: Int?
}
// MARK: - Doors
struct Doors: Codable {
let type: String?
let number: Int?
}
// MARK: - Lights
struct Lights: Codable {
let number: Int?
}
// MARK: - MeasurePoints
struct MeasurePoints: Codable {
let measurePoint: [MeasurePoint]?
enum CodingKeys: String, CodingKey {
case measurePoint = "MeasurePoint"
}
}
// MARK: - MeasurePoint
struct MeasurePoint: Codable {
let code, codeLocation: String?
}
// MARK: - Config
struct Config: Codable {
let port: Int?
let a, b: K?
enum CodingKeys: String, CodingKey {
case port
case a = "A"
case b = "B"
}
}
// MARK: - K
struct K: Codable {
let writable: Bool?
let value: Int?
}
// MARK: - Wakeup
struct Wakeup: Codable {
let startH, startM, maxAttempts, maxRetry: Int?
}
Function to search for a key:
func setKeyValue(jsonKey: String, value: String) {
let decoder = JSONDecoder()
let jsonData = Data(C.jsonString.utf8)
if let jsonResult = try? decoder.decode(JsonConfig.self, from: jsonData) {
// At this point I have the jsonKey = "JSONRoot.Wakeup.maxRetry" but how I can use it to search for
// the key in the jsonResult?
}
}
Obviously I need to create a new struct to edit the json but one step at a time.
Using JSONSerialisation is probably the most straightforward way here
var value: Any?
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
let keys = "JSONRoot.Wakeup.maxRetry".split(separator: ".").map {String($0)}
var dict = jsonResult
for i in 0..<keys.count {
if let temp = dict[keys[i]] as? [String:Any] {
dict = temp
continue
}
value = dict[keys[i]]
}
}
} catch {
print(error)
}
Note that this doesn't support arrays but a solution for that is very dependent on how the search key syntax would handle an array
If my thinking is correct as you, you can try with this code.
override func viewDidLoad() {
super.viewDidLoad()
let jsonString = """
{
"JSONRoot": {
"version": 1,
"Town": {
"hour": 0,
"latitude": "",
"longitude": 0,
"latitudine": 0
},
"MeasurePoints": {
"MeasurePoint": [{
"code": "",
"codelocation": ""
}]
},
"Wakeup": {
"startH": 6,
"startM": 0,
"maxAttempts": 3,
"maxRetry": 10
},
"Config": {
"port": 12345,
"A": {
"writable": true,
"value": 12
}
},
"Sales": {
"Stores": {
"Store": [{
"description": "A description",
"type": "1",
"Floors": {
"basement": true,
"number": 2
},
"Doors": {
"type": "",
"number": 7
},
"Lights": {
"number": 20
}
},
{
"description": "A description",
"type": "4",
"Floors": {
"basement": "none",
"number": 1
},
"Doors": {
"type": "",
"number": 4
},
"Lights": {
"number": 8
}
}
]
}
}
}
}
"""
editJson(jsonString)
}
func editJson(_ jsonString: String) {
do{
let jsonData = Data(jsonString.utf8)
var jsonObject = try JSONSerialization.jsonObject(with: jsonData)
parseDict(&jsonObject)
print("jsonObject: \(String(describing: jsonObject))")
}catch let error {
print(error.localizedDescription)
}
}
func parseDict(_ jsonObject: inout Any) {
if let _ = jsonObject as? String {
return
} else if var dictionary = jsonObject as? Dictionary<String, Any> {
for (key, value) in dictionary {
var nextObject = value
parseDict(&nextObject)
if let value = getValueWith(key), let _ = dictionary.removeValue(forKey: key) {
dictionary[key] = value
} else {
dictionary[key] = nextObject
}
}
jsonObject = dictionary
}else if let array = jsonObject as? Array<Any> {
var updatedArray = array
for (index, value) in array.enumerated() {
var nextObject = value
parseDict(&nextObject)
updatedArray[index] = nextObject
}
jsonObject = updatedArray
}
}
func getValueWith(_ key: String) -> String? {
return [
"description" : "Amit (amitpstu1#gmail.com) ... so on"
][key]
}
You can refresh your memory or learn more here:
https://developer.apple.com/documentation/foundation/archives_and_serialization/using_json_with_custom_types
You would be looking at merge json from different depths section. Using encodable extension etc.
You could also look here: In Swift, can one use a string to access a struct property? If you want to roll your own search function, like a modified dfs or something.
JSON File:
{
"success": 1,
"msg": "User Phone Usage",
"data": [
{
"date": "2019-12-19",
"val": [
{
"ride_id": 44,
"date": "2019-12-19 09:32:19",
"total_km": null,
"startTime": "2019-12-19 09:32:19",
"endTime": "2019-12-19 09:37:08",
"rides": 0
},
{
"ride_id": 43,
"date": "2019-12-19 09:28:16",
"total_km": null,
"startTime": "2019-12-19 09:28:16",
"endTime": "2019-12-19 09:32:23",
"rides": 0
},
{
"ride_id": 42,
"date": "2019-12-19 09:28:12",
"total_km": null,
"startTime": "2019-12-19 09:28:12",
"endTime": "2019-12-19 09:29:13",
"rides": 0
}
]
},
{
"date": "2019-12-13",
"val": [
{
"ride_id": 2,
"date": "2019-12-13 08:14:34",
"total_km": 12.64,
"startTime": "2019-12-13 08:14:34",
"endTime": "2019-12-18 03:49:43",
"rides": 2
}
]
},
{
"date": "2019-12-12",
"val": [
{
"ride_id": 1,
"date": "2019-12-12 06:59:26",
"total_km": 101.36,
"startTime": "2019-12-12 06:59:26",
"endTime": "2019-12-18 03:07:00",
"rides": 0
}
]
}
]
}
So i have to set the tableview section title as (date) and show the event occurred on that particular date.
Below is the sample image i have to achieve
Section title is date and total km, rides are the row that i want to populate as per the date specified. API data is saved in model class and not in dictionary format so need some solution where i can sort the data and display it on the table view.
Model class:
class ShowDetails
{
var startRideDateTime:String?
var rideID:Int?
var endRideDateTime:String?
var totalKM:Double?
var rides:Int?
init(rideID: Int?,totalKM: Double?, startRideDateTime: String?, endRideDateTime: String?, rides: Int?) {
self.rideID = rideID
self.totalKM = totalKM
self.startRideDateTime = startRideDateTime
self.endRideDateTime = endRideDateTime
self.rides = rides
}
}
Viewcontroller code:
showdetailsArray is the saved array from API Response
func grouping()
{
let groupData = Dictionary(grouping: self.showDetailsArray){ (element) -> String in
return (element.startRideDateTime ?? "")
}
groupData.forEach { (key) in
print("VALUES")
print(groupData.values)
}
}
class ShowDetails {
var startRideDateTime:String?
var rideID:Int?
var endRideDateTime:String?
var totalKM:Double?
var rides:Int?
init(rideID: Int?,totalKM: Double?, startRideDateTime: String?, endRideDateTime: String?, rides: Int?) {
self.rideID = rideID
self.totalKM = totalKM
self.startRideDateTime = startRideDateTime
self.endRideDateTime = endRideDateTime
self.rides = rides
}
}
class myData {
let date: String // this value should not be optional as you are sorting on basis of this
let value: [ShowDetails]?
init(date: String, value: [ShowDetails]?) {
self.date = date
self.value = value
}
}
// set your data here
var data:[myData] = []
// this is the array sorted according to date
data.sort(by: { $0.date > $1.date })
Use Codable for data parsing and the later sorting it based on date.
The models that you can use,
struct Root: Decodable {
var data: [Model]
}
struct Model: Decodable {
let date: String
let val: [Ride]
}
struct Ride: Decodable {
let rideId: Int
let date: String
let totalKm: Double?
let startTime: String
let endTime: String
}
Now, parse the data like,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
var response = try decoder.decode(Root.self, from: data)
response.data.sort { $0.date < $1.date }
print(response)
} catch {
print(error)
}
In the above code, use response as your tableView's dataSource.
So i found the solution to the Ans if anybody needs:
After saving the data into class model at the end you can add this code to Sort.
func groupingByDate()
{
let groupData = Dictionary(grouping: self.showDetailsArray){ (element) -> String in
return (element.startRideDate ?? "") //It fetches the element from model class array and set as key of dictionary and other elements as values
}
groupData.keys.forEach { (key) in
let values = groupData[key]
twoDArray.append(values ?? []) //twoDArray is a two dimensional Array
}
//reloadTableview if you want
}