I'm using Alamofire and SwiftyJSON and I'm getting my data, but my view is only repeating the data for the first element of the JSON. The data should be Monday, Tuesday, Wednesday, and so on
[![enter image description here][1]][1]
Here's the class I'm using
class observer : ObservableObject {
#Published var datas = [datatype]()
init() {
AF.request("https://api.npoint.io/e667b934a476b8b88745").responseData { (data) in
let json = try! JSON(data: data.data!)
let trainingDay = json["weekExercise"].arrayValue.map
{$0["exercise"].stringValue}
print(trainingDay)
for i in json["weekExercise"]{
self.datas.append(datatype(id: i.1["weeknumber"].intValue,
day: i.1["day"].stringValue,
exercise: i.1["exercise"].stringValue,
dayMiles: i.1["dayMiles"].intValue))
}
}
}
}
My data looks like this:
{
"weeknumber": 1,
"weekExercise": [
{
"day": "Monday",
"dayMiles": 6,
"exercise": "6 miles"
},
{
"day": "Tuesday",
"dayMiles": 9,
"exercise": "12 x 400m WU/CD"
},
{
"day": "Wednesday",
"dayMiles": 0,
"exercise": "Rest"
},
{
"day": "Thursday",
"dayMiles": 6,
"exercise": "6 miles"
},
{
"day": "Friday",
"dayMiles": 6,
"exercise": "6 miles"
},
{
"day": "Saturday",
"dayMiles": 6,
"exercise": "6 miles"
},
{
"day": "Saturday",
"dayMiles": 8,
"exercise": "8 miles"
}
],
"totalWeekMiles": 41,
"planName": "Hanson Method Advance"
}
[1]: https://i.stack.imgur.com/9ZlRy.png?s=256
My suggestion is to take advantage of Alamofire's support of Codable and Combine. SwiftyJSON is outdated and not needed anymore.
import Combine
import Alamofire
struct ExerciseData : Codable, Identifiable {
let id : Int
let weeknumber : Int
let weekExercise : [Exercise]
}
struct Exercise : Codable, Identifiable {
let id = UUID()
let day: String
let dayMiles: Int
let exercise: String
private enum CodingKeys : String, CodingKey { case day, dayMiles, exercise}
}
class Observer : ObservableObject {
private var subscription : AnyCancellable?
#Published var exercises = [Exercise]()
init() {
subscription = AF.request("https://api.npoint.io/e667b934a476b8b88745")
.publishDecodable(type: [ExerciseData].self, queue: .global())
.result()
.receive(on: DispatchQueue.main)
.map{ result -> [Exercise] in
switch result {
case .success(let data) : return data.first?.weekExercise ?? []
case .failure: return []
}
}
.sink(receiveCompletion: { _ in
self.subscription = nil // this breaks the retain cycle
}, receiveValue: { exercises in
self.exercises = exercises
})
}
}
You can even remove Alamofire in favor of the built-in data task publisher
import Combine
class Observer : ObservableObject {
private var subscription : AnyCancellable?
#Published var exercises = [Exercise]()
init() {
let url = URL(string: "https://api.npoint.io/e667b934a476b8b88745")!
subscription = URLSession.shared.dataTaskPublisher(for: url)
.receive(on: DispatchQueue.main)
.map(\.data)
.decode(type: [ExerciseData].self, decoder: JSONDecoder())
.map{$0.first?.weekExercise ?? []}
.replaceError(with: [])
.sink(receiveCompletion: { _ in
self.subscription = nil
}, receiveValue: { exercises in
self.exercises = exercises
})
}
}
The error handling is rudimentary. It returns an empty array in case of an error.
Related
I read a JSON-String from my api with a Date-Array.
[
{
"id": 1,
"headline": "How to messure the T8",
"descriptiontext": "Learn how to messure the T8",
"adddate": {
"date": "2022-03-02 00:00:00.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"changedate": {
"date": "2022-03-02 00:00:00.000000",
"timezone_type": 3,
"timezone": "UTC"
}
}
]
I had add a datemodel to the the class. without the date my decoding works and the objects would be viewed. But it seems that i have a fail in my decoding of the date.
Here is my struct
// MARK: - TutorialModel
struct TutorialModel: Identifiable, Codable {
let id: Int
let adddate, changedate: CustomDateModel
let headline, descriptiontext: String
}
// MARK: - CustomDateModel
struct CustomDateModel: Codable {
let date: String
let timezoneType: Int
let timezone: String
enum CodingKeys: String, CodingKey {
case date
case timezoneType
case timezone
}
}
And here is the part, were i try to Decode the JSON
guard let newTutorials = try? JSONDecoder().decode([TutorialModel].self, from: data) else { return }
DispatchQueue.main.async { [weak self] in
self?.tutorials = newTutorials
}
Could it be, that I have to read the data in a other way?
I imported json manually (json file). I get this error when I decode the json.
The data couldn’t be read because it isn’t in the correct format.
where am I doing wrong? Is my model wrong? Is my reference wrong?
JSON:
{
"allQuiz": [
{
"title":"Ağustos Test 1",
"test": [
{
"id": 1,
"question":"Şekle göre aşağıdakiler hangisi doğrudur",
"isQuestionImage": true,
"isSectionImage": false,
"imageName":"1.png",
"sections": {
"A":"2 numaralı aracın öncelikle geçmesi",
"B":"1 numaralı aracın hızını arttırarak kavşağa girmesi",
"C":"2 numaralı aracın 3 numaralı aracın geçmesini beklemesi",
"D":"3 numaralı aracın 2 numaralı aracı ikaz ederek durdurması"
},
"correct": "A"
}
]
}
]
}
Model:
struct QuizContainer: Codable {
var allQuiz: [Quiz]
}
struct Quiz: Codable {
var title: String
var test: [Test]
}
enum TestSectionType: String, Codable, Hashable {
case A = "A"
case B = "B"
case C = "C"
case D = "D"
}
struct Test: Codable {
var id: Int
var question: String
var isQuestionImage: Bool
var isSectionImage: Bool
var imageName: String
var sections: [TestSectionType.RawValue : String]
var correct: String
}
JSON Decode:
func getQuizQuestion() {
let databaseReference = Database.database().reference().child("allQuiz")
databaseReference.observeSingleEvent(of: .value) { snapshot in
do {
let foo = try FirebaseDecoder().decode(QuizContainer.self, from: snapshot.value ?? "")
print(foo)
} catch {
print(error.localizedDescription)
}
}
}
Could the getQuizQuestion function be faulty?
I solved my problem.
I don't need reference.
func getQuizQuestion() {
let databaseReference = Database.database().reference() // here
databaseReference.observeSingleEvent(of: .value) { snapshot in
do {
let foo = try FirebaseDecoder().decode(QuizContainer.self, from: snapshot.value ?? "")
print(foo)
} catch {
print(error.localizedDescription)
}
}
}
I'm trying to decode some json for my application and I usually do this.
My struct;
struct RequestTypes: Codable {
let MerchRequestTypeID: Int?
let TypeName: String?
let LayoutID: Int?
private enum CodingKeys: Int, CodingKey {
case MerchRequestTypeID
case TypeName
case LayoutID
}
}
And decoding;
func downloadRequestTypesJson(){
guard let gitUrl = URL(string: "URL") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let RequestData = try decoder.decode(Array<RequestTypes>.self, from: data)
DispatchQueue.main.sync {
print(RequestData[0].MerchRequestTypeID)
print(RequestData[1].MerchRequestTypeID)
print(RequestData[2].MerchRequestTypeID)
}
} catch let err {
print("Err", err)
}
}.resume()
}
This works fine for below json;
[
{
"MerchRequestTypeID": 1,
"TypeName": "Stok",
"LayoutID": 1
},
{
"MerchRequestTypeID": 2,
"TypeName": "Stand",
"LayoutID": 2
},
{
"MerchRequestTypeID": 3,
"TypeName": "Eğitim",
"LayoutID": 2
}
]
But now I need to decode this json and im getting Expected to decode Array but found a dictionary instead. error;
{
"RequestTypes": [
{
"MerchRequestTypeID": 1,
"TypeName": "Stock",
"LayoutID": 1
},
{
"MerchRequestTypeID": 2,
"TypeName": "Stand",
"LayoutID": 2
},
{
"MerchRequestTypeID": 3,
"TypeName": "Education",
"LayoutID": 2
}
]
}
Couldn't be able to find proper way to do this. Any help is appreciated.
Edit: I am beginner on Swift. I want to know how to decode second json and how to reach its elements.
That's because you are trying to decode a JSON object with a "RequestTypes" property and not an array. One solution is to create a new struct for this data structure and use that to decode your JSON:
struct RequestTypesContainer: Codable {
let RequestTypes : [RequestTypes]
private enum CodingKeys: String, CodingKey {
case RequestTypes
}
}
And then:
let RequestData = try decoder.decode(RequestTypesContainer.self, from: data)
I’m stuck with some problem. I have a JSON response from server:
{
"days" : [
{
"id" : 1,
"name" : "Day 1 - first day",
"url": "http://example.com/days/1"
},
{
"id" : 2,
"name" : "Day 2 - second day",
"url": "http://example.com/days/2"
},
...
],
"week" : [
{
"id" : 1,
"dayIds" : [1, 2, 6, 9, 23, 44, 2345],
"name" : "Rest week"
},
{
"id" : 35,
"dayIds" : [34,77,23,67,126,224],
"name" : "Educational week"
},
],
"plan" : {
"weekIds: [1, 6, 23, 74]
}
}
My data models (without mapping):
class Day: Object {
#objc dynamic var id: Int = -1
#objc dynamic var name: String = ""
#objc dynamic var url: String = ""
}
class Week: Object {
var dayIds = List<String>()
#objc dynamic var name: String = ""
#objc dynamic var id: Int = -1
var days: List<Week>? = nil
}
class Plan: Object {
var weekDays = List<String>()
var weeks: List<Week>? = nil
}
Mapping code:
let json: [String: Any] = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
let plans: [Plan] = Mapper<Plan>().mapArray(JSONArray: json["plans"] as! [[String: Any]])
let days: [Day] = Mapper<Day>().mapArray(JSONArray: json["days"] as! [[String: Any]])
let weeks: [Week] = Mapper<Week>().mapArray(JSONArray: json["weeks"] as! [[String: Any]])
So, I need to tell realm that an array weeks belong to plan.weeks and an array days belong to object week.days and related by theirs id’s. How can I do this more simply? Do you have any ideas?
The alternative solution is in-head brute force like this.
for week in weeks {
for dayId in week.dayIds {
for day in days {
if day.id == dayId {
week.days.append(day)
}
}
}
}
for plan in plans {
for week in weeks {
for weekId in plans.weekIds {
if weekId == week.id {
plan.weeks.append(week)
}
}
}
}
I believe that somewhere exist more pure and simple solution :)
Thanks.
Your data structures seem very nested, so you're going to have to do the internal looping. If you want something more swifty, use map and filter here instead of for loops:
let days = weeks.map({
$0.dayIds.map({
$0.filter({
$0.id == dayId
})
})
})
Can someone tell me what I'm doing wrong? I've looked at all the questions on here like from here How to decode a nested JSON struct with Swift Decodable protocol? and I've found one that seems exactly what I need Swift 4 Codable decoding json.
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
struct Location: Codable {
var data: [LocationList]
}
struct LocationList: Codable {
var LocID: Int!
var LocName: String!
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "/getlocationlist")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print(error!)
return
}
guard let data = data else {
print("Data is empty")
return
}
do {
let locList = try JSONDecoder().decode(Location.self, from: data)
print(locList)
} catch let error {
print(error)
}
}
task.resume()
}
The error I am getting is:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath:
[], debugDescription: "Expected to decode Array but found a
dictionary instead.", underlyingError: nil))
Check the outlined structure of your JSON text:
{
"success": true,
"message": "got the locations!",
"data": {
...
}
}
The value for "data" is a JSON object {...}, it is not an array.
And the structure of the object:
{
"LocationList": [
...
]
}
The object has a single entry "LocationList": [...] and its value is an array [...].
You may need one more struct:
struct Location: Codable {
var data: LocationData
}
struct LocationData: Codable {
var LocationList: [LocationItem]
}
struct LocationItem: Codable {
var LocID: Int!
var LocName: String!
}
For testing...
var jsonText = """
{
"success": true,
"message": "got the locations!",
"data": {
"LocationList": [
{
"LocID": 1,
"LocName": "Downtown"
},
{
"LocID": 2,
"LocName": "Uptown"
},
{
"LocID": 3,
"LocName": "Midtown"
}
]
}
}
"""
let data = jsonText.data(using: .utf8)!
do {
let locList = try JSONDecoder().decode(Location.self, from: data)
print(locList)
} catch let error {
print(error)
}
After searching lots of thing internet, I certainly figured out this is the sweetest way to print well formatted json from any object.
let jsonString = object.toJSONString(prettyPrint: true)
print(jsonString as AnyObject)
Apple documentation about JSONEncoder ->
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/