{
"data":{
"email":"ms.lightwave#example.com",
"password":"123",
"token":""
}
}
struct JsonResult: View{
#State private var results = [GetData]()
var body: some View{
List(results, id: \.email){ item in
VStack(alignment: .leading) {
Text(item.password)
.font(.headline)
Text(item.token)
.font(.headline)
}
}.task {
await loadData()
}
}
struct Response : Codable {
var results: [GetData]
}
struct GetData: Codable{
var data : [Result]
}
struct Result: Codable {
var email: String
var password: String
var token: String
}
func loadData() async{
guard let url = URL(string: "MYURL") else {
print("invalid URL")
return
}
do{
let(data,_) = try await URLSession.shared.data(from: url)
// more code
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data)
{
results = decodedResponse.results
}
} catch {
print("Invalid Data")
}
}
}
i need to know if the codable structure is right according to the structure of data i gave ? and also the fetching in list ? please i need help in the URLsession i am still new and i barely know about url alot !
i would be grateful if you help me ! thank you verrrry much !!!!
In the JSON there is no array involved (no [] at all).
The model corresponding to the JSON is
struct Response: Decodable {
let data : UserData
}
struct UserData: Decodable {
let email: String
let password: String
let token: String
}
So the data source cannot be declared as an array. To avoid an optional type create an enum with associated values indicating a state. The benefit is that you can show different views depending on the state
struct JsonResult: View {
enum LoadingState {
case idle, loading, loaded(UserData), failure(Error)
}
this is the rest of the struct, consider that there is no List either because UserData is a single object.
#State private var state : LoadingState = .idle
var body: some View {
VStack {
switch state {
case .idle: EmptyView()
case .loading: ProgressView()
case .loaded(let userData):
VStack(alignment: .leading) {
Text(userData.password)
.font(.headline)
Text(userData.email)
.font(.headline)
}
case .failure(let error): Text(error.localizedDescription)
}
}.task {
await loadData()
}
}
func loadData() async {
state = .loading
guard let url = URL(string: "MYURL") else {
state = .failure(URLError(.badURL))
return
}
do {
let (data,_) = try await URLSession.shared.data(from: url)
// more code
let decodedResponse = try JSONDecoder().decode(Response.self, from: data)
state = .loaded(decodedResponse.data)
} catch {
state = .failure(error)
print(error) // this shows the real DecodingError
}
}
}
struct Response : Decodable, Hashable {
var results: [GetData]
}
struct GetData: Decodable, Hashable{
var data : [Result]
}
struct Result: Decodable, Hashable {
var email: String
var password: String
var token: String
}
enum RequestError: Error {
case invalidURL
case missingData
}
class JsonResultViewModel: ObservableObject{
#Published var response = [Response]()
func performHTTPRequest(urlString: String) async throws{
guard let url = URL(string: urlString) else {throw RequestError.invalidURL}
guard let (data, resp) = try? await URLSession.shared.data(from: url) else{throw RequestError.invalidURL}
guard (resp as? HTTPURLResponse)?.statusCode == 200 else {throw RequestError.invalidURL}
let decoder = JSONDecoder()
guard let jsonResponse = try? decoder.decode([Response].self, from: data) else {throw RequestError.missingData}
DispatchQueue.main.async {
self.response = jsonResponse
}
}
}
struct ContentView: View {
#StateObject private var results = JsonResultViewModel()
var body: some View {
List(results.response.indices, id: \.self){ index in
VStack(alignment: .leading) {
Text(results.response[index].results[index].data[index].email)
.font(.headline)
Text(results.response[index].results[index].data[index].token)
.font(.headline)
}
}
.onAppear(perform: {
Task{
do {
try await results.performHTTPRequest(urlString: "wwww.url.com")
} catch RequestError.invalidURL{
print("invalid URL")
} catch RequestError.missingData{
print("missing data")
}
}
})
}
}
Related
I have data on from the api on my app
I am sending the new data from my site
I want to put a message or notification on the application about the arrival of new data for this API
What is the best way to do this task?
This is the data I got
struct VideoView_Msrhiat: View {
#StateObject var model = Api()
var body: some View {
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(model.models) { item in
VStack {
Text(item.title)
}
}
}
.onAppear() {
model.getData(url: APIgetURL.Tap1)
}
}
}
Also
struct model : Identifiable, Codable {
let id = UUID()
var color : String?
var details : String
}
class Api : ObservableObject{
#Published var models : [model] = []
func getData (url : String) {
guard let url = URL(string: url) else { return }
var request = URLRequest(url: url)
let token = "38|Xxxx"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, responce, err in
guard let data = data else { return }
do {
let dataModel = try JSONDecoder().decode([model].self, from: data)
DispatchQueue.main.async {
self.models = dataModel
} catch {
print("error: ", error)
}
}
resume()
}
}
class Network {
func getingData(completion : #escaping ([Model]) -> ()) async {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let posts = try? JSONDecoder().decode([Model].self, from: data) {
completion(posts)
}
}
catch {
print("error")
}
}
}
You could try something like this approach, where getData works with Decodable, as was mentioned in the previous answer.
In this particular example an array of Decodable.
struct Post: Decodable, Identifiable {
let userId: Int
let id: Int
let title: String
let body: String
var comments: [Comment]?
}
struct Comment: Decodable, Identifiable {
let postId: Int
let id: Int
let name: String
let email: String
let body: String
}
struct ContentView: View {
let client = Network()
#State var posts: [Post] = []
var body: some View {
List {
ForEach(posts, id: \.id) { post in
Text(post.title)
}
}
.task {
posts = await client.getData(from: "https://jsonplaceholder.typicode.com/posts")
// all comments from the first post
let comments: [Comment] = await client.getData(from: "https://jsonplaceholder.typicode.com/posts/\(posts[0].id)/comments")
print("\n---> comments: \(comments)")
}
}
}
class Network {
func getData<T: Decodable>(from urlString: String) async -> [T] {
guard let url = URL(string: urlString) else {
print(URLError(.badURL))
return [] // <-- todo, deal with errors
}
do {
let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print(URLError(.badServerResponse))
return [] // <-- todo, deal with errors
}
let results = try JSONDecoder().decode([T].self, from: data)
return results
}
catch {
return [] // <-- todo, deal with errors
}
}
}
Is it what you're looking for?
import Foundation
class Network {
func getingData<Model: Decodable>(completion : #escaping ([Model]) -> ()) async {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let posts = try? JSONDecoder().decode([Model].self, from: data) {
completion(posts)
}
} catch {
print("error")
}
}
}
If so, you only need to declare the Model type as generic. The only thing you need Model to conform is Decodable (the requirement of the JSONDecoder().decode([Model].self, from: data) call).
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
My API return a JSON data with 2 possible data set formats and how do I decode it? Thank you so much in advance
result.json
format 1:
{
product_name: "coffee"
product_code: "100010"
variant: [
flavour: [
{
flavour_name: "chocolate",
flavour_code: "C001",
},
{
flavour_name: "vanilla",
flavour_code: "C002",
}
],
size: [ { ... }, { ... } ]
]
}
format 2:
{
product_name: "bread",
product_code: "B123",
variant: [
portion: ["quarter", "half", "full"],
type: ["plain", "wheat", "oat"],
]
}
MenuViewModel.swift
class MenuDetailViewModel: ObservableObject, MenuDetailService {
var apiSession: APIService
#Published var detaildata: MenuDetailData?
var cancellables = Set<AnyCancellable>()
init(apiSession: APIService = APISession()) {
self.apiSession = apiSession
}
func getMenuDetail() {
let cancellable = self.getMenuDetail(menuCode: menuCode)
.sink(receiveCompletion: { result in
switch result {
case .failure(let error):
print("Handle error: \(error)")
case .finished:
break
}
}) { (detail) in
self.detaildata = detail.data
}
cancellables.insert(cancellable)
}
}
MenuDetailService.swift
protocol MenuDetailService {
var apiSession: APIService {get}
func getMenuDetail(menuCode: String) -> AnyPublisher<MenuDetailAPIResponse, APIError>
}
extension MenuDetailService {
func getMenuDetail(menuCode: String) -> AnyPublisher<MenuDetailAPIResponse, APIError> {
return apiSession.request(with: APIEndpoint.menuDetail(menuCode: menuCode))
.eraseToAnyPublisher()
}
}
APIService.swift
protocol APIService {
func request<T: Decodable>(with builder: RequestBuilder) -> AnyPublisher<T, APIError>
}
RequestBuilder.swift
protocol RequestBuilder {
var urlRequest: URLRequest {get}
}
APISession.swift
struct APISession: APIService {
func request<T>(with builder: RequestBuilder) -> AnyPublisher<T, APIError> where T: Decodable {
// 1
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// 2
return URLSession.shared
.dataTaskPublisher(for: builder.urlRequest)
// 3
.receive(on: DispatchQueue.main)
// 4
.mapError { _ in .unknown }
// 5
.flatMap { data, response -> AnyPublisher<T, APIError> in
if let response = response as? HTTPURLResponse {
// let dataString = String(data: data, encoding: .utf8) {
// print("Response data string:\n \(dataString)")
// }
if (200...299).contains(response.statusCode) {
print(String(data: data, encoding: .utf8) ?? "")
// 6
return Just(data)
.decode(type: T.self, decoder: decoder)
.mapError {_ in .decodingError}
.eraseToAnyPublisher()
} else {
// 7
return Fail(error: APIError.httpError(response.statusCode))
.eraseToAnyPublisher()
}
}
return Fail(error: APIError.unknown)
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
APIEndpoint.swift
enum APIEndpoint {
case menuDetail(menuCode: String)
}
extension APIEndpoint: RequestBuilder {
var urlRequest: URLRequest {
switch self {
case .menuDetail(let menuCode):
guard let url = URL(string: "API_URL/product/detail")
else {preconditionFailure("Invalid URL format")}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(Constants.API_TOKEN, forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
let body: [String: Any] = ["brand": 1, "city": "", "menu_code": menuCode, "member_phone": ""]
let rb = try! JSONSerialization.data(withJSONObject: body)
request.httpBody = rb
return request
}
}
}
MenuDetailAPIResponse.swift
struct MenuDetailAPIResponse: Codable {
let data: MenuDetailData
}
MenuDetailData.swift
struct MenuDetailData: Codable, Identifiable {
let id = UUID()
let productName: String
let productCode: String
let variant: [MenuVariant]
}
struct MenuVariant: Codable, Identifiable, Hashable {
let id = UUID()
let flavour: [MenuFlavour]
let size: [MenuSize]
}
struct MenuFlavour: Codable, Identifiable, Hashable {
let id = UUID()
let flavourName: String
let flavourCode: String
}
try combine two models into one with optional variables:
struct MenuDetailData: Codable, Identifiable {
let id = UUID()
let productName: String
let productCode: String
let variant: [MenuVariant]
}
struct MenuVariant: Codable, Identifiable, Hashable {
let id = UUID()
let flavour: [MenuFlavour]?
let size: [MenuSize]?
let portion: [String]?
let type: [String]?
}
and after you can check received data
if let _ = model.type
if let _ = model.flavour...
etc.
I'm having a hard time understanding how JSON data is supposed to be updated in a List in SwiftUI. I'm fetching data from NewsAPI.org, my list and detail views work just fine. I'm trying to figure out how to keep the list up-to-date when my json data changes, but the data remains outdated. I'm still a beginner to swift so if I made a mistake any help would be greatly appreciated.
UPDATED
Attempted to use combine with the same results, outdated data
New data class
class NewsData: ObservableObject {
var objectWillChange = PassthroughSubject<NewsData, Never>()
#Published var articles = [Article]() {
willSet {
objectWillChange.send(self)
}
}
init() {
guard let url = URL(string: "http://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY") else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(News.self, from: data) {
DispatchQueue.main.async() {
self.articles = response.articles
}
}
}
}
.resume()
}
/*
init() {
URLSession.shared
.dataTaskPublisher(for: URLRequest(url: URL(string: "http://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY")!))
.map(\.data)
.decode(type: News.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { data in
self.articles = data.articles
})
.store(in: &self.cancellables)
}
*/
/*
init() {
load()
}
func load() {
guard let url = URL(string: "http://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY") else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(News.self, from: data) {
DispatchQueue.main.async() {
self.articles = response.articles
}
}
}
}
.resume()
}
*/
}
My data old class
struct News : Codable {
var articles : [Article]
}
struct Article : Codable {
let description : String?
let title : String?
let author: String?
let source: Source
let content: String?
let publishedAt: String?
}
struct Source: Codable {
let name: String?
}
class NewsData: ObservableObject {
#Published var news: News = News(articles: [])
init() {
load()
}
func load() {
guard let url = URL(string: "http://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY_HERE") else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(News.self, from: data) {
DispatchQueue.main.async() {
self.news = response
}
}
}
}
.resume()
}
}
My ContentView
func relativeDate(date: String) -> String {
let formatter = RelativeDateTimeFormatter()
let dateFormatter = ISO8601DateFormatter()
return formatter.localizedString(for: dateFormatter.date(from: date) ?? Date(), relativeTo: Date())
}
struct ContentView: View {
#ObservedObject var news: NewsData
var body: some View {
NavigationView {
List(news.news.articles , id: \.title) { article in
VStack (alignment: .leading, spacing: 5){
Text(article.title ?? "")
.fontWeight(.bold)
.font(.subheadline)
.lineLimit(1)
Text(article.description ?? "")
.font(.subheadline)
.foregroundColor(.secondary)
.lineLimit(1)
Text(relativeDate(date: article.publishedAt ?? ""))
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.navigationTitle("News")
}
}
}
I'm trying to retrieve data from an API url
This is what the implementation guide reads, so the URL should match this format:
The request for information in JSON format is submitted as a GET
operation to the endpoint:
http://digit-eyes.com/gtin/v2_0/?upc_code=x&app_key=x&signature=x&language=x&field_names=x
This is my function that fetches the data from the JSON and decodes it from JSON.
I've replaced the signature and API Key with x.
Signature is generated by combining the app_key and the barcode forming a hashed value.
func loadData() {
guard let url = URL(string: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=x&signature=x&language=en&field_names=description,brand,ingredients,image,upc_code") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, responce, error in
if let data = data {
if let decodedRepsonce = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async{
self.results = decodedRepsonce.results
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")") //This is the error I get
}.resume()
}
Here's what I get when I paste the URL into safari
I've tested the URL with: "https://itunes.apple.com/search?term=radiohead&entity=song" and it works. A noticeable difference is that this link downloads a JSON file, my URL doesn't.
I store the JSON into an array Results:
struct Result: Codable {
var description: String
var brand: String
var ingredients: String
var image: String
var upc_code: Int
}
Which is then displayed in the body:
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.indicator.padding()
List(self.results, id: \.upc_code) { item in
VStack(alignment: .leading) {
Text(item.brand)
.font(.headline)
Text(item.description)
}
}
}
EDIT
Dealing with nulls from the JSON data
To call loadData, I have an .onAppear on a VStack in the body.
.onAppear {
//let signiture = self.scannedCode.barcode.hashedValue("Ls75O8z1q9Ep9Kz0")
self.loadData(url: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=/9nOS+obsRF5&signature=DiKl4lURenoNe53I0a/i3kiAkQQ=&language=en&field_names=description,ingredients,brand,image") { error, result in
if let err = error {
print(err)
}
}
}
}
This is in a struct outside of the body
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
}
I'm now getting:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "brand", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
In the JSON object, the brand name isn't always found, so it's sometimes null. I don't know how I can resume the decoder if a null is found.
Try this code to call in the body:
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
loadData(url: "https://google.com") { error, result in
if let err = error {
print(err)
}
}
Try to modify the struct as follows and add the other variables. I also noticed that upc_code is a String.
struct Result: Codable {
var description: String?
var brand: String?
var ingredients: String?
var image: String?
var upc_code: String?
var return_message: String?
var return_code: String?
}