Swift 4: struct in struct - swift

I have a problem with creating a struct.
My struct:
public struct Device: Codable {
let data: DeviceData
let meta: Meta?
}
public struct DeviceData: Codable {
let deviceID: String?
let type: String?
let attributes: Attributes?
private enum CodingKeys: String, CodingKey {
case deviceID = "id"
case type
case attributes
}
}
public struct Attributes: Codable {
let name: String?
let asdf: String?
let payload: Payload?
}
public struct Payload: Codable {
let example: String?
}
public struct Meta: Codable {
let currentPage: Int?
let nextPage: Int?
let deviceID: [String]?
}
When I now would like to create an element of this struct with:
var exampleData = Device(
data: DeviceData(
type: "messages",
attributes: Attributes(
name: "Hello World",
asdf: "This is my message",
payload: Payload(
example: "World"
)
)
),
meta: Meta(
deviceID: ["asfd-asdf-asdf-asdf-asdfcasdf"]
)
)
I will get an error. Cannot specify this error in detail, because when I delete the "meta" element, because it's optional, another error occures... The error message for this specific code is:
Extra argument 'meta' in call
I hope that someone can help me.

You forgot the deviceID: named arguments of your call to DeviceData.init(deviceID:type:attributes:), and you also forgot the currentPage and nextPage named arguments to Meta.init(currentPage:nextPage:deviceID).
Here's a sample that compiles:
var exampleData = Device(
data: DeviceData(
deviceID: "someID",
type: "messages",
attributes: Attributes(
name: "Hello World",
asdf: "This is my message",
payload: Payload(
example: "World"
)
)
),
meta: Meta(
currentPage: 123,
nextPage: 456,
deviceID: ["asfd-asdf-asdf-asdf-asdfcasdf"]
)
)

You have omitted arguments to both your DeviceData and Meta initializers. In a comment on another answer you ask:
do I have to add them and set them to nil, even if they are optional? maybe that's my problem!
You can do that, e.g. something like:
meta: Meta(currentPage: nil,
nextPage: nil,
deviceID: ["asfd-asdf-asdf-asdf-asdfcasdf"]
)
Alternatively you can write your own initializer rather than rely on the default memberwise one, and supply default values there instead of on each call e.g. something like:
init(currentPage : Int? = nil, nextPage : Int? = nil, deviceID : [String]? = nil)
{
self.currentPage = currentPage
self.nextPage = nextPage
self.deviceID = deviceID
}
Your original call, which omitted currentPage and nextPage, would then be valid and would set those two to nil.
HTH

Related

How to disable default nil-initialization of Optional

