Struct with array for Alamofire responseDecodable - swift

I'm struggling to setup a struct correctly for Alamofire responseDecodable to be decoded.
My JSON return structure is:
{
“SESSIONID” : ”GUID”,
“ISADMIN” : ”YES or NO”,
“FNAME” : ”ABC”,
“SNAME” : ”ABC”,
“EMPNO” : ”ABC”,
"SITES": [
{
"NAME": “MTN-ALICE LANE”,
"WEBSITEAPPID": “SiteGUID”
}
]
}
My Swift code is where the issue is under the array SITES. I know this because if I remove the SITES from my struct then the rest of the JSON response accordingly but without the SITES array.
struct ValidateUser: Decodable{
let sessionId: String?
let isAdmin: String?
let fullName: String?
let surname: String?
let employNo: String?
let siteNames: [UserSites]
enum CodingKeys: String, CodingKey {
case sessionId = "SESSIONID"
case isAdmin = "ISADMIN"
case fullName = "FNAME"
case surname = "SNAME"
case employNo = "EMPNO"
case siteNames = "SITES"
}
}
struct UserSites: Decodable{
let siteName: String?
let siteId: String?
enum CodingKeys: String, CodingKey {
case siteName = "NAME"
case siteId = "WEBSITEAPPID"
}
}
I assume my error is how I've created struct UserSites but unsure.

Missing option ? after let siteNames: [UserSites]
let siteNames: [UserSites]?

Related

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:

Codable Decodable JSON from String to Enum

I'm trying to decode a JSON using codable. I'm wondering if there's a way to customize codable to return HelloModel's typeCustomer as type TypeOfCustomerEnum instead of String?
Example:
{
"name": "Hello",
"lastName": "World",
"typeOfCustomer": "Student"
}
enum TypeOfCustomerEnum: String {
let Student = "Student"
let Paying = "Paying"
let NonPaying = "Nonpaying"
}
struct HelloModel: Codable {
let name: String
let lastName: String
let typeOfCustomer: TypeOfCustomerEnum // JSON for TypeOfCustomer is a String but TypeOfCustomer wanted
}
The type TypeOfCustomerEnum must also conform to Codable and the cases (must be cases) should be lowercased and the literal strings must match the JSON values
enum TypeOfCustomerEnum: String, Codable {
case student = "Student"
case paying = "Paying"
case nonPaying = "NonPaying"
}
struct HelloModel: Codable {
let name: String
let lastName: String
let typeOfCustomer: TypeOfCustomerEnum
}

How do I get all data from this api into a UILabels in a UITableView?

Currently I am using the Tracker Network API for Apex Legends and have hit a problem. I am unable to get all the data back from the api. As you can see below I can call...
self.legendTitle = [json.data.children[0].metadata.legendName]
However, this is only giving me the index of 1. I wanted to know how I could get all these indexes into an array(legendTitle). Currently, I am only receiving one index as the above code shows. Also, leaving out the '[0]' will throw errors.
Here is the code I am using to sort through the APIs JSON.
import Foundation
struct Store: Codable {
let data: DataClass
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
struct DataClass: Codable {
let id: String
let type: String
let children: [Child]
let metadata: DataMetadata
let stats: [Stat]
enum CodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case children = "children"
case metadata = "metadata"
case stats = "stats"
}
}
struct Child: Codable {
let id: String
let type: String
let metadata: ChildMetadata
let stats: [Stat]
enum CodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case metadata = "metadata"
case stats = "stats"
}
}
struct ChildMetadata: Codable {
let legendName: String
let icon: String
let bgimage: String
enum CodingKeys: String, CodingKey {
case legendName = "legend_name"
case icon = "icon"
case bgimage = "bgimage"
}
}
struct Stat: Codable {
let metadata: StatMetadata
let value: Int
let percentile: Double?
let rank: Int?
let displayValue: String
let displayRank: String
enum CodingKeys: String, CodingKey {
case metadata = "metadata"
case value = "value"
case percentile = "percentile"
case rank = "rank"
case displayValue = "displayValue"
case displayRank = "displayRank"
}
}
struct StatMetadata: Codable {
let key: String
let name: String
let categoryKey: StatsCategoryOrder
let categoryName: CategoryName
let isReversed: Bool
enum CodingKeys: String, CodingKey {
case key = "key"
case name = "name"
case categoryKey = "categoryKey"
case categoryName = "categoryName"
case isReversed = "isReversed"
}
}
enum StatsCategoryOrder: String, Codable {
case combat = "combat"
case game = "game"
case weapons = "weapons"
}
enum CategoryName: String, Codable {
case combat = "Combat"
case game = "Game"
case weapons = "Weapons"
}
struct DataMetadata: Codable {
let statsCategoryOrder: [StatsCategoryOrder]
let platformID: Int
let platformUserHandle: String
let accountID: String
let cacheExpireDate: String
let level: Int
enum CodingKeys: String, CodingKey {
case statsCategoryOrder = "statsCategoryOrder"
case platformID = "platformId"
case platformUserHandle = "platformUserHandle"
case accountID = "accountId"
case cacheExpireDate = "cacheExpireDate"
case level = "level"
}
}
Then here is my code that I am using to get the data and then put certain data sets into arrays which I would then display in a UITableView.
let formattedName = usernameEntry.text!.replacingOccurrences(of: " ", with: "%20")
let PlayerStatURL = URL(string: "https://public-api.tracker.gg/apex/v1/standard/profile/\(selectedPlatform)/\(formattedName)")
if let unwrappedURL = PlayerStatURL {
var request = URLRequest(url: unwrappedURL)
request.addValue("My-API-KEY", forHTTPHeaderField: "TRN-Api-Key")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
// you should put in error handling code, too
if let data = data {
do {
let json = try JSONDecoder().decode(Store.self, from: data) as Store
// print(json.data)
//account level
print("Level: \(json.data.metadata.level)")
self.legendTitle = [json.data.children[0].metadata.legendName]
self.title1 = [json.data.children[0].stats[0].metadata.name]
self.data1 = [json.data.children[0].stats[0].value]
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.asyncAfter(deadline: .now() ) {
self.tableView.reloadData()
self.viewDidLoad()
self.statusOfLoad.stopAnimating()
self.statusOfLoad.isHidden = true
}
}
}
dataTask.resume()
}
}
As you can see I also have title1 and data1, which I will also put into arrays for the tableView.
I just have not been able to figure out how to get all available 'legendNames' into 'legendTitle'. (along with the other two)
For API Key: https://apex.tracker.gg/site-api
I just have not been able to figure out how to get all available 'legendNames' into 'legendTitle'
Change
self.legendTitle = [json.data.children[0].metadata.legendName]
to
self.legendTitle = json.data.children.map{$0.metadata.legendName}

