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
})
})
})
Related
I'm trying something but I could not achieve the result I want and I'm asking for your help because I've struggled a lot.
I have a list of document and I want to display each of them using Sections, so for documents from June to make a section Like June 2022, for documents from August August 2022. Basically, I want to display all the documents, but each document to be displayed in the correct Month by using a section.
I'll share bellow what I tried.
The screen :
struct DocumentsListScreen: View {
#ObservedObject var viewModel : DocumentsViewModel
init(viewModel: DocumentsViewModel)
{
self.viewModel = viewModel
viewModel.fetchDocuments()
}
var body: some View {
if viewModel.presentingLoader {
LoaderView()
}
else {
List {
ForEach(viewModel.getUniqueDates(), id: \.self) { date in
Section(header: Text(viewModel.prettyDate(date))
.foregroundColor(.red)) {
ForEach(viewModel.arrayByDate(), id: \.idid) { doc in
VStack {
Text(doc.category)
Text(doc.title)
Text(doc.isPersonDoc)
Text(doc.isPersonDoc)
Text(doc.date)
}
}
}
}
}
}
}
}
The View Model :
class DocumentsViewModel : ObservableObject {
#Injected private var documentsUseCase: DocumentsUseCaseProtocol
#Published var documentsAssetsData: [DocumentsAssetsData] = []
#Published public var presentingLoader = true
var newArray: [String] = []
func fetchDocuments() {
documentsUseCase.performDocumentsRequest { [weak self] result in
switch result {
case .success(let response):
self?.documentsAssetsData = response
self?.presentingLoader = false
case .failure(_):
break
}
}
}
func prettyDate(_ date: String) -> String {
let dateFormatterGet = DateFormatter()
dateFormatterGet.dateFormat = "yyy.MM.dd"
let dateFormatterPrint = DateFormatter()
dateFormatterPrint.dateFormat = "MMMM yyyy"
let formattedDate: Date? = dateFormatterGet.date(from: date)
guard let date = formattedDate else {
return ""
}
return dateFormatterPrint.string(from: date)
}
func getUniqueDates() -> [String]{
for item in documentsAssetsData {
newArray.append(prettyDate(item.date))
}
let uniqueDates = newArray.compactMap { $0 }
let datesArray = Array(Set(uniqueDates))
let formatter = DateFormatter()
let output = datesArray
.map { (string: $0, date: formatter.date(from: $0)) }
.sorted {
guard let date1 = $0.date else { return true }
guard let date2 = $1.date else { return false }
return date1 > date2
}.map { $0.string }
return output
}
func arrayByDate() -> [DocumentsAssetsData]{
let items = documentsAssetsData.filter {prettyDate($0.date) == getUniqueDates().first}
return items
}
}
The json file that correspond to the Model I'm using, maybe it will help.
[
{
"idid": "1",
"title": "Document nr.4",
"category" : "Tax documents",
"date": "2022-08-08",
"isPersonDoc": false,
"productId": "4",
"personName": "Marie-Ange Schramm",
"productName": "Product Name 4",
"bpNr": 121324600
},
{
"idid": "2",
"title": "Document nr.5",
"category" : "Tax documents",
"date": "2022-08-09",
"isPersonDoc": false,
"productId": "5",
"personName": "Alessia Ackermann",
"productName": "Product Name 5",
"bpNr": 121324700
}
]
Also, another problem is that the title of the section is missing ( I don't know why ), but I tried to debug it and there are datas there .
Debugging the output for getUniqueDates()
Printing description of output:
▿ 4 elements
- 0 : "Juli 2022"
- 1 : "Juni 2022"
- 2 : "August 2022"
- 3 : "Mai 2022"
I'll share here a picture with the actual state of the List. Check image
Thanks a lot !
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'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?
I am trying to do some complex filtering in my app and I am to a point where I don't know what to do next. My data consistes of an array of dictionaries where the values in each of the dictionaries can be String, Int or [String].
let person1: [String : Any] = ["first_name" : "John",
"last_name" : "Smith",
"age" : 21,
"skills" : ["C#", "Java", "Swift"]]
let person2: [String : Any] = ["first_name" : "Kim",
"last_name" : "Smith",
"age" : 28,
"skills" : ["Java", "Swift"]]
let person3: [String : Any] = ["first_name" : "Kate",
"last_name" : "Bell",
"age" : 24,
"skills" : ["C#"]]
var people = [person1, person2, person3]
I let the user choose how to filter this data and create a dictionary of filter criteria. This dictionary can have any number of keys and values.
let filters: [String : [Any]] = ["age" : [28, 24],
"skills" : ["Java", "Swift"]]
In this example I want to show persons who are age 28 or 24 and have a skills of Java or Swift, which would be person2
Here is what I have so far but it only works with Int values:
for (key, values) in filters {
var filteredItems = people.filter {
var match = false
for filterValue in values {
if $0[key] as! Int == filterValue as! Int {
match = true
break
}
else {
match = false
}
}
return match
}
people = filteredItems
}
Here's how I would do this:
struct Person {
let firstName: String
let lastName: String
let age: Int
let skills: [String]
enum Filter {
enum FilterType<T: Hashable> {
case one(of: [T])
case all(of: [T])
// Match against a property that's a single value
func matches(_ value: T) -> Bool {
switch self {
case .one(let filterValues): return filterValues.contains(value)
case .all(let filterValues): return filterValues.count == 1 && filterValues[0] == value
}
}
// Match against a property that's a list of values
func matches(_ values: [T]) -> Bool {
switch self {
case .one(let filterValues): return !Set(filterValues).intersection(values).isEmpty
case .all(let filterValues): return Set(filterValues).isSuperset(of: values)
}
}
}
case age(is: FilterType<Int>)
case skills(is: FilterType<String>)
func matches(_ p: Person) -> Bool {
switch self {
case .age(let filterValues): return filterValues.matches(p.age)
case .skills(let filterValues): return filterValues.matches(p.skills)
}
}
}
}
extension Array where Element == Person.Filter {
func atLeastOneMatch(_ p: Person) -> Bool {
self.contains(where: { $0.matches(p) })
}
func matchesAll(_ p: Person) -> Bool {
self.allSatisfy { $0.matches(p) }
}
}
let people = [
Person(
firstName: "John",
lastName : "Smith",
age: 21,
skills: ["C#", "Java", "Swift"]
),
Person(
firstName: "Kim",
lastName : "Smith",
age: 28,
skills: ["Java", "Swift"]
),
Person(
firstName: "Kate",
lastName: "Bell",
age: 24,
skills: ["C#"]
),
]
let filters: [Person.Filter] = [
.age(is: .one(of: [28, 24])),
.skills(is: .one(of: ["Java", "Swift"])),
]
let peopleWhoMatchAllFilters = people.filter(filters.matchesAll)
print(peopleWhoMatchAllFilters)
let peopleWhoMatchAtLeastOneFilter = people.filter(filters.atLeastOneMatch)
print(peopleWhoMatchAtLeastOneFilter)
I've extended the filtering capability to be able to specify wether all values of a filter should be matched (e.g. a person must know Java AND Swift AND C#) or at least one (e.g. a person must know AT LEAST Java OR Swift OR C#)
I am trying to use firebase for iOS and I am in front of a problem.
I am using the following JSON tree :
{
"expressions" : {
"expr0" : {
"id" : 0,
"text" : "Text0"
},
"expr1" : {
"id" : 1,
"text" : "Text1"
},
"expr2" : {
"id" : 2,
"text" : "Text2"
},
"expr3" : {
"id" : 3,
"text" : "Text3"
}
},
"levels" : {
"level0" : {
"coverImage" : "lvl0",
"expressions" : {
"expr0" : true,
"expr2" : true
},
"id" : 0,
"title" : "Level0"
},
"level1" : {
"coverImage" : "lvl1",
"expressions" : {
"expr0" : true,
"expr1" : true,
"expr3" : true
},
"id" : 1,
"title" : "Level1"
},
"level2" : {
"coverImage" : "lvl2",
"expressions" : {
"expr1" : true,
"expr2" : true,
"expr3" : true
},
"id" : 2,
"title" : "Level2"
},
"level3" : {
"coverImage" : "lvl3",
"expressions" : {
"expr3" : true
},
"id" : 3,
"title" : "Level3"
}
}
}
I have two kind of objects, expressions and levels. Here are the data models of those 2 objects :
struct Level {
let id : Int
let coverImage : String
let title : String
let expressions : [Expression]
}
struct Expression {
let id : Int
let expression : String
}
I also have a DataService singleton to provide me Firebase Database References :
import UIKit
import FirebaseDatabase
class DataService {
private static let _instance = DataService()
static var instance: DataService {
return _instance
}
var mainRef: FIRDatabaseReference {
return FIRDatabase.database().reference()
}
var levelsRef: FIRDatabaseReference {
return mainRef.child("levels")
}
var expressionsRef: FIRDatabaseReference {
return mainRef.child("expressions")
}
}
I want to store on a Levels array the level with their corresponded expressions and display this array with a collectionView. Based on the JSON file, the final result must be :
levelsArr[Level(id: 0, coverImage: "lvl0", title: "Level0", expressions: [Expression(id: 0, expression: "Text0"), Expression(id: 2, expression: "Text2")]), Level(id: 1, coverImage: "lvl1", title: "Level1", expressions: [Expression(id: 0, expression: "Text0"), Expression(id: 1, expression: "Text1"), Expression(id: 3, expression: "Text3")]), Level(id: 2, coverImage: "lvl2", title: "Level2", expressions: [Expression(id: 1, expression: "Text1"), Expression(id: 3, expression: "Text3"), Expression(id: 2, expression: "Text2")]), Level(id: 3, coverImage: "lvl3", title: "Level3", expressions: [Expression(id: 3, expression: "Text3")])]
Here is the following function I have for the moment :
private var levelsArr = [Level]()
func fetchLevels() {
DataService.instance.levelsRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let levels = snapshot.value as? Dictionary<String, AnyObject> {
for(_, valueLvl) in levels {
if let dictLvl = valueLvl as? Dictionary<String, AnyObject> {
if let id = dictLvl["id"] as? Int, let coverImage = dictLvl["coverImage"] as? String, let title = dictLvl["title"] as? String {
var expressionsArr = [Expression]()
if let expr = dictLvl["expressions"] as? Dictionary<String, AnyObject> {
for(keyLvlExp, _) in expr {
DataService.instance.expressionsRef.observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
if let expressions = snapshot.value as? Dictionary<String, AnyObject> {
for (keyExpr, valueExpr) in expressions {
if keyLvlExp == keyExpr {
if let dictExpr = valueExpr as? Dictionary<String, AnyObject> {
if let id = dictExpr["id"] as? Int, let text = dictExpr["text"] as? String{
let expression = Expression(id: id, expression: text)
expressionsArr.append(expression)
}
}
}
}
}
let level = Level(id: id, coverImage: coverImage, title: title, expressions: expressionsArr)
self.levelsArr.append(level)
print(level)
})
}
}
}
}
}
}
})
DispatchQueue.main.async{
self.collectionView?.reloadData()
}
}
The function fetchLevels() is called in the viewDidLoad().
The result of the print(snapshot) is :
Snap (expressions) {
expr0 = {
id = 0;
text = Text0;
};
expr1 = {
id = 1;
text = Text1;
};
expr2 = {
id = 2;
text = Text2;
};
expr3 = {
id = 3;
text = Text3;
};
}
Thank you.