Swift: Using Integer as field in struct - swift

I am making a call to an API in Swift and I am using the JSONDecoder. The API call returns categories and ids, the problem I am having, is that it stores the subcategories under the key of the main categories id.
The returned data looks like this:
categories = {
data = {
main = (
{
id = 39;
name = Electronics;
}
)
sub = {
39 =
(
{
id = 49;
name = TV;
}
)
}
}
}
The problem is that 39. In my structure I can't have let 39:[Category] or else I get the error:
Expected patten.
Is there any workaround for this?

You can not use 39 as a parameter name, but you can use this
let category_39: [Category]
private enum CodingKeys : String, CodingKey {
case category_39 = "39"
}
But i think this structure will be difficult, if a new Category added from server side there is no preDefined category id
i suggest to use the following model
struct Model: Codable {
let main: [Category]
let sub: [String: [Category]]
}
struct Category: Codable {
let id: Int
let name: String
}
and then get SubCategories by category id
let subCategories = model.sub[String(id)] // String(id) --> "39"

Related

Cannot assign to a property of an instance that get returned from a get-only subscript

I return an instance from a get-only subscript, but I cannot assign to a property of the returned instance
struct Student {
var id: String
var attendance = 0
var absence = 0
}
class StudentStore {
var students = [Student]()
subscript(id: String) -> Student? {
students.first(where: { $0.id == id } )
}
}
var studentStore = StudentStore()
var newStudent = Student(id: "2014901001")
studentStore.students.append(newStudent)
studentStore["2014901001"]?.attendance = 1 // error: Cannot assign to property: subscript is get-only
The subscription doesn't have a setter. What you have is the implicit getter which is a short form of
subscript(id: String) -> Student? {
get {
students.first(where: { $0.id == id } )
}
}
But it's impossible to update a value type item in an array with key subscription anyway.
An alternative is to add an update function to the store which takes the id, a keyPath and a value
func updateStudent<Value>(withID id: String, keyPath: WritableKeyPath<Student,Value>, value: Value) {
guard let index = students.firstIndex(where: {$0.id == id}) else { return }
students[index][keyPath: keyPath] = value
}
Getting the index is mandatory to be able to modify the item in the array directly.
Then you can replace
studentStore["2014901001"]?.attendance = 1
with
studentStore.updateStudent(withID: "2014901001", keyPath: \.attendance, value:1)
When you do
studentStore["2014901001"]?.attendance = 1
You are trying to set one of the Student objects in the students array, aren't you?
However, the subscript getter doesn't return (a reference to) one of the objects in the students array. Due to the value semantics of the Student struct, you return a copy of the Student object that is in the students array. You can't achieve what you want if you just set the attendance of that copy. Therefore, the Swift compiler compiles your code to something like this:
let copy = studentStore["2014901001"]
copy?.attendance = 1
studentStore["2014901001"] = copy
You can see that it sets the setter with the modified copy again. But your subscript does not have a setter, which is where the error comes from.
To solve this, you can make Student a class, which has reference semantics. This enables you to return the Student object that is in the students array, allowing the caller of the subscript to set their attendance.
class Student {
var id: String
var attendance = 0
var absence = 0
init(id: String, attendance: Int = 0, absence: Int = 0) {
self.id = id
self.attendance = attendance
self.absence = absence
}
}
Alternatively, you can add a setter. However, that would allow nonsense like this:
studentStore["1"] = Student(id: "2")
You need to decide what you want to happen here.
Error show exactly what you have to change. Because subcript will return something likes:
let updateStudent = studentStore["studentStore"]
You can not change with let
So update with this:
if var updateStudent = studentStore["studentStore"] {
updateStudent.attendance = 1
}

Swift 4 - CodableFirebase and nested child with UID

