I'm new to Swift, I'm trying to have this JSON
{
"title": "newSurvey",
"pages": [
{
"questions": [
{
"title": "Untitled Question",
"type": "QUESTION_SLIDER",
"content": {
"min": "1",
"max": "10",
"step": "3",
"defaultValue": "5"
}
},
{
"title": "asdasddfdf",
"type": "QUESTION_TEXT",
"choices": ["choice1","choice2"]
}
]
}
]}
I'm suffering from converting subclass to JSON
I thought about divide my code to three objects then add them to the final
string from jsonEncoder
so that's what I did
public class Question : Encodable {
var title : String?
var description: String?
init(_ title: String , _ desc: String) {
self.title = title
self.description = desc
}}
struct Questions : Encodable{
var questions : [Question]
}
class Create : Encodable {
var title : String?
var pages : [Questions] = []
init(_ title:String , _ q : Questions) {
self.title = title
self.pages.append(q)
}
func postData () -> String{
let jsonEncoder = JSONEncoder()
do {
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode([self])
let json = String(data: jsonData ,encoding: .utf8)
print( json!)
return json!
}
catch {
print("Error")
}
return ""
}
class Content :Encodable {
init(_ min : Int , _ max : Int){
self.min = min
self.max = max
}
var min : Int?
var max : Int?
var minLabel : String?
var maxLabel : String?
var defaultValue : Int?
var step : Int?
}
class SliderQuestion :Question {
let TYPE = "SHORT TEXT"
var content = Content(0,2)
init(_ title: String, _ desc: String,_ content : Content ) {
self.content = content
super.init(title, desc)
}
}
I'm sorry for the long code but I want to clarify my idea, is there any way to have the subclass converted to JSON?
Related
I am having an issue dynamically populating the date associated with each section.
The value of grouped is currently derived from grouping the raw JSON data by routeid
You can see that I am currently using a static value for date as a test, while $0.key represent the value of myID dynamically.
How can also add the dynamic value of myDate intead of "test"?
Variables:
var sections = [mySections]()
var structure = [myStructure]()
Currently this is true
sections.map(\.id) == [8,4]
At the end this must be true as well
sections.map(\.date) == [2021-01-20, 2021-01-18]
Decoding:
do {
let decoder = JSONDecoder()
let res = try decoder.decode([myStructure].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0.myID })
_ = grouped.keys.sorted()
sections = grouped.map { mySections(date: "test", id: $0.key, items: $0.value) }
.sorted { $0.id > $1.id }
}
Struct:
struct mySections {
let date : String
let id : Int
var items : [myStructure]
}
struct myStructure: Decodable {
let name: String
let type: String
let myID: Int
let myDate: String
}
Example JSON:
[
{
"name": "Jeff",
"type": "large",
"myID": 8,
"myDate": "2021-01-20"
},
{
"name": "Jessica",
"type": "small",
"myID": 4,
"myDate": "2021-01-18"
},
{
"name": "Beth",
"type": "medium",
"myID": 4,
"myDate": "2021-01-18"
}
]
So, you want it grouped by both id number and date string? I would create a Hashable structure for those two values.
struct Section {
let date: String
let id: Int
var items: [Item]
}
extension Section {
struct Header: Hashable {
let date: String
let id: Int
}
}
struct Item: Decodable {
let name: String
let type: String
let myID: Int
let myDate: String
}
And
let decoder = JSONDecoder()
let res = try decoder.decode([Item].self, from: data)
let sections = Dictionary(grouping: res) { Section.Header(date: $0.myDate, id: $0.myID) }
.sorted { $0.key.id > $1.key.id }
.map { Section(date: $0.key.date, id: $0.key.id, items: $0.value) }
I have JSON as below.
{
"type": "regular",
"data": [
{
"title": "Title 1",
"price_regular": 1.1,
"price_express": 2.2,
},
{
"title": "Title 2",
"price_regular": 1.1,
"price_express": 2.2,
}
]
}
For this I have model as below.
struct MainModel : Codable {
var type : String?
var data : [DataModel]?
}
struct DataModel : Codable {
var title : String?
var price_regular : Float?
var price_express : Float?
var price : Float {
get {
if (type == "regular") { ----> Please check below query for this.
return price_regular ?? 0.0
}
return price_express ?? 0.0
}
}
}
What I am doing in DataModel is create new variable as price. I want to check what data I have for type of main class and based on that I want to get value for Price. Is there way I can achieve this?
Actually I am playing in model. I know I can make this run-time, but I would like to do this in model so that logic is at 1 place only.
I'd do it with a custom init(from decoder:), allowing to pass type from MainModel to DataModel.
struct MainModel : Codable {
var type : String?
var data : [DataModel]?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)
var dataContainer = try container.nestedUnkeyedContainer(forKey: .data)
var dataTemp: [DataModel] = []
while !dataContainer.isAtEnd {
let dataSubcontainer = try dataContainer.nestedContainer(keyedBy: DataModel.DataModelKeys.self)
let title = try dataSubcontainer.decode(String.self, forKey: .title)
let price_regular = try dataSubcontainer.decode(Float.self, forKey: .price_regular)
let price_express = try dataSubcontainer.decode(Float.self, forKey: .price_express)
dataTemp.append(DataModel(title: title, price_regular: price_regular, price_express: price_express, type: type))
}
data = dataTemp
}
}
struct DataModel : Codable {
var title : String?
var price_regular : Float?
var price_express : Float?
var type: String?
enum DataModelKeys: String, CodingKey {
case title
case price_regular
case price_express
}
var price : Float {
get {
if (type == "regular") {
return price_regular ?? 0.0
}
return price_express ?? 0.0
}
}
}
To test it:
let json = """
[{
"type": "regular",
"data": [
{
"title": "Title 1",
"price_regular": 1.1,
"price_express": 1.2,
},
{
"title": "Title 2",
"price_regular": 2.1,
"price_express": 2.2,
}
]
},
{
"type": "irregular",
"data": [
{
"title": "Title 3",
"price_regular": 1.1,
"price_express": 1.2,
},
{
"title": "Title 4",
"price_regular": 2.1,
"price_express": 2.2,
}
]
}]
"""
do {
let model = try JSONDecoder().decode([MainModel].self, from: Data(json.utf8))
print(model)
model.forEach { aMainModel in
print("MainModel type: \(aMainModel.type)")
aMainModel.data?.forEach {
print("Title: \($0.title) - type: \($0.type) - price: \($0.price)")
}
}
} catch {
print("Error: \(error)")
}
Which output:
$>MainModel type: Optional("regular")
$>Title: Optional("Title 1") - type: Optional("regular") - price: 1.1
$>Title: Optional("Title 2") - type: Optional("regular") - price: 2.1
$>MainModel type: Optional("irregular")
$>Title: Optional("Title 3") - type: Optional("irregular") - price: 1.2
$>Title: Optional("Title 4") - type: Optional("irregular") - price: 2.2
With help of #Larme answer, I updated model and made it working.
struct MainModel : Codable {
var type : String?
var data : [DataModel]? {
didSet { // on didSet assigned value of type to type
for i in 0..<(self.data ?? [DataModel]()).count {
self.data![i].type = self.type ?? ""
}
}
}
}
struct DataModel : Codable {
var title : String?
var price_regular : Float?
var price_express : Float?
var type : String? // added new variable
var price : Float? {
get {
if (type == "regular") {
return price_regular ?? 0.0
}
return price_express ?? 0.0
}
}
}
And this helped me of doing what I was looking for...
The following two forms of data were successfully requested.
{
"ride_fare": 1000,
"km": 7
]
}
{
"ride_fare": 1000,
"km": 7,
"options": [ 0, 1, 2]
}
However, I don't know how to request a two-dimensional associative array like the one below.
How can I request it?
{
"ride_fare": 1000,
"km": 7,
"option_fares": [
{
"price": 200,
"name": "立ち寄り",
"id": 1
}
]
}
The code that I wrote:
var options = [Any]()
for option in optionFares {
let params = [
"id" : option.id ?? 0,
"name" : option.name ?? "",
"price" : option.price ?? 0
] as [String : Any]
options.append(params)
}
let faresData = [
"id" : driverOrder.id ?? 0,
"km" : driverOrder.distance ?? 0,
"option_fares" : options,
"ride_fare" : driverOrder.ride_fare ?? 0
] as [String : Any]
First, create a struct that matches the json format you want to request.
struct Params: Codable {
let rideFare, km: Int
let optionFares: [OptionFare]
enum CodingKeys: String, CodingKey {
case rideFare = "ride_fare"
case km
case optionFares = "option_fares"
}
}
struct OptionFare: Codable {
let price: Int
let name: String
let id: Int
}
And you must create a request parameter in Moya's task.
import Moya
extension APITarget: TargetType {
var task: Task {
case .yourCaseName(let price, let name, let id, let rideFare, let km):
let encoder: JSONEncoder = JSONEncoder()
let optionFareData: [OptionFare] = []
optionFareData.append(OptionFare(price, name, id))
let paramsData = Params(rideFare, km, optionFareData)
let jsonData: Data = try! encoder.encode(paramsData)
return .requestData(jsonData)
}
}
}
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)
}
}
}
This question already has answers here:
Sorting a Swift array by ordering from another array
(10 answers)
Closed 3 years ago.
In my json result, there is odds array. And inside of it, there will be 8 types of bet. And i want to sort the array by bet type as i want to show. Eg. first "name": "3Way Result", second another one, third "over/under" etc..
Here is my json result from server.
{
"success": true,
"result": [
{
"league_id": 5,
"localTeam": {"data": {}},
"visitorTeam": {"data": {}},
"scores": {},
"time": {"starting_at": {},},
"league": {"data": {"coverage": {}}},
"odds": [
{
"id": 12,
"name": "Over/Under",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "Over",
"value": "2.00",
"extra": null,
"probability": "50%",
"dp3": "2.000",
"american": 100,
"handicap": null,
"total": "2.5",
"winning": null,
"stop": false,
"bookmaker_event_id": 84922729,
},
{
"label": "Under",
"value": "1.80",
"probability": "55.56%",
"dp3": "1.800",
}
]
}
}
]
}
},
{
"id": 1,
"name": "3Way Result",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "1",
"value": "2.10",
"extra": null,
"probability": "47.62%",
"dp3": "2.100",
"american": 110,
},
{
"label": "X",
"value": "3.30",
"extra": null,
"probability": "30.3%",
"dp3": "3.300",
"american": 229,
},
{
"label": "2",
"value": "3.60",
}
]
}
}
]
}
},
{
"id": 975909,
"name": "Correct Score",
"suspended": false,
"bookmaker": {
"data": [
{
"id": 2,
"name": "bet365",
"odds": {
"data": [
{
"label": "1:0",
"value": "7.50",
"extra": null,
"probability": "13.33%",
"dp3": "7.500",
"american": 650,
"factional": null,
"handicap": null,
"total": null,
"winning": null,
"stop": false,
"bookmaker_event_id": 84922729,
"last_update": {
"date": "2020-02-20 10:59:06.746514",
"timezone_type": 3,
"timezone": "UTC"
}
},
{
"label": "2:0",
"value": "10.00",
},
]
}
}
]
}
},
],
"tipsters": 2
}
]
}
so it is not alphabetically, datetime or anytype i could access. How could i do ?
Update: I have added model.
struct BetterMatchResults: APIModel, Codable {
var success: Bool?
var result: [BetterMatch]?
}
struct BetterMatch: APIModel, Codable {
var id,_id: String?
var localTeam, visitorTeam: BetterTeam?
var spId, league_id, seasonID: Int?
var winningOddsCalculated: Bool?
var time: BetterTime?
var league: BetterLeague?
var createdAt, updatedAt: String?
var odds: [BetterOdd]!
var resultID: String?
var tipsters: Int?
var stats_url: String?
}
struct BetterLeague : APIModel, Codable {
var data: LeagueData?
}
struct LeagueData : APIModel, Codable{
var id: Int?
var active: Bool?
//var legacyID, countryID: Int?
var logo_path: String?
var name: String?
//var isCup: Bool?
}
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var suspended: Bool?
var bookmaker: BetterBookmaker?
}
// MARK: - Bookmaker
struct BetterBookmaker : APIModel, Codable {
var data: [BetterBookmakerDatum]?
}
// MARK: - BookmakerDatum
struct BetterBookmakerDatum : APIModel, Codable {
var id: Int?
var name: String?
var odds: BetterOdds?
}
// MARK: - Odds
struct BetterOdds : APIModel, Codable {
var data: [BetterOddsDatum]?
}
class BetterOddsDatum: APIModel , Codable {
var label: String?
//var extra: NSNull?
//var probability, dp3: String?
var american: Int?
//var factional, handicap: NSNull?
var total: String?
var winning: Bool?
var stop: Bool?
var bookmakerEventID: Int?
//private var odd: Double
public var value: String?
init() {
}
}
If I understand your question correctly you want to be able sort the data based on betting type but the value of betting types are not sortable if used as a String variable. The solution would be to converting them into enum types with raw values and then sorting the array based on those raw values. Here is an example:
// Create a BetType for your datas
enum BetType: Int {
case overUnder = 0
case threeWayResult = 1 // 3WayResult
...
}
// Update your BetterOdd
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var betType: BetType = .overUnder // or put your default value here
var suspended: Bool?
var bookmaker: BetterBookmaker?
}
// Loop your BetterMatch property's odds value after fetching datas.
for i in 0..<betterMatch.odds {
if betterMatch.odds[i].name == "over/under" {
betterMatch.odds[i].betType = .overUnder
}
... // Do the same for other types as well in else if blocks
}
Another alternative would be to add a function for getting the type in BetterOdd
struct BetterOdd ... {
... // Your properties
func getBetType() -> BetType {
if name == "over/under" {
return .overUnder
} else if name == "3WayResult" {
return .threeWayResult
}
... // Other cases
}
}
Finnaly for sorting you can do:
let result = betterMatch.odds.sorted({ $0.betType.rawValue > $1.betType.rawValue })
// or if you used the function solution
let result = betterMatch.odds.sorted({ $0.getBetType().rawValue > $1.getBetType().rawValue })
Since you are using a Codable approach you might need to loop the array and set the betType values based on name values.
i changed model
struct BetterOdd : APIModel, Codable {
var id: Int?
var name: String?
var suspended: Bool?
var bookmaker: BetterBookmaker?
//var betType: BetType = .threeWayResult
enum BetType:Int, APIModel, Codable {
case threeWayResult = 7
case overUnder = 6
case doubleChance = 5
case bothTeamsToScore = 4
case threeWayResultFirstHalf = 3
case threeWayResultSecondHalf = 2
case correctScore = 1
case hTfTdouble = 0
}
//
func getBetType() -> BetType {
if name == "3Way Result" {
return .threeWayResult
} else if name == "Over/Under" {
return .overUnder
} else if name == "Double Chance" {
return .doubleChance
} else if name == "Both Teams To Score" {
return .bothTeamsToScore
} else if name == "3Way Result 1st Half" {
return .threeWayResultFirstHalf
} else if name == "3Way Result 2nd Half"{
return .threeWayResultSecondHalf
} else if name == "Correct Score"{
return .correctScore
} else if name == "HF/FT Double" {
return .hTfTdouble
} else {
return .correctScore
}
}
}
and then :
let matchOddsArray = match.odds!
let result = matchOddsArray.sorted(by: { $0.betType.rawValue > $1.betType.rawValue})
let bet = result[indexPath.row]
works perfect.
First of all, mark result in BetterMatchResults and odds in BetterMatch as var, i.e.
struct BetterMatchResults: Codable {
let success: Bool
var result: [BetterMatch] //here....
}
struct BetterMatch: Codable {
var odds: [BetterOdd] //here....
//rest of the parameters...
}
Next, while parsing your JSON data, use map(_:) and sorted(by:) to modify the result and odds in the betterMatchResults, i.e.
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
var betterMatchResults = try JSONDecoder().decode(BetterMatchResults.self, from: data)
betterMatchResults.result = betterMatchResults.result.map {(betterMatch) in
var match = betterMatch
match.odds = match.odds.sorted(by: { $0.name < $1.name })
return match
}
//Test the response...
print(betterMatchResults.result.map({ $0.odds.map({ $0.name }) })) //[["3Way Result", "Correct Score", "Over/Under"]]
} catch {
print(error)
}