I have the following function:
func executeGet( completion: #escaping (Data?, Error?) -> Void) {
AF.request("https:URL",
method:.get,
headers:headers).response{ response in
debugPrint(response)
if let error = response.error {
completion(nil, error)
}
else if let jsonArray = response.value as? Data{
completion(jsonArray, nil)
}
}
}
Which is being called as follows:
executeGet() { (json, error) in
if let error = error{
print(error.localizedDescription)
}
else if let json = json {
print(type(of:json))
print(json)
let welcome = try? JSONDecoder().decode(Welcome.self, from: json)
print(welcome)
}
}
But for some reason, my 'welcome' value always returns nil. Can anyone suggest what could've gone wrong? When I print(json) I'm getting '294 Bytes' for some reason so clearly something went wrong before decoding, right?
EDIT: Upon Udi's request here's the Welcome struct
// MARK: - Welcome
struct Welcome: Codable {
let statusCode: Int
let messageCode: String
let result: Result
}
// MARK: - Result
struct Result: Codable {
let id: String
let inputParameters: InputParameters
let robotID: String
let runByUserID, runByTaskMonitorID: JSONNull?
let runByAPI: Bool
let createdAt, startedAt, finishedAt: Int
let userFriendlyError: JSONNull?
let triedRecordingVideo: Bool
let videoURL: String
let videoRemovedAt: Int
let retriedOriginalTaskID: String
let retriedByTaskID: JSONNull?
let capturedDataTemporaryURL: String
let capturedTexts: CapturedTexts
let capturedScreenshots: CapturedScreenshots
let capturedLists: CapturedLists
enum CodingKeys: String, CodingKey {
case id, inputParameters
case robotID = "robotId"
case runByUserID = "runByUserId"
case runByTaskMonitorID = "runByTaskMonitorId"
case runByAPI, createdAt, startedAt, finishedAt, userFriendlyError, triedRecordingVideo
case videoURL = "videoUrl"
case videoRemovedAt
case retriedOriginalTaskID = "retriedOriginalTaskId"
case retriedByTaskID = "retriedByTaskId"
case capturedDataTemporaryURL = "capturedDataTemporaryUrl"
case capturedTexts, capturedScreenshots, capturedLists
}
}
// MARK: - CapturedLists
struct CapturedLists: Codable {
let companies: [Company]
}
// MARK: - Company
struct Company: Codable {
let position, name, location, description: String
enum CodingKeys: String, CodingKey {
case position = "Position"
case name, location, description
}
}
// MARK: - CapturedScreenshots
struct CapturedScreenshots: Codable {
}
// MARK: - CapturedTexts
struct CapturedTexts: Codable {
let productName, width, patternRepeat, construction: String
let fiber: String
let color: JSONNull?
let mainImage: String
enum CodingKeys: String, CodingKey {
case productName = "Product Name"
case width = "Width"
case patternRepeat = "Pattern Repeat"
case construction = "Construction"
case fiber = "Fiber"
case color = "Color"
case mainImage = "Main Image"
}
}
// MARK: - InputParameters
struct InputParameters: Codable {
let originURL: String
let companiesSkip, companiesLimit: Int
enum CodingKeys: String, CodingKey {
case originURL = "originUrl"
case companiesSkip = "companies_skip"
case companiesLimit = "companies_limit"
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool
{
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
and here's a sample of JSON response
{
"statusCode": 200,
"messageCode": "success",
"result": {
"id": "f6fb62b6-f06a-4bf7-a623-c6a35c2e70b0",
"inputParameters": {
"originUrl": "https://www.ycombinator.com/companies/airbnb",
"companies_skip": 0,
"companies_limit": 10
},
"robotId": "4f5cd7ff-6c98-4cac-8cf0-d7d0cb050b06",
"runByUserId": null,
"runByTaskMonitorId": null,
"runByAPI": true,
"createdAt": 1620739118,
"startedAt": 1620739118,
"finishedAt": 1620739118,
"userFriendlyError": null,
"triedRecordingVideo": true,
"videoUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.mp4",
"videoRemovedAt": 1620739118,
"retriedOriginalTaskId": "673da019-bf0c-476e-9c4f-d35252a151dc",
"retriedByTaskId": null,
"capturedDataTemporaryUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQVG3TPBVXHSCAX63%2F20221031%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221031T185642Z&X-Amz-Expires=1800&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDfX8VNAl5kBgttrCU85U5wc1ZtSOmshO6%2FPilXOv8nvgIhAIveFfsk%2B2CnEkrMZWriodEPsj0osO5a5zV6eVu%2FXfuZKp8DCHwQAhoMMDQ1NTU3NzA4OTA3IgyrbhVK0MP1WMFBXh0q%2FAJulP5qfaV5mn3NRbINqZN4hy4Dg3IujNrZjw8ef32sWE1Gj2D%2Fc0YTJUzvx%2Fnm7LxyNO6AR35mrVy%2FBm9Q80UIspkcLMl45EK%2FoUDO0fAvoUF8g6iZ905qS3MvnOTxXkObhM1PVmpFeJFMw3jksnOPfKE4X7Ut%2FJXNwD%2F5QzdkQCXkGem%2BlrYSSSf8jB8lihTAjT%2FNXmOKMv3jktmZ13T8J1R8F8zeuLPMQf7QphUzlKn5joPb28cConluQC97y%2BjwxqIYjvIFKXY9cZEoaHGh4c6FbXsia714zG3CQp8NSGLbqCCu93oJI1Z61E%2BZ6PhB3vZGdBvXi61AlJcxZ7sti6i0h4VAbWspiJIgWwoZzrsTtneBNNpUW9tvtacGgEZIwAKV%2F3AhVEZu3WC1eQ9HtfjT9%2FjW99SEB8VVGXwkM%2FA9mtT%2FuiL0cAfQZRMhtbQJXXDRdkYEw%2FWuhjJ3zxEtEB2m3uH%2B%2BUEzOzGTd5Knm%2Bero%2BhMfN8X%2Botm3DDbtICbBjqcAf5Riii0XE1w2TZvpm%2FPNHTchCu7FnNz5hfvflv8scpgO5M4bGpy%2FadI4%2F7AUQqCQXFw4scF0FCCdb8AKJZsFGG18W1jjDHyR0YuxZFQ%2FJQRt0JP3yr%2BkVxjAH7qTtc0AzF%2FnGTgy3MOF%2Bm6Y7EkyCWyV2r6o1JTBQMftlf7MI8Uvw4cSZE6JoZviaFtmKVLGGgR4F3cDiyU56augA%3D%3D&X-Amz-Signature=a7bb4d7597ad37cdf1f260890c3c474f7f49334db58c9650d75302a34126f7bc&X-Amz-SignedHeaders=host",
"capturedTexts": {
"Product Name": "Alexis",
"Width": "15",
"Pattern Repeat": "PATTERN REPEAT",
"Construction": "Hand woven",
"Fiber": "100% Wool",
"Color": null,
"Main Image": "https://isteam.wsimg.com/ip/e31f7bba-252b-4669-9209-639d1c00765d/ols/258_original"
},
"capturedScreenshots": {
"top-ads": {
"id": "b4d132f3-12d9-4770-ac7d-88e481fc5b47",
"name": "Top ads",
"src": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
"width": 600,
"height": 120,
"x": 201,
"y": 142,
"deviceScaleFactor": 1.2,
"full": "page",
"comparedToScreenshotId": "29d742c2-6f45-4f29-9d48-ba6fe66e6e3d",
"diffImageSrc": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
"changePercentage": 20,
"diffThreshold": 5,
"fileRemovedAt": 1620739118
}
},
"capturedLists": {
"companies": [
{
"Position": "1",
"name": "Airbnb",
"location": "San Francisco, CA, USA",
"description": "Book accommodations around the world."
},
{
"Position": "2",
"name": "Coin base",
"location": "San Francisco, CA, USA",
"description": "Buy, sell, and manage crypto currencies."
},
{
"Position": "3",
"name": "DoorDash",
"location": "San Francisco, CA, USA",
"description": "Restaurant delivery."
}
]
}
}
}
EDIT2: Upon Rob's suggestion, I tried do-try-catch, as follows:
executeGet() { (json, error) in
if let error = error{
print(error.localizedDescription)
}
else if let json = json {
print(type(of:json)) // Data
print(json) // 2479 Bytes
do{
var welcome = try JSONDecoder().decode(Welcome.self, from: json)
print(welcome)
}
catch {
print(error)
}
}
}
Which reports the error:
keyNotFound(CodingKeys(stringValue: "companies_skip", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "inputParameters", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "companies_skip", intValue: nil) ("companies_skip").", underlyingError: nil))
The string response you show in your comment, means you get a valid response from the server,
and so you should be able to decode it with the following models.
Use #vadian answer to your previous question :
Unable to parse JSON data properly from Alomafire
Here are the test code and models to decode the response into a set of structs.
Note you will have to consult the server doc to determine which properties are Optional and adjust the code (i,e put ?)
where nessesary .
struct ContentView: View {
#State var welcome: WelcomeResponse?
var body: some View {
VStack {
if let response = welcome {
Text(response.messageCode)
Text("\(response.statusCode)")
ForEach(response.result.capturedLists.companies) { item in
Text(item.description)
}
}
}
.onAppear {
let json = """
{
"statusCode": 200,
"messageCode": "success",
"result": {
"id": "f6fb62b6-f06a-4bf7-a623-c6a35c2e70b0",
"inputParameters": {
"originUrl": "https://www.ycombinator.com/companies/airbnb",
"companies_skip": 0,
"companies_limit": 10
},
"robotId": "4f5cd7ff-6c98-4cac-8cf0-d7d0cb050b06",
"runByUserId": null,
"runByTaskMonitorId": null,
"runByAPI": true,
"createdAt": 1620739118,
"startedAt": 1620739118,
"finishedAt": 1620739118,
"userFriendlyError": null,
"triedRecordingVideo": true,
"videoUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.mp4",
"videoRemovedAt": 1620739118,
"retriedOriginalTaskId": "673da019-bf0c-476e-9c4f-d35252a151dc",
"retriedByTaskId": null,
"capturedDataTemporaryUrl": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/system-1620230966-b1a9688b-05d3-4682-beeb-9ce035e482b1.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQVG3TPBVXHSCAX63%2F20221031%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221031T185642Z&X-Amz-Expires=1800&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEJP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJIMEYCIQDfX8VNAl5kBgttrCU85U5wc1ZtSOmshO6%2FPilXOv8nvgIhAIveFfsk%2B2CnEkrMZWriodEPsj0osO5a5zV6eVu%2FXfuZKp8DCHwQAhoMMDQ1NTU3NzA4OTA3IgyrbhVK0MP1WMFBXh0q%2FAJulP5qfaV5mn3NRbINqZN4hy4Dg3IujNrZjw8ef32sWE1Gj2D%2Fc0YTJUzvx%2Fnm7LxyNO6AR35mrVy%2FBm9Q80UIspkcLMl45EK%2FoUDO0fAvoUF8g6iZ905qS3MvnOTxXkObhM1PVmpFeJFMw3jksnOPfKE4X7Ut%2FJXNwD%2F5QzdkQCXkGem%2BlrYSSSf8jB8lihTAjT%2FNXmOKMv3jktmZ13T8J1R8F8zeuLPMQf7QphUzlKn5joPb28cConluQC97y%2BjwxqIYjvIFKXY9cZEoaHGh4c6FbXsia714zG3CQp8NSGLbqCCu93oJI1Z61E%2BZ6PhB3vZGdBvXi61AlJcxZ7sti6i0h4VAbWspiJIgWwoZzrsTtneBNNpUW9tvtacGgEZIwAKV%2F3AhVEZu3WC1eQ9HtfjT9%2FjW99SEB8VVGXwkM%2FA9mtT%2FuiL0cAfQZRMhtbQJXXDRdkYEw%2FWuhjJ3zxEtEB2m3uH%2B%2BUEzOzGTd5Knm%2Bero%2BhMfN8X%2Botm3DDbtICbBjqcAf5Riii0XE1w2TZvpm%2FPNHTchCu7FnNz5hfvflv8scpgO5M4bGpy%2FadI4%2F7AUQqCQXFw4scF0FCCdb8AKJZsFGG18W1jjDHyR0YuxZFQ%2FJQRt0JP3yr%2BkVxjAH7qTtc0AzF%2FnGTgy3MOF%2Bm6Y7EkyCWyV2r6o1JTBQMftlf7MI8Uvw4cSZE6JoZviaFtmKVLGGgR4F3cDiyU56augA%3D%3D&X-Amz-Signature=a7bb4d7597ad37cdf1f260890c3c474f7f49334db58c9650d75302a34126f7bc&X-Amz-SignedHeaders=host",
"capturedTexts": {
"Product Name": "Alexis",
"Width": "15",
"Pattern Repeat": "PATTERN REPEAT",
"Construction": "Hand woven",
"Fiber": "100% Wool",
"Color": null,
"Main Image": "https://isteam.wsimg.com/ip/e31f7bba-252b-4669-9209-639d1c00765d/ols/258_original"
},
"capturedScreenshots": {
"top-ads": {
"id": "b4d132f3-12d9-4770-ac7d-88e481fc5b47",
"name": "Top ads",
"src": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
"width": 600,
"height": 120,
"x": 201,
"y": 142,
"deviceScaleFactor": 1.2,
"full": "page",
"comparedToScreenshotId": "29d742c2-6f45-4f29-9d48-ba6fe66e6e3d",
"diffImageSrc": "https://prod-browseai-captured-data.s3.amazonaws.com/1fae674a-2788-46a8-83c8-95c4664c6d25/6326b3c1-7b16-4256-a323-7d8d8954bd4e/1061671f-7f71-42ac-bb9a-207d126d1f3a/00001-user-1620230947-6f113cf2-90ef-4c66-a448-9d5c6bd64873.png",
"changePercentage": 20,
"diffThreshold": 5,
"fileRemovedAt": 1620739118
}
},
"capturedLists": {
"companies": [
{
"Position": "1",
"name": "Airbnb",
"location": "San Francisco, CA, USA",
"description": "Book accommodations around the world."
},
{
"Position": "2",
"name": "Coin base",
"location": "San Francisco, CA, USA",
"description": "Buy, sell, and manage crypto currencies."
},
{
"Position": "3",
"name": "DoorDash",
"location": "San Francisco, CA, USA",
"description": "Restaurant delivery."
}
]
}
}
}
"""
// simulated API data from the server
let data = json.data(using: .utf8)!
do {
let results = try JSONDecoder().decode(WelcomeResponse.self, from: data)
welcome = results
print("\n---> results: \(results) \n")
} catch {
print("\n---> decoding error: \n \(error)\n")
}
}
}
}
// MARK: - WelcomeResponse
struct WelcomeResponse: Codable {
let statusCode: Int
let messageCode: String
let result: Result
}
// MARK: - Result
struct Result: Codable {
let id: String
let inputParameters: InputParameters
let robotID: String
let runByUserID, runByTaskMonitorID: String?
let runByAPI: Bool
let createdAt, startedAt, finishedAt: Int
let userFriendlyError: String?
let triedRecordingVideo: Bool
let videoURL: String
let videoRemovedAt: Int
let retriedOriginalTaskID: String
let retriedByTaskID: String?
let capturedDataTemporaryURL: String
let capturedTexts: CapturedTexts
let capturedScreenshots: CapturedScreenshots
let capturedLists: CapturedLists
enum CodingKeys: String, CodingKey {
case id, inputParameters
case robotID = "robotId"
case runByUserID = "runByUserId"
case runByTaskMonitorID = "runByTaskMonitorId"
case runByAPI, createdAt, startedAt, finishedAt, userFriendlyError, triedRecordingVideo
case videoURL = "videoUrl"
case videoRemovedAt
case retriedOriginalTaskID = "retriedOriginalTaskId"
case retriedByTaskID = "retriedByTaskId"
case capturedDataTemporaryURL = "capturedDataTemporaryUrl"
case capturedTexts, capturedScreenshots, capturedLists
}
}
// MARK: - CapturedLists
struct CapturedLists: Codable {
let companies: [Company]
}
// MARK: - Company
struct Company: Identifiable, Codable {
let id = UUID()
let position, name, location, description: String
enum CodingKeys: String, CodingKey {
case position = "Position"
case name, location, description
}
}
// MARK: - CapturedScreenshots
struct CapturedScreenshots: Codable {
let topAds: TopAds
enum CodingKeys: String, CodingKey {
case topAds = "top-ads"
}
}
// MARK: - TopAds
struct TopAds: Codable {
let id, name: String
let src: String
let width, height, x, y: Int
let deviceScaleFactor: Double
let full, comparedToScreenshotId: String
let diffImageSrc: String
let changePercentage, diffThreshold, fileRemovedAt: Int
}
// MARK: - CapturedTexts
struct CapturedTexts: Codable {
let productName, width, patternRepeat, construction: String
let fiber: String
let color: String?
let mainImage: String
enum CodingKeys: String, CodingKey {
case productName = "Product Name"
case width = "Width"
case patternRepeat = "Pattern Repeat"
case construction = "Construction"
case fiber = "Fiber"
case color = "Color"
case mainImage = "Main Image"
}
}
// MARK: - InputParameters
struct InputParameters: Codable {
let originUrl: String
let companiesSkip: Int?
let companiesLimit: Int?
enum CodingKeys: String, CodingKey {
case originUrl
case companiesSkip = "companies_skip"
case companiesLimit = "companies_limit"
}
}
Your error was reportedly:
keyNotFound(CodingKeys(stringValue: "companies_skip", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "result", intValue: nil), CodingKeys(stringValue: "inputParameters", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: "companies_skip", intValue: nil) ("companies_skip").", underlyingError: nil))
That points you precisely to where the decoding failed. There is apparently no key called companies_skip in result » inputParameters. Now, you don't show us the full response you actually received, so it is hard to be precise. But we can infer from this error that the response does not precisely match your sample JSON, but rather, the companies_skip key is not present.
We might infer from the name, inputParameters, that your request URL (which, again, you have not shared with us) may possibly need to supply that parameter. Or, alternatively, perhaps that parameter name shouldn't be marked as a required sub key of the inputParameters structure (e.g., you might want to make it an optional).
Regardless of the particulars, this is the process. If decoding fails, look at the complete error object, and it will tell you where it had problems. Note, if there are multiple decoding problems, the error will only report the first one, so do not be surprised if it takes a few times and different queries to resolve all of the issues. The first time you start decoding a particular request, resolving all of the potential discrepancies may be an iterative process.
Related
Unable to parse using codable structs for following json
{
"data": {
"listDeviceStateTable": {
"items": [
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station1"
},
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station2"
}
]
}
}
}
Error :
APIError: keyNotFound key CodingKeys(stringValue: "data", intValue:
nil) Caused by: keyNotFound(CodingKeys(stringValue: "data", intValue:
nil), Swift.DecodingError.Context(codingPath: [], debugDescription:
"No value associated with key CodingKeys(stringValue: "data",
intValue: nil) ("data").", underlyingError: nil)))
Model:
//MARK: DeviceState
struct DeviceState:Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let listDeviceStateTable: ListDeviceStateTable
}
// MARK: - ListDeviceStateTable
struct ListDeviceStateTable: Codable {
let items: [Item]
}
// MARK: - Item
struct Item: Codable {
let data, pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
works well for me. This is the test code I used to show how to decode the json data into your structs. Of course Item->data is here decoded as a String, as per the json you show.
struct ContentView: View {
#State var devState: DeviceState?
var body: some View {
Group {
if let devFirst = devState?.data.listDeviceStateTable.items.first {
Text("pk: \(devFirst.pk)")
}
}
.onAppear {
let json = """
{
"data": {
"listDeviceStateTable": {
"items": [
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station1"
},
{
"Data": "{'state': -1, 'remainSec': 0}",
"PK": "DEVICE#144b584b-xxxx-xxxx-xxxx-1e584bdb1e8c",
"SK": "Station2"
}
]
}
}
}
"""
let data = json.data(using: .utf8)!
do {
devState = try JSONDecoder().decode(DeviceState.self, from: data)
print(devState)
} catch {
print("\(error)")
}
}
}
}
struct DeviceState:Codable {
let data: DataClass
}
// MARK: - DataClass
struct DataClass: Codable {
let listDeviceStateTable: ListDeviceStateTable
}
// MARK: - ListDeviceStateTable
struct ListDeviceStateTable: Codable {
let items: [Item]
}
// MARK: - Item
struct Item: Codable {
let data, pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
to decode Data into something else than a String, eg a ItemData, you need to remove the double quotes,
and replace the single quote with double quotes (in the value not the key).
eg. "Data": "{'state': -1, 'remainSec': 0}", to "Data": {"state": -1, "remainSec": 0},
and use the following Item struct and decoding code:
// MARK: - Item
struct Item: Codable {
let data: ItemData
let pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
pk = try container.decode(String.self, forKey: .pk)
sk = try container.decode(String.self, forKey: .sk)
do {
let theString = try container.decode(String.self, forKey: .data)
let json = theString.replacingOccurrences(of: "\"", with: "").replacingOccurrences(of: "'", with: "\"")
data = try JSONDecoder().decode(ItemData.self, from: json.data(using: .utf8)!)
} catch DecodingError.typeMismatch {
data = ItemData(state: 0, remainSec: 0) // <-- todo
}
}
}
struct ItemData: Codable {
let state, remainSec: Int
}
You need one more struct to decode data inside Item like this
struct Item: Codable {
let data: ItemData
let pk, sk: String
enum CodingKeys: String, CodingKey {
case data = "Data"
case pk = "PK"
case sk = "SK"
}
}
struct ItemData: Codable {
let state, remainSec: Int
}
I am trying to decode a JSON object from a remote API, Xcode doesn't raise any flags but the screen remains blank, I couldn't pinpoint where the error is coming from but if I have to take I guess, I think it's something to do with my parsing.
What's going wrong here? everything seems at its place
This is my ContentView.swift
import SwiftUI
struct ContentView: View {
#State var results = UserProducts(status: Bool(), userProducts: [UserProduct]())
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: nil) {
ForEach(0..<results.userProducts!.count) {
res in
VStack(){Text(verbatim: String(format: String(),((results.userProducts![res].id ?? "NA"))))}
}.onAppear(perform: loadShelf)
}
}
Spacer()
}).background(Color(red: 250 / 255, green: 248 / 255, blue: 244 / 255))
}
func loadShelf(){
guard let apiBaseURL = URL(string: "...") else {
print("Base URL is invalid.")
return
}
let request = URLRequest(url: apiBaseURL)
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
if let data = data {
do{
let decoded = try JSONDecoder().decode(UserProducts.self, from: data)
self.results = decoded
print("decoded: \(decoded)")
//prints: UserProducts(status: nil, userProducts: nil, recommendedProducts: nil)
}
catch{
print("Fetching data failed: \(error)")
}
}
}
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is my Structs:
import Foundation
// MARK: - UserProducts
struct UserProducts: Codable {
let status: Bool?
let userProducts: [UserProduct]?
enum CodingKeys: String, CodingKey {
case status
case userProducts = "user_products"
}
}
// MARK: - UserProduct
struct UserProduct: Codable {
let id, userID, productID: Int?
let routineTime, createdAt, updatedAt: String?
let archived, volume: Int?
let deletedAt, addedBy, weeklyRoutineOne, weeklyRoutineTwo: String?
let product: Product?
let user: User?
let phaseOut, refill, empty, recommended: Int?
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case productID = "product_id"
case routineTime = "routine_time"
case createdAt = "created_at"
case updatedAt = "updated_at"
case archived, volume
case deletedAt = "deleted_at"
case addedBy = "added_by"
case weeklyRoutineOne = "weekly_routine_one"
case weeklyRoutineTwo = "weekly_routine_two"
case product, user
case phaseOut = "phase_out"
case refill, empty, recommended
}
}
// MARK: - Product
struct Product: Codable {
let productName, productDescription, productIngredients: String?
let productPrice, volume: Int?
let image, interference, activeIngredients, howToUse: String?
let brandID, productTypeID: Int?
let brand, type: Brand?
let rating: JSONNull?
enum CodingKeys: String, CodingKey {
case productName = "product_name"
case productDescription = "product_description"
case productIngredients = "product_ingredients"
case productPrice = "product_price"
case volume, image, interference
case activeIngredients = "active_ingredients"
case howToUse = "how_to_use"
case brandID = "brand_id"
case productTypeID = "product_type_id"
case brand, type, rating
}
}
// MARK: - Brand
struct Brand: Codable {
let id: Int?
let name, createdAt, updatedAt, commission: String?
let category: Int?
enum CodingKeys: String, CodingKey {
case id, name
case createdAt = "created_at"
case updatedAt = "updated_at"
case commission, category
}
}
// MARK: - User
struct User: Codable {
let name, email: String?
let image1, deviceToken: JSONNull?
let account, followup: Bool?
enum CodingKeys: String, CodingKey {
case name, email, image1
case deviceToken = "device_token"
case account, followup
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Now this is the JSON model and how it's supposed to look:
{
"status": true,
"user_products": [
{
"id": 1,
"user_id": 1,
"product_id": 1,
"routine_time": "",
"created_at": "",
"updated_at": "",
"archived": 0,
"volume": 1,
"deleted_at": "",
"added_by": "",
"weekly_routine_one": "",
"weekly_routine_two": "",
"product": {
"product_name": "",
"product_description": "",
"product_ingredients": "",
"product_price": 1,
"volume": 1,
"image": "",
"interference": "",
"active_ingredients": "",
"how_to_use": "",
"brand_id": 1,
"product_type_id": 1,
"brand": {
"id": 1,
"name": "",
"created_at": "",
"updated_at": "",
"commission": ""
},
"type": {
"id": 1,
"name": "",
"created_at": "",
"updated_at": "",
"category": 1
},
"rating": null
},
"user": {
"name": "",
"email": "",
"image1": null,
"device_token": null,
"account": false,
"followup": false
},
"phase_out": 0,
"refill": 0,
"empty": 0,
"recommended": 0
}
]
}
You actually have two problems here.
1st, this is where your nils are coming from:
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
If you comment it out or make it do what you actually want it to do instead of what it's currently doing (making everything nil)... that'll bring us to problem #2
You'll have your data, but because it's all optionals, there's some unwrapping that needs to be done before it'll make much sense.
I am trying to fetch data from the Unsplash API however I am getting the following error: "Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates."
Here is the model struct:
// MARK: - UnsplashData
struct UnsplashData: Codable {
let id: String
let createdAt, updatedAt, promotedAt: Date
let width, height: Int
let color, blurHash: String
let unsplashDataDescription: String?
let altDescription: String
let urls: Urls
let links: UnsplashDataLinks
let categories: [String]
let likes: Int
let likedByUser: Bool
let currentUserCollections: [String]
let sponsorship: JSONNull?
let user: User
let exif: Exif
let location: Location
let views, downloads: Int
enum CodingKeys: String, CodingKey {
case id
case createdAt = "created_at"
case updatedAt = "updated_at"
case promotedAt = "promoted_at"
case width, height, color
case blurHash = "blur_hash"
case unsplashDataDescription = "description"
case altDescription = "alt_description"
case urls, links, categories, likes
case likedByUser = "liked_by_user"
case currentUserCollections = "current_user_collections"
case sponsorship, user, exif, location, views, downloads
}
}
// MARK: - Exif
struct Exif: Codable {
let make, model, exposureTime, aperture: String
let focalLength: String
let iso: Int
enum CodingKeys: String, CodingKey {
case make, model
case exposureTime = "exposure_time"
case aperture
case focalLength = "focal_length"
case iso
}
}
// MARK: - UnsplashDataLinks
struct UnsplashDataLinks: Codable {
let linksSelf, html, download, downloadLocation: String
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case html, download
case downloadLocation = "download_location"
}
}
// MARK: - Location
struct Location: Codable {
let title, name, city, country: String?
let position: Position
}
// MARK: - Position
struct Position: Codable {
let latitude, longitude: Double?
}
// MARK: - Urls
struct Urls: Codable {
let raw, full, regular, small: String
let thumb: String
}
// MARK: - User
struct User: Codable {
let id: String
let updatedAt: Date
let username, name, firstName, lastName: String
let twitterUsername: String?
let portfolioURL: String
let bio: String?
let location: String
let links: UserLinks
let profileImage: ProfileImage
let instagramUsername: String
let totalCollections, totalLikes, totalPhotos: Int
let acceptedTos: Bool
enum CodingKeys: String, CodingKey {
case id
case updatedAt = "updated_at"
case username, name
case firstName = "first_name"
case lastName = "last_name"
case twitterUsername = "twitter_username"
case portfolioURL = "portfolio_url"
case bio, location, links
case profileImage = "profile_image"
case instagramUsername = "instagram_username"
case totalCollections = "total_collections"
case totalLikes = "total_likes"
case totalPhotos = "total_photos"
case acceptedTos = "accepted_tos"
}
}
// MARK: - UserLinks
struct UserLinks: Codable {
let linksSelf, html, photos, likes: String
let portfolio, following, followers: String
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
case html, photos, likes, portfolio, following, followers
}
}
// MARK: - ProfileImage
struct ProfileImage: Codable {
let small, medium, large: String
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
And here is my ObservableObject:
class UnsplashAPI: ObservableObject {
enum State {
case loading
case loaded(UnsplashData)
}
#Published var state = State.loading
let url = URL(string: "https://api.unsplash.com/")!
func request() {
guard var components = URLComponents(url: url.appendingPathComponent("photos/random"),
resolvingAgainstBaseURL: true)
else {
fatalError("Couldn't append path component")
}
components.queryItems = [
URLQueryItem(name: "client_id", value: "vMDQ3Vzix8FN6MJL5Qpl3y0F7GdQsTtOjBe_L-IG2ro")
]
let request = URLRequest(url: components.url!)
let urlSession = URLSession(configuration: URLSessionConfiguration.default)
urlSession.dataTask(with: request) { data, urlResponse, error in
if let data = data {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
let response = try decoder.decode(UnsplashData.self, from: data)
self.state = .loaded(response) //error here
} catch {
print(error)
fatalError("Couldn't decode")
}
} else if let error = error {
print(error.localizedDescription)
} else {
fatalError("Didn't receive data")
}
}.resume()
}
}
Finally here is an example response I requested using Postman:
{
"id": "HWx5PYGudcI",
"created_at": "2020-12-08T22:11:11-05:00",
"updated_at": "2020-12-26T23:19:29-05:00",
"promoted_at": "2020-12-09T03:14:06-05:00",
"width": 4000,
"height": 6000,
"color": "#8ca6a6",
"blur_hash": "LAD,r_D*_M?^%ER4%$-oyYp0m+WE",
"description": null,
"alt_description": "boy in gray crew neck shirt",
"urls": {
"raw": "https://images.unsplash.com/photo-1607483421673-181fb79394b3?ixid=MXwxMTU5MTR8MHwxfHJhbmRvbXx8fHx8fHx8&ixlib=rb-1.2.1",
"full": "https://images.unsplash.com/photo-1607483421673-181fb79394b3?crop=entropy&cs=srgb&fm=jpg&ixid=MXwxMTU5MTR8MHwxfHJhbmRvbXx8fHx8fHx8&ixlib=rb-1.2.1&q=85",
"regular": "https://images.unsplash.com/photo-1607483421673-181fb79394b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MXwxMTU5MTR8MHwxfHJhbmRvbXx8fHx8fHx8&ixlib=rb-1.2.1&q=80&w=1080",
"small": "https://images.unsplash.com/photo-1607483421673-181fb79394b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MXwxMTU5MTR8MHwxfHJhbmRvbXx8fHx8fHx8&ixlib=rb-1.2.1&q=80&w=400",
"thumb": "https://images.unsplash.com/photo-1607483421673-181fb79394b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MXwxMTU5MTR8MHwxfHJhbmRvbXx8fHx8fHx8&ixlib=rb-1.2.1&q=80&w=200"
},
"links": {
"self": "https://api.unsplash.com/photos/HWx5PYGudcI",
"html": "https://unsplash.com/photos/HWx5PYGudcI",
"download": "https://unsplash.com/photos/HWx5PYGudcI/download",
"download_location": "https://api.unsplash.com/photos/HWx5PYGudcI/download"
},
"categories": [],
"likes": 51,
"liked_by_user": false,
"current_user_collections": [],
"sponsorship": null,
"user": {
"id": "3Bj-zCFL4-g",
"updated_at": "2020-12-26T14:58:36-05:00",
"username": "owensito",
"name": "Owen Vangioni",
"first_name": "Owen",
"last_name": "Vangioni",
"twitter_username": null,
"portfolio_url": null,
"bio": "Capturing magical moments...\nInstagram: #owensitens 18 years",
"location": "Argentina ",
"links": {
"self": "https://api.unsplash.com/users/owensito",
"html": "https://unsplash.com/#owensito",
"photos": "https://api.unsplash.com/users/owensito/photos",
"likes": "https://api.unsplash.com/users/owensito/likes",
"portfolio": "https://api.unsplash.com/users/owensito/portfolio",
"following": "https://api.unsplash.com/users/owensito/following",
"followers": "https://api.unsplash.com/users/owensito/followers"
},
"profile_image": {
"small": "https://images.unsplash.com/profile-1583211530737-0c1a46227535image?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32",
"medium": "https://images.unsplash.com/profile-1583211530737-0c1a46227535image?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64",
"large": "https://images.unsplash.com/profile-1583211530737-0c1a46227535image?ixlib=rb-1.2.1&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128"
},
"instagram_username": "owensitens",
"total_collections": 1,
"total_likes": 13,
"total_photos": 135,
"accepted_tos": true
},
"exif": {
"make": "NIKON CORPORATION",
"model": "NIKON D3300",
"exposure_time": "1/320",
"aperture": "5.3",
"focal_length": "45.0",
"iso": 200
},
"location": {
"title": null,
"name": null,
"city": null,
"country": null,
"position": {
"latitude": null,
"longitude": null
}
},
"views": 536045,
"downloads": 1267
}
You will need to switch thread to the main thread from which you are allowed (and only from it!) to make UI changes in iOS. To fix the error you will need to use GCD and simply wrap the line where you change your state in the async closure block.
DispatchQueue.main.async {
self.state = .loaded(response) // error should not be triggered anymore
}
add #MainActor before your class definition.
So i try to parse with jsondecoder and when i see in the log menu, all the data in json is nil. While the json i check in postman all have data on it
so here's the json i want to parse (*i just want to parse the row) :
{
" user": {
"command": "SELECT",
"rowCount": 1,
"oid": null,
"rows": [
{
"user_id": 193,
"u_name": "Gunawan Wibisono",
"email": "gunwibi89#gmail.com",
"div_name": "Design Aplication & Infrastructure",
"url": "2"
}
],
"fields": [
{
"name": "user_id",
"tableID": 1656774,
"columnID": 1,
"dataTypeID": 23,
"dataTypeSize": 4,
"dataTypeModifier": -1,
"format": "text"
},
{
"name": "u_name",
"tableID": 1656774,
"columnID": 2,
"dataTypeID": 1043,
"dataTypeSize": -1,
"dataTypeModifier": 54,
"format": "text"
},
{
"name": "email",
"tableID": 1656774,
"columnID": 3,
"dataTypeID": 1043,
"dataTypeSize": -1,
"dataTypeModifier": 259,
"format": "text"
},
{
"name": "div_name",
"tableID": 1656724,
"columnID": 2,
"dataTypeID": 1043,
"dataTypeSize": -1,
"dataTypeModifier": 259,
"format": "text"
},
{
"name": "url",
"tableID": 1656774,
"columnID": 9,
"dataTypeID": 1043,
"dataTypeSize": -1,
"dataTypeModifier": 259,
"format": "text"
}
],
"_parsers": [
null,
null,
null,
null,
null
],
"_types": {
"_types": {
"arrayParser": {}
},
"text": {},
"binary": {}
},
"RowCtor": null,
"rowAsArray": false
},
"status": 1
}
this is the code :
struct User : Codable {
let command : String?
let rowCount : Int?
let oid : Int?
let rows : [Rowss]?
}
struct Rowss : Codable {
let user_id: Int?
let u_name : String?
let email : String?
let div_name: String?
let url : String?
enum Codingkeys : String, CodingKey {
case user_id = "user_id"
case u_name = "u_name"
case email = "email"
case div_name = "div_name"
case url = "url"
}
}
func Json() {
let user = UserName.text
let pass = Password.text
let json = "http://ratings.immobispsa.com/getslogs/\(user!)/\(pass!)"
guard let myUrl = URL(string: json) else { return }
URLSession.shared.dataTask(with: myUrl) { (data, response, error) in
guard let data = data else {return}
do{
let user = try JSONDecoder().decode(User.self, from: data)
print("this is the json\(user)")
}catch{
print(error)
}
}.resume()
this is the log menu after i build :
"this is the jsonUser(command: nil, rowCount: nil, oid: nil, rows: nil)"
any idea where ive done wrong?
Your Codable structure is wrong. You should refer some tutorials for the same. Here is the Codable structure as per your response:
struct UserResponse: Codable {
let status: Int
let user: User
private enum CodingKeys: String, CodingKey {
case status
case user = " user"
}
}
struct User: Codable {
let command: String?
let rowCount: Int?
let oid: Int?
let rowCtor: Int?
let rowAsArray: Bool?
let rows: [Rows]?
let fields: [Fields]?
let parsers: [Parsers]?
let types: Type?
private enum CodingKeys: String, CodingKey {
case command
case rowCount
case oid
case rowCtor = "RowCtor"
case rowAsArray
case rows
case fields
case parsers = "_parsers"
case types = "_types"
}
}
struct Rows: Codable {
let userId: Int
let uName: String
let email: String
let divName: String
let url: String
private enum CodingKeys: String, CodingKey {
case userId = "user_id"
case uName = "u_name"
case email
case divName = "div_name"
case url
}
}
struct Fields: Codable {
let name: String
let tableID: Int
let columnID: Int
let datatypeID: Int?
let dataTypeSize: Int
let dataTypeModifier: Int
let format: String
private enum CodingKeys: String, CodingKey {
case name
case tableID
case columnID
case datatypeID
case dataTypeSize
case dataTypeModifier
case format
}
}
struct Parsers: Codable {
}
struct Types: Codable {
let types: Type?
let text: Text?
let binary: Binary?
private enum CodingKeys: String, CodingKey {
case types = "_types"
case text
case binary
}
}
struct Type: Codable {
}
struct Text: Codable {
}
struct Binary: Codable {
}
If any value can come as null, then only mark it as optional (?) otherwise don't, and in JSON your user key is having extra space at the front like " user", your API developer should fix it if possible.
Now decode it:
do{
let decodeResponse = try JSONDecoder().decode(UserResponse.self, from: data)
print("this is the json\(decodeResponse.user)")
}catch{
print(error)
}
I am currently using Tracker Networks Apex Legends API version 2.
I am trying to get the Legend’s data for each legend and then be able to show the data for each specific legend.
However, I cannot figure out an easy way of doing this.
Any ideas on how I could get something like...
Pathfinder
Kills - 100
Damage - 3000
Etc.
And do that for each legend.
JSON response:
{
"type": "legend",
"attributes": {
"id": "legend_8"
},
"metadata": {
"name": "Pathfinder",
"imageUrl": "https://trackercdn.com/cdn/apex.tracker.gg/legends/pathfinder-tile.png",
"tallImageUrl": "https://trackercdn.com/cdn/apex.tracker.gg/legends/pathfinder-tall.png",
"bgImageUrl": "https://trackercdn.com/cdn/apex.tracker.gg/legends/pathfinder-concept-bg-small.jpg",
"isActive": true
},
"expiryDate": "2019-10-04T21:59:04.1270197Z",
"stats": {
"kills": {
"rank": 208,
"percentile": 99.9,
"displayName": "Kills",
"displayCategory": "Combat",
"category": null,
"metadata": {},
"value": 12728,
"displayValue": "12,728",
"displayType": "Unspecified"
},
"killsAsKillLeader": {
"rank": 22,
"percentile": 99.9,
"displayName": "Kills As Kill Leader",
"displayCategory": "Combat",
"category": null,
"metadata": {},
"value": 5764,
"displayValue": "5,764",
"displayType": "Unspecified"
},
"sniperKills": {
"rank": 990,
"percentile": 99.7,
"displayName": "Sniper Kills",
"displayCategory": "Weapons",
"category": null,
"metadata": {},
"value": 104,
"displayValue": "104",
"displayType": "Unspecified"
},
"seasonWins": {
"rank": 31158,
"percentile": 92,
"displayName": "Season 1 Wins",
"displayCategory": "Game",
"category": null,
"metadata": {},
"value": 24,
"displayValue": "24",
"displayType": "Unspecified"
},
"seasonDamage": {
"rank": 5310,
"percentile": 98.8,
"displayName": "Season 1 Damage",
"displayCategory": "Game",
"category": null,
"metadata": {},
"value": 403954,
"displayValue": "403,954",
"displayType": "Unspecified"
}
}
},
"expiryDate": "2019-10-04T21:59:04.1270197Z"
}
You see I want to get this stats information for each legend and then display their name with whatever data on them is available inside a collection view or tableView. Probably collection view.
Structs used (done with QuickType):
struct Store: Codable {
let data: DataClass
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
// MARK: - DataClass
struct DataClass: Codable {
let platformInfo: PlatformInfo
let userInfo: UserInfo
let metadata: DataMetadata
let segments: [Segment]
let availableSegments: [AvailableSegment]
//let expiryDate: ExpiryDate
enum CodingKeys: String, CodingKey {
case platformInfo = "platformInfo"
case userInfo = "userInfo"
case metadata = "metadata"
case segments = "segments"
case availableSegments = "availableSegments"
//case expiryDate = "expiryDate"
}
}
// MARK: - AvailableSegment
struct AvailableSegment: Codable {
let type: TypeEnum
let attributes: MetadataClass
enum CodingKeys: String, CodingKey {
case type = "type"
case attributes = "attributes"
}
}
// MARK: - MetadataClass
struct MetadataClass: Codable {
}
enum TypeEnum: String, Codable {
case legend = "legend"
case overview = "overview"
}
// MARK: - DataMetadata
struct DataMetadata: Codable {
let currentSeason: Int
let activeLegend: String
let activeLegendName: String
enum CodingKeys: String, CodingKey {
case currentSeason = "currentSeason"
case activeLegend = "activeLegend"
case activeLegendName = "activeLegendName"
}
}
// MARK: - PlatformInfo
struct PlatformInfo: Codable {
let platformSlug: String
let platformUserId: String
let platformUserHandle: String
let platformUserIdentifier: String
let avatarUrl: String
let additionalParameters: JSONNull?
enum CodingKeys: String, CodingKey {
case platformSlug = "platformSlug"
case platformUserId = "platformUserId"
case platformUserHandle = "platformUserHandle"
case platformUserIdentifier = "platformUserIdentifier"
case avatarUrl = "avatarUrl"
case additionalParameters = "additionalParameters"
}
}
// MARK: - Segment
struct Segment: Codable {
let type: TypeEnum
let attributes: SegmentAttributes
let metadata: SegmentMetadata
// let expiryDate: ExpiryDate
let stats: Stats
enum CodingKeys: String, CodingKey {
case type = "type"
case attributes = "attributes"
case metadata = "metadata"
//case expiryDate = "expiryDate"
case stats = "stats"
}
}
// MARK: - SegmentAttributes
struct SegmentAttributes: Codable {
let id: String?
enum CodingKeys: String, CodingKey {
case id = "id"
}
}
// MARK: - SegmentMetadata
struct SegmentMetadata: Codable {
let name: String
let imageUrl: String?
let tallImageUrl: String?
let bgImageUrl: String?
let isActive: Bool?
enum CodingKeys: String, CodingKey {
case name = "name"
case imageUrl = "imageUrl"
case tallImageUrl = "tallImageUrl"
case bgImageUrl = "bgImageUrl"
case isActive = "isActive"
}
}
// MARK: - Stats
struct Stats: Codable {
let level: ArKills?
let kills: ArKills
let damage: ArKills?
let headshots: ArKills?
let finishers: ArKills?
let arKills: ArKills?
let carePackageKills: ArKills?
let seasonWins: ArKills?
let seasonKills: ArKills?
let season2Wins: ArKills?
let rankScore: RankScore?
let smokeGrenadeEnemiesHit: ArKills?
let eyeEnemiesScanned: ArKills?
let grappleTravelDistance: ArKills?
enum CodingKeys: String, CodingKey {
case level = "level"
case kills = "kills"
case damage = "damage"
case headshots = "headshots"
case finishers = "finishers"
case arKills = "arKills"
case carePackageKills = "carePackageKills"
case seasonWins = "seasonWins"
case seasonKills = "seasonKills"
case season2Wins = "season2Wins"
case rankScore = "rankScore"
case smokeGrenadeEnemiesHit = "smokeGrenadeEnemiesHit"
case eyeEnemiesScanned = "eyeEnemiesScanned"
case grappleTravelDistance = "grappleTravelDistance"
}
}
// MARK: - ArKills
struct ArKills: Codable {
let rank: Int?
let percentile: Double?
let displayName: String
let displayCategory: DisplayCategory
let category: JSONNull?
let metadata: MetadataClass
let value: Double
let displayValue: String
let displayType: DisplayType
enum CodingKeys: String, CodingKey {
case rank = "rank"
case percentile = "percentile"
case displayName = "displayName"
case displayCategory = "displayCategory"
case category = "category"
case metadata = "metadata"
case value = "value"
case displayValue = "displayValue"
case displayType = "displayType"
}
}
enum DisplayCategory: String, Codable {
case combat = "Combat"
case game = "Game"
case weapons = "Weapons"
}
enum DisplayType: String, Codable {
case unspecified = "Unspecified"
}
// MARK: - RankScore
struct RankScore: Codable {
let rank: JSONNull?
let percentile: Int
let displayName: String
let displayCategory: DisplayCategory
let category: JSONNull?
let metadata: RankScoreMetadata
let value: Int
let displayValue: String
let displayType: DisplayType
enum CodingKeys: String, CodingKey {
case rank = "rank"
case percentile = "percentile"
case displayName = "displayName"
case displayCategory = "displayCategory"
case category = "category"
case metadata = "metadata"
case value = "value"
case displayValue = "displayValue"
case displayType = "displayType"
}
}
// MARK: - RankScoreMetadata
struct RankScoreMetadata: Codable {
let iconUrl: String
enum CodingKeys: String, CodingKey {
case iconUrl = "iconUrl"
}
}
// MARK: - UserInfo
struct UserInfo: Codable {
let isPremium: Bool
let isVerified: Bool
let isInfluencer: Bool
let countryCode: String
let customAvatarUrl: JSONNull?
let socialAccounts: JSONNull?
enum CodingKeys: String, CodingKey {
case isPremium = "isPremium"
case isVerified = "isVerified"
case isInfluencer = "isInfluencer"
case countryCode = "countryCode"
case customAvatarUrl = "customAvatarUrl"
case socialAccounts = "socialAccounts"
}
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public func hash(into hasher: inout Hasher) {
// No-op
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Code used for api call and displaying data:
let PlayerStatURL = URL(string: "https://public-api.tracker.gg/v2/apex/standard/profile/origin/itsspress")
if let unwrappedURL = PlayerStatURL {
var request = URLRequest(url: unwrappedURL)
request.addValue("db833c37-1f45-4be4-a670-38272dba7504", 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 store = try JSONDecoder().decode(Store.self, from: data) as Store
print(store.data.platformInfo.avatarUrl)
print(store.data.platformInfo.platformUserHandle)
print("Level: \(store.data.segments[0].stats.level!.displayValue)")
print("lifetime Kills: \(store.data.segments.map{$0.stats.kills.displayValue}[0])")
print(store.data.segments.map{$0.metadata.name})
print(store.data.segments.map{$0.stats.carePackageKills?.displayName}[2])
} catch {
print(error.localizedDescription)
print(error)
}
}
}
dataTask.resume()
}
I am unsure how to get this data from the above JSON. The struct only seems to let me pull, let’s say kill stats, from all then legends and doesn’t let me just pull from let’s say pathfinder.
How could I just pull stats on pathfinder?
You can achieve that as below,
do {
let store = try JSONDecoder().decode(Store.self, from: data) as Store
store.data.segments.map{$0.metadata.name}.forEach { legendName in
if let stats = store.data.segments.first(where: { $0.metadata.name == legendName })?.stats {
let killString = stats.kills.displayName + " - " + stats.kills.displayValue
var damageString: String = ""
if let damage = stats.damage {
damageString = damage.displayName + " - " + damage.displayValue
}
print(legendName + " " + killString + " " + damageString )
}
}
} catch {
print(error.localizedDescription)
print(error)
}
Output:
Lifetime Kills - 408 Damage - 74,053
Pathfinder Kills - 230 Damage - 59,694
Gibraltar Kills - 1
Bangalore Kills - 116
Octane Kills - 0
Bloodhound Kills - 6
Wraith Kills - 18 Damage - 6,357
Mirage Kills - 14 Damage - 5,307
Caustic Kills - 7 Damage - 2,695
Lifeline Kills - 16