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
Related
Im fairly new to programming and I fixed a few errors but only one error ist left, which states: Type 'DataClass' does not conform to protocol 'Decodable' and Type 'DataClass' does not conform to protocol 'Encodable' on line 14.
I checked smiliar questions but I'm not fairly sure if i can find a solution if i put init
// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
// let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
import Foundation
// MARK: - Welcome
struct Welcome1: Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let id: Int
let entityType, label: String
let apiURL, abgeordnetenwatchURL: String
let firstName, lastName: String
let birthName: String
let sex: String
let yearOfBirth: Int
// let party: Referenz -> Party???
let partyPast: String
let education: String
let residence: String
let occupation: String
let statisticQuestions: Int
let statisticQuestionsAnswered: Int
let qidWikidata, fieldTitle: String
enum CodingKeys: String, CodingKey {
case id = "id"
case entityType = "entity_type"
case label = "label"
case apiURL = "api_url"
case abgeordnetenwatchURL = "abgeordnetenwatch_url"
case firstName = "first_name"
case lastName = "last_name"
case birthName = "birth_name"
case sex = "sex"
case yearOfBirth = "year_of_birth"
case party = "party"
case partyPast = "party_past"
case education = "education"
case residence = "residence"
case occupation = "occupation"
case statisticQuestions = "statistic_questions"
case statisticQuestionsAnswered = "statistic_questions_answered"
case qidWikidata = "qid_wikidata"
case fieldTitle = "field_title"
}
}
// MARK: - Party
struct Party1: Codable {
let id: Int
let entityType, label: String
let apiURL: String
enum CodingKeys: String, CodingKey {
case id
case entityType = "entity_type"
case label
case apiURL = "api_url"
}
}
I am trying to use Json parser to collect data from this site lingk. I used quicktype recommended by a tutorial.
Remove
case party = "party" line
or open
// let party: Referenz -> Party? line
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"
}
}
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:
I am trying to implement a cache using CoreData.
Up until this point I've been storing models that are simple, however I have a model below that contains data types such as CodablePartialUser and CodableFeedItemType.
How should these types be modelled in CoreData?
Should I use the Data type and store them in a data format?
As CodableFeedItemType is an enum, should I store the raw value and convert between formats again?
struct CodablePartialUser: Equatable, Codable {
let userID: String
let firstName: String
let lastName: String
init(userID: String, firstName: String, lastName: String) {
self.userID = userID
self.firstName = firstName
self.lastName = lastName
}
}
enum CodableFeedItemType: String, Codable {
case recognition = "RECOGNITION"
case news = "COMPANY_NEWS"
}
struct CodableFeedItem: Codable {
let id: String
let type: CodableFeedItemType
let createdDate: Date
let createdBy: CodablePartialUser
let likesCount: Int
let commentsCount: Int
let externalID: String
let title: String?
let imageURL: URL?
init(id: String, type: CodableFeedItemType, createdDate: Date, createdBy: CodablePartialUser, likesCount: Int, commentsCount: Int, externalID: String, title: String?, imageURL: URL?) {
self.id = id
self.type = type
self.createdDate = createdDate
self.createdBy = createdBy
self.likesCount = likesCount
self.commentsCount = commentsCount
self.externalID = externalID
self.title = title
self.imageURL = imageURL
}
}
For the CodablePartialUser you can use relationship by creating a new Entity named as "CodablePartialUser"
For CodableFeedItemType you can use enum as like
enum CodableFeedItemType: String, Codable {
case recognition = "RECOGNITION"
case news = "COMPANY_NEWS"
}
extension CodableFeedItemEntity {
var type: CodableFeedItemType {
get {
return CodableFeedItemType(rawValue: typeRaw)!
}
set {
typeRaw = newValue.rawValue
}
}
}
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}