Let's say I have these two structures:
struct DataOne {
let id: String
// ...
}
struct DataTwo {
let id: String
// ...
}
And there is a separate conversion function:
extension DataOne {
func convertToDataTwo() -> DataTwo {
.init(
id: self.id,
// ...
)
}
}
At some point, the var name: String? field is added to both structures:
struct DataOne {
let id: String
var name: String?
// ...
}
struct DataTwo {
let id: String
var name: String?
// ...
}
But the assembly does not swear because the field is optional.
And when converting, the name field is lost.
Is it possible to disable the autofill of the option or to call the warnings?
I tried to find such a rule in SwiftLint, but I didn't find it.
If your var's type is written T? with no default value, and Swift synthesizes a memberwise init for your type, then the synthesized init uses a default value of nil for that var.
However, if your var's type is written Optional<T> with no default value, then the synthesized init does not use a default value.
So write this instead:
struct DataOne {
let id: String
var name: Optional<String>
// ...
}
struct DataTwo {
let id: String
var name: Optional<String>
// ...
}
Or write out your init instead of letting the compiler synthesize it:
struct DataOne {
let id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
struct DataTwo {
let id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
You can use Xcode's Editor > Refactor > Generate Memberwise Initializer command to write most of the init for you, then delete the = nil default:

Argument passed to call that takes no arguments when mapping firebase fetch call

I get this error when mapping the results of a firebase query into my model.
"Argument passed to call that takes no arguments" exactly in the return conversations point. why??????
I sincerely don't know why, probably something simple, hopefully Peter Friese will see this post...
#Published var chats = [Conversations]()
private let db = Firestore.firestore()
private let user = Auth.auth().currentUser
func getFilteredConversations(query: String) {
if (user != nil) {
db.collection("conversations").whereField("users", arrayContains: user!.displayName).addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("no conversations found")
return
}
//mapping
self.chats = documents.map{(queryDocumentSnapshot) -> Conversations in
let data = queryDocumentSnapshot.data()
let docId = queryDocumentSnapshot.documentID
let users = data["users"] as? [String] ?? [""]
let msgs = data["messages"] as? [Message] ?? []
let unreadmsg = data["hasUnreadMessage"] as? Bool ?? false
//ERROR HERE!!!!! Argument passed to call that takes no arguments
return Conversations(id: docId, users: users, messages: msgs, hasUnreadMessage: unreadmsg)
}
}
}
}
class Conversations: Decodable, Identifiable, ObservableObject {
//var id: UUID { person.id }
var id: String = ""
var users: [String] = [""]
var messages: [Message] = []
var hasUnreadMessage : Bool = false
}
//ERROR here as well saying it's not decodable!
struct Message: Decodable, Identifiable {
enum MessageType {
case Sent, Received
}
let id = UUID()
let date: Date
let text: String
let type: MessageType?
init(_ text: String, type: MessageType, date: Date) {
self.date = date
self.text = text
self.type = type
}
init(_ text: String, type: MessageType) {
self.init(text, type: type, date: Date())
}
}
The problem is that Conversations is defined as a class with default values and no initializer. So, the only way to initialize it is to use Conversations() (thus the no arguments error). Instead, Conversations should almost certainly be a struct and not be an ObservableObject (it doesn't have any #Published properties anyway and is already getting broadcasted by chats which is #Published. Nesting ObservableObjects is fraught with other issues anyway, so it's best to avoid it):
struct Conversations: Decodable, Identifiable {
var id: String = ""
var users: [String] = [""]
var messages: [Message] = []
var hasUnreadMessage : Bool = false
}
To fix your Codable issue, mark MessageType as Codable then use CodingKeys to tell Swift that there won't be an id to decode:
struct Message: Decodable, Identifiable {
enum CodingKeys : CodingKey {
case date, text, type
}
enum MessageType : Codable {
case Sent, Received
}
let id = UUID()
let date: Date
let text: String
let type: MessageType?
init(_ text: String, type: MessageType, date: Date) {
self.date = date
self.text = text
self.type = type
}
init(_ text: String, type: MessageType) {
self.init(text, type: type, date: Date())
}
}
(It's unclear to me whether there will always be a date to decode -- if you run into issues with this, it's probably best to start a new question about it since it's tangential from the original issue anyway)

Swift ui codable struct with empty fields

I have the following structs:
struct ResponseToken: Identifiable, Codable, Hashable {
var id: UUID { return UUID() }
let access_token: String
enum CodingKeys: String, CodingKey {
case access_token
}
}
struct ResponseUserInfo: Identifiable, Codable, Hashable {
var id: UUID { return UUID() }
let login, avatar_url, html_url, created_at, updated_at: String
let public_repos, public_gists, followers, following: Int
enum CodingKeys: String, CodingKey {
case login, avatar_url, html_url, public_repos, public_gists, followers, following, created_at, updated_at
}
}
I would like to avoid doing this every time to declare empty objs:
var token: ResponseToken = ResponseToken(access_token: "")
var userInfo: ResponseUserInfo =
ResponseUserInfo(login: "", avatar_url: "", html_url: "", created_at: "", updated_at: "", public_repos: 0, public_gists: 0, followers: 0, following: 0)
The result I would like to have is something like this:
var token: ResponseToken = ResponseToken()
var userInfo: ResponseUserInfo = ResponseUserInfo()
Can you give me a hand?
A possible solution is a static property which creates an empty struct for example
struct ResponseToken: Identifiable, Codable, Hashable {
let id = UUID()
let accessToken: String
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
}
static let empty = ResponseToken(accessToken: "")
}
and use it
let token = ResponseToken.empty
Notes:
The computed property to return an UUID is pointless, declare a constant.
If you are specifying CodingKeys anyway, use them also to convert the snake_case keys.