How can I init a struct from the values of a different struct

I have a user profile I am storing with a struct shaped like
struct Profile: Codable {
let company: String?
let country: String?
let createdDate: String?
let dateOfBirth: String?
let department: String?
let email: String?
let employeeKey: String?
let firstName: String?
let gender: String?
let id: String?
let jobTitle: String?
let lastName: String?
let location: String?
let mobileDeviceToken: String?
let pictureUri: String?
let roles: [String]?
let status: String?
let updatedDate: String?
let userId: String?
let webDeviceToken: String?
let webMobileDeviceToken: String?
enum CodingKeys: String, CodingKey {
case company = "company"
case country = "country"
case createdDate = "createdDate"
case dateOfBirth = "dateOfBirth"
case department = "department"
case email = "email"
case employeeKey = "employeeKey"
case firstName = "firstName"
case gender = "gender"
case id = "id"
case jobTitle = "jobTitle"
case lastName = "lastName"
case location = "location"
case mobileDeviceToken = "mobileDeviceToken"
case pictureUri = "pictureUri"
case roles = "roles"
case status = "status"
case updatedDate = "updatedDate"
case userId = "userId"
case webDeviceToken = "webDeviceToken"
case webMobileDeviceToken = "webMobileDeviceToken"
}
}
I have another struct which looks like
struct ArticleAuthor {
let name: String
let department: String
let email: String
}
When fetching a user profile, I'd like to be able to create my ArticleAuthor struct using the Profile object returned from my Profile Service.
I was hoping to do something like this, but it does not work as the from value is expected to be data.
self?.profileService.fetchForUserByUserId(userId: authorId) { [weak self] profile, error in
guard error == nil else { return }
let author = try? JSONDecoder().decode(ArticleAuthor.self, from: profile)
print(author) // should be a populated author property
}
I was hoping to avoid something like let author = ArticleAuthor(name: profile?.firstName, department: profile?.department, email: profile?.email) as this object could grow in time.
The profile object in your sample code is already 'Decoded', so you dont need to decode it again.
To avoid using the default init, you can just add a custom initializer, so that you can pass in a Profile struct and set the values. This is usually the best way to go about it as it prevents making lots of changes throughout the codebase when you add new properties
struct ArticleAuthor {
let name: String?
let department: String?
let email: String?
init(profile: Profile) {
self.name = profile.firstName
self.department = profile.department
self.email = profile.email
}
}
self?.profileService.fetchForUserByUserId(userId: authorId) { [weak self] profile, error in
guard error == nil else { return }
let author = Author(profile: profile)
print(author) // should be a populated author property
}

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