Swift ui codable struct with empty fields - swift

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.

Related

How to convert class to decodable Json like #SerializedName in Swift?

I need create a two class to decode json response, like #SerializedName in Kotlin, like this:
class PixHistoryResponse(
#SerializedName("cadastro")
var createdAt: String = "",
#SerializedName("status")
var status: String = "",
#SerializedName("valor")
var finalAmount: String = "",
#SerializedName("timeline")
var history: MutableList<PixTimelineResponse> = mutableListOf(),
#Keep
class PixTimelineResponse(
#SerializedName("cadastro")
var date: String = "",
#SerializedName("status")
var event: String = "",
From my experience, I think you should use struct for modeling data, because it's a value type. Should not use reference type like class.
struct PixHistoryResponse: Decodable {
var createdAt: String = ""
var finalAmount: String = "",
var history: [PixTimelineResponse] = []
enum CodingKeys: String, CodingKey {
case createdAt = "cadastro"
case finalAmount = "valor"
case hisotry = "timeline"
}
}
struct PixTimelineResponse: Decodable {
var date: String = ""
var event: String = ""
enum CodingKeys: String, CodingKey {
case data = "cadastro"
case event = "status"
}
}

Trouble with embeddd Codable objects and Firestore in SwiftUI

I have the following
class OrderBasket: Identifiable, Codable {
#DocumentID var id: String!
var credits: Int! = 200
var basketTotal: Int
var orderLines: [OrderLine] = []
enum CodingKeys: String, CodingKey {
case id
case credits
case basketTotal
case orderLines
}
}
and
class OrderLine: Identifiable, Equatable, Codable {
#DocumentID var id: String! = UUID ().uuidString
var sku : String!
var quantity : Int
var lineTotal: Int
enum CodingKeys: String, CodingKey {
case id
case sku
case quantity
case lineTotal
}
}
I am able to successfully read the data from Firestore. However, how do I extract the orderLines?
The following code works to read the OrderBasket. How do I get the orderLines?
db.collection("Basket").document(Auth.auth().currentUser!.uid).addSnapshotListener { (querySnapshot, error) in
guard let basket = querySnapshot?.data() else {
print("No documents for basket")
return
}
self.orderBasket.credits = (basket["credits"] as! Int)
self.orderBasket.basketTotal = basket["basketTotal"] as! Int
}
basket["orderLines"] contains the data but how do I get it to the object?
I am using FirebaseFirestoreSwift BTW.

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

Swift 4: struct in struct

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