Exclude CodingKeys that doesn't need to be altered?

Say I have a struct User model which has many properties in it.
struct User: Codable {
let firstName: String
let lastName: String
// many more properties...
}
As you can see above it conforms to Codable. Imagine if the lastName property is should be encoded/decoded as secondName and I would like to keep it as lastName at my end, I need to add the CodingKeys to the User model.
struct User: Codable {
//...
private enum CodingKeys: String, CodingKey {
case firstName
case lastName = "secondName"
// all the other cases...
}
}
Is there any possible way to avoid including all the cases in CodingKeys that have the same value as rawValue like the firstName in the above example (Feels redundant)? I know if I avoid the cases in CodingKeys it won't be included while decoding/encoding. But, is there a way I could override this behaviour?
There is a codable way, but the benefit is questionable.
Create a generic CodingKey
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue; self.intValue = nil }
init?(intValue: Int) { self.stringValue = String(intValue); self.intValue = intValue }
}
and add a custom keyDecodingStrategy
struct User: Codable {
let firstName: String
let lastName: String
let age : Int
}
let jsonString = """
{"firstName":"John", "secondName":"Doe", "age": 30}
"""
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ keyPath -> CodingKey in
let key = keyPath.last!
return key.stringValue == "secondName" ? AnyKey(stringValue:"lastName")! : key
})
let result = try decoder.decode(User.self, from: data)
print(result)
} catch {
print(error)
}
There is not such a feature at this time. But you can take advantage of using computed properties and make the original one private.
struct User: Codable {
var firstName: String
private var secondName: String
var lastName: String {
get { secondName }
set { secondName = newValue }
}
}
So no need to manual implementing of CodingKeys at all and it acts exactly like the way you like. Take a look at their counterparts:

Decodable JSONSerialization error custom object alamofire

I have a custom APIClient using alamofire5 beta that conforms to Codable protocol for the request.
I'm trying to send a custom object via httpBody (post) and I'm getting this error:
Invalid type in JSON write (_SwiftValue)
This is the object that I'm trying to send:
struct Complex: Codable {
var id: String
var name: String
var address: String
var zipcode: String
var amenities: [String]
var schedules: [ComplexSchedules]
init(id: String, name: String, address: String, zipcode: String, amenities: [String], schedules: [ComplexSchedules]) {
self.id = id
self.name = name
self.address = address
self.zipcode = zipcode
self.amenities = amenities
self.schedules = schedules
}
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case address = "address"
case zipcode = "zipcode"
case amenities = "amenities"
case schedules = "schedules"
}
struct TimeRange: Codable {
var from: String
var to: String
init(from: String, to: String) {
self.from = from
self.to = to
}
enum CodingKeys: String, CodingKey {
case from = "from"
case to = "to"
}
}
struct ComplexSchedules: Codable {
var day: String
var timeRanges: [TimeRange]
init(day: String, timeRanges: [TimeRange]) {
self.day = day
self.timeRanges = timeRanges
}
enum CodingKeys: String, CodingKey {
case day = "day"
case timeRanges = "time_ranges"
}
}
}
It fails when I call this method:
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: complex, options: [])
Any thoughts?
You may need
do {
let data = try JSONEncoder().encode(complex)
urlRequest.httpBody = data
}
catch {
print(error)
}
as Codable is used to be able to utilize JSONDecoder and JSONEncoder not to use with JSONSerialization that expects a non-custom object like raw String/Array/Dictionary