I am trying to use CodableFirebase to parse a snapshot.
My Json is like this :
snapshot.value = Optional({
Box = {
Outbox = {
"-LpdzdzdzdzdDs7" = {
DateInvoice = "2019-09-23 10:41:20 +0000";
TrailOrHike = Hike;
Number_of_child = 2;
Children = {
0VZrgdYvV4hyhhyhhy4tyElASvR83 = {
DateVenue = "2019-09-23 10:43:44 +0000";
Age = 3;
"Avatar" = "gs://oooo.appspot.com/Avatar/0V3R4tyElASvR83.png";
};
wQpyIhyhyhyhyh1CI6YBfc3yppX2 = {
"Avatar" = "http://graph.facebook.com/00000/picture?type=large&height=200&width=200";
};
};
In my Outbox, I can have UID (of families)
And in each UID, I can have 0,1 or x children, with data inside.
I would like to parse it correctly but I don't manage to do it with dynamic UID (like -LpdzdzdzdzdDs7), and inside this dynamic UID ,there is more data to parse (Age,DateVenue,Avatar)..
I have no problem to parse other data, but when it's nested I am lost .
My Codable for the rest is simple :
struct CampProfile: Codable {
struct Family : Codable {
let DateVenue : String?
enum CodingKeys : String, CodingKey {
case DateVenue
}
}
let Camp_Name: String
let Camp_Adress: String
}
let model = try FirebaseDecoder().decode(CampProfile.self, from: value)
print(model)
let model2 = try FirebaseDecoder().decode(CampProfile.Family.self, from: value)
print(model2) . //NOTWORKING

How to use enums for class functions

I'm working on a project and I have created a class to handle the json response to convert it to modal class and change it back to json request with updated data if needed.
Here in the class I'm getting and saving values from and into dictionary. I need to create an enum for the dictionary keys so that there should be less chance for error for complex key formats.
I even tried using like
enum Fields {
case Name
case Email
}
but Fields.Email return Fields object
if I use a protocol of a variable like
protocol someProtocol {
var name: String { get }
}
extension someProtocol {
var name:String {
return String(describing: self)
}
}
and then extend the enum Fields:someProtocol
then I can use it like Fields.name.name or Fields.email.name
But My client will not approve this I want to create an enum so that I can access the string directly like for name I want key "Name" and I should get it liek "Fields.name" or ".name"
So here I have two objectives
first it that I need to create something that can be accessed through class function
second it should be common so that I can use it with multiple classes
third I can access it with less operators
—
class PersonService {
class Update {
var name = ""
var email = ""
var personId = 0
func createDataFrom(dic:[AnyHashable : Any]) -> Update {
let update = Update()
update.name = dictionary["Name"]
update.email = dictionary["Email"]
update.personId = dictionary["Id"]
return update
}
func createDataTo() -> [AnyHashable:Any] {
var ret = [AnyHashable : Any]()
ret["Name"] = name
ret["Email"] = email
ret["Id"] = personId
return ret
}
}
}
Something like that?
enum Fields: String {
case Name = "Name"
case Email = "Email"
}
Print(Fields.Name.rawValue)
result: "Name"
Or
struct Constants {
static let name = "Name"
static let email = "Email"
}
print(Constants.name)
result: "Name"

How can i get sorted data according to order key using JSONDecoder?

I am getting the response from API and going to parse response using 'JSONDecoder' and able to parse but I want the sorted array of 'FieldModel' according to 'order' key in "content" object of "ContentModel" auto when I decode using JSONDecoder?
My response from API:
{content = (
{
fieldName = \"$.alcohol.beer\";
label = Beer;
order = 2;
},
{
fieldName = \"$.alcohol.wine\";
label = Wine;
order = 1;
},
{
fieldName = \"$.alcohol.any\”;
label = Wine;
order = 3;
}
);}
My models:
struct ContentModel: Codable {
var content: [FieldModel]?
}
struct FieldModel: Codable {
var fieldName: String?
var order: Int?
var label: String?
}
Code for decode the data:
let myFinalData = try JSONDecoder().decode(ContentModel.self, from: jsonData)
How can I get content of ContentModel will be sorted according to order key after decode using above code?
You can achieve your goals by creating a custom init(from decoder) method for your ContentModel struct, where you use the order property of the FieldModel elements to assign the FieldModels to their respective place in the content array.
I'd also suggest making all field of your structs immutable and non-optional unless you have a good reason to do otherwise.
struct ContentModel: Codable {
let content: [FieldModel]
private enum CodingKeys: String, CodingKey {
case content
}
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let content = try container.decode([FieldModel].self, forKey: .content)
var orderedContent = content
for element in content {
orderedContent[element.order-1] = element
}
self.content = orderedContent
}
}
struct FieldModel: Codable {
let fieldName: String
let label: String
let order: Int
}

Want to create particular property value array from array

Having array of employee object, called employees and now I want to create another array called filteremployees which is having only id and date of birth value.
Using
let filteremployees = employee.map({ $0.id})
I can get array which contains only id value but i want to have id as well dateOfBirth
class Employee {
var id: Int
var firstName: String
var lastName: String
var dateOfBirth: NSDate?
init(id: Int, firstName: String, lastName: String) {
self.id = id
self.firstName = firstName
self.lastName = lastName
}
}
Try this:
let employees : [Employee] = ...
let list: [(Int, NSDate?)] = employees.map { ($0.id, $0.dateOfBirth) }
You must explicitly declare the type of list otherwise you get this error from the compiler
Type of expression is ambiguous without more context.
Tested with Xcode 7 Playground and Swift 2.0.
Hope this helps.
You could try using the same map method and returning a tuple of your expected values:
let filter employees: [(Int, NSDate?)] = employee.map({ ($0.id, $0.dateOfBirth) })
Alternatively, and I think this is a better solution, create a new value type and create that with only your required values
struct FilteredEmployee {
let id: String
let dateOfBirth: NSDate?
init(employee: Employee) {
id = employee.id
dateOfBirth = employee.dateOfBirth
}
}
And then you can map the initialiser over the array
let filteremployees = employee.map { FilteredEmployee($0) }