I have a JSON object below that uses everything from Strings, Bools and Int's. I'm currently having a difficult time recreating the person_details section of the object and I think because it's in brackets and has multiple values, like [String: Bool], [String: String] & [String: Int] ?
I posted towards the bottom what populates on the console, but any help structuring there person_details section in the would be great.
You'll see below, in my let order, I'm structuring the data.
let testJson = """
{
"household": {
"region": "PA",
"household_size": 1,
"receiving_benefits": [
],
"energy_crisis": false,
"utility_providers": [
"peco"
],
"residence_type": "other",
"property_tax_past_due": false,
"home_needs_repairs": false,
"filed_previous_year_tax_return": false,
"heating_system_needs_repairs": false,
"at_risk_of_homelessness": false,
"received_maximum_benefit": {
"cip": false
},
"person_details": [
{
"age": 18,
"marital_status": "single",
"minimum_employment_over_extended_period": false,
"work_status": "recent_loss",
"pregnant": false,
"attending_school": false,
"disabled": false
}
],
"incomes": [
{
"gross_monthly_amount": 700,
"countable_group": "household",
"year": "current"
},
{
"gross_monthly_amount": 700,
"countable_group": "household",
"year": "previous"
}
],
"assets": [
{
"amount": 1000,
"countable_group": "household"
}
]
}
}
"""
struct Eligibility: Encodable {
let residence: String
let hhmembers: Int
let receivingBen: [String]
let unhoused: Bool
let utilityType: [String]
let residenceType: String
let propertyTax: Bool
let homeRepairs: Bool
let fileLastTax: Bool
let heatRepairs: Bool
let receivingMax: [String: Bool]
enum CodingKeys: String, CodingKey {
case residence = "region"
case hhmembers = "household_size"
case receivingBen = "receiving_benefits"
case unhoused = "at_risk_of_homelessness"
case utilityType = "utility_providers"
case residenceType = "residence_type"
case propertyTax = "property_tax_past_due"
case homeRepairs = "home_needs_repairs"
case fileLastTax = "filed_previous_year_tax_return"
case heatRepairs = "heating_system_needs_repairs"
case receivingMax = "received_maximum_benefit"
}
}
struct PersonDetails: Encodable {
let age: Int
// let marital_status: String
// let minimum_employment_over_extended_period: Bool
// let work_status: String
// let pregnant: Bool
// let attending_school: Bool
// let disabled: Bool
enum CodingKeys: String, CodingKey {
case age = "age"
// case marital_status = "marital_status"
// case minimum_employment_over_extended_period = "minimum_employment_over_extended_period"
// case work_status = "work_status"
// case pregnant = "pregnant"
// case attending_school = "attending_school"
// case disabled = "disabled"
}
}
I believe What I'm missing is inside the let order = , see below:
struct Order: Encodable {
let household: Eligibility
let person_details: PersonDetails
}
let order = Order(household: Eligibility(residence: "PA", hhmembers: 1, receivingBen: [], unhoused: false, utilityType: ["Peco"], residenceType: "other", propertyTax: false, homeRepairs: false, fileLastTax: false, heatRepairs: false, receivingMax: ["cip": false]), person_details: PersonDetails(age: 19))
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let orderJsonData = try! encoder.encode(order)
print(String(data: orderJsonData, encoding: .utf8)!)
Inside the Console shows that person_details in outside of 'household' but I would need the person_details inside of the household object as the above full JSON object shows at the top of the question (note square brackets too). Console below:
{
"household" : {
"region" : "PA",
"residence_type" : "other",
"at_risk_of_homelessness" : false,
"property_tax_past_due" : false,
"utility_providers" : [
"Peco"
],
"home_needs_repairs" : false,
"filed_previous_year_tax_return" : false,
"household_size" : 1,
"receiving_benefits" : [
],
"heating_system_needs_repairs" : false,
"received_maximum_benefit" : {
"cip" : false
}
},
"person_details" : {
"age" : 19
}
}
You've got the structure of your data hierarchy wrong converting from JSON to swift.
It should be...
struct Order: Codable {
let household: Household
}
struct Household: Codable {
let personDetails: [Person]
}
struct Person: Codable {
let age: Int
let maritalStatus: String
let minimumEmploymentOverExtendedPeriod: Bool
let workStatus: String
let pregnant: Bool
let attendingSchool: Bool
let disabled: Bool
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let order = try! decoder.decode(Order.self, from: Data(testJson.utf8))
returns
Person(age: 18, maritalStatus: "single",
minimumEmploymentOverExtendedPeriod: false, workStatus: "recent_loss",
pregnant: false, attendingSchool: false, disabled: false)]
Also worth pointing out is the use of the .keyDecodingStrategy to ease the converting from snake case. This saves defining the CodingKeys. Obviously this will only work where you're happy to keep the naming the same.
Related
I am trying to write to a Realm DB from JSON file (using swiftyJSON) get the following error:
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'RLMException', reason: 'Invalid value '{
"vehicle" : true,
"viewable" : true,
"id" : 0,
"weapon" : false,
"genres" : "[Fantasy, General]",
"name" : "Car"
}' to initialize object of type 'Item': missing key 'id''
My JSON file is structured as follows:
[
{
"id": 0,
"name": "Car",
"genres": "[Fantasy, General]",
"viewable": true,
"weapon": false,
"vehicle": true
},
{
"id": 1,
"name": "Truck",
"genres": "[General]",
"viewable": true,
"weapon": false,
"vehicle": true
},
]
My Realm DB Class is:
class Item: Object {
#objc dynamic var id: Int = 0
#objc dynamic var name = ""
let genres = List<String>()
#objc dynamic var visable: Bool = false
#objc dynamic var weapon: Bool = false
#objc dynamic var vehicle: Bool = false
override static func primaryKey() -> String? {
return "id"
}
override static func indexedProperties() -> [String] {
return ["genre", "visable"]
}
}
and the code to write the JSON to the RealmDB is as follows:
func jsonAdd() {
if let path = Bundle.main.path(forResource: "Data", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let json = JSON(data)
for (index,subJson):(String, JSON) in json {
do {
try realm.write {
realm.create(Item.self, value: subJson, update: .modified)
}
} catch let error as NSError {
print("Unable to write")
print(error)
}
}
} catch {
print("Error")
}
}
}
I think the issue is a JSON mapping one but any help will be greatly appreciated to write the JSON to the Realm DB including the genres as a List.
NB: I've managed to write to the Realm DB using the following code (based off the Realm documentation) before the 'Write' function but cannot seem to get it to work for my JSON structure.
let newdata = "{\"id\": 0, \"name\": \"Car\", \"genres\": [\"Fantasy\",\"General\"], \"viewable\": true, \"weapon\": false, \"vehicle\": true}".data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: newdata, options: [])
Thank you!
Solved through the following approach:
Slight change to my JSON structure as follows:
[
{
"id": 0,
"name": "Car",
"genres": ["Fantasy", "General"],
"viewable": true,
"weapon": false,
"vehicle": true
},
{
"id": 1,
"name": "Truck",
"genres": ["Fantasy", "General"],
"viewable": true,
"weapon": false,
"vehicle": true
}
]
Created a new Genres Object to better handle the list
class Genres: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
override static func primaryKey() -> String? {
return "id"
}
}
class Item: Object {
#objc dynamic var id = 0
#objc dynamic var name = ""
var genres = List<Genres>()
#objc dynamic var viewable: Bool = false
#objc dynamic var weapon: Bool = false
#objc dynamic var vehicle: Bool = false
override static func primaryKey() -> String? {
return "id"
}
override static func indexedProperties() -> [String] {
return ["genres", "viewable"]
}
}
3)Crucially in the jsonADD function updated the code to create a new Item Object then mapped the JSON values to that object before attempting to write to the Realm DB:
for (key,subJson):(String, JSON) in jsonObjects {
let thisItem = Item()
thisItem.id = subJson["id"].intValue
thisItem.name = subJson["name"].stringValue
thisItem.visable = subJson["viewable"].boolValue
thisItem.weapon = subJson["weapon"].boolValue
thisItem.vehicle = subJson["vehicle"].boolValue
let genreArray = subJson["genres"].arrayValue
for genre in genreArray {
let string = genre.stringValue
let predicate = NSPredicate(format: "name = %#", string)
if let foundGenre = realm.objects(Genres.self).filter(predicate).first {
thisItem.genres.append(foundGenre)
}
}
do {
try realm.write {
realm.create(Item.self, value: thisItem, update: .modified)
}
} catch let error as NSError {
print("Unable to write")
print(error)
}
}
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.
I'm accessing the data from an API with XCode(10.2.1) and Swift(5.0) and ran into a problem I cannot seem to find the answer to. I am able to get data from all other parts of the API apart from one, which has been named as a number string "750", im not sure how to grab that data when I can't use a variable that jsonDecoder can read?
This is an example of what I know won't work but gives you an idea of what I'm trying to do.
class Images: Codable {
let "750": String
init("750": String){
self."750" = "750"
}
}
Here's a snippet from the API I'm trying to get the images from:
"id": "069f7f26",
"sku": "AP",
"title": "Pizza",
"description": "A really great pizza",
"list_price": "9.95",
"is_vatable": true,
"is_for_sale": false,
"age_restricted": false,
"box_limit": 2,
"always_on_menu": false,
"volume": null,
"zone": null,
"created_at": "2017-03-06T10:52:43+00:00",
"attributes": [
{
"id": "670f0e7c",
"title": "Allergen",
"unit": null,
"value": "Products manufactured in a nut environment"
},
{
"id": "c29e7",
"title": "Weight",
"unit": "g",
"value": "300"
}
],
"tags": [
],
"images": {
"750": {
"src": "https:\/\/some_website.co.uk\/cms\/product_image\some_image.jpg",
"url": "https:\/\/some_website.co.uk\/cms\/product_image\some_image.jpg",
"width": 750
}
}
},
I setup an example that better match your situation in order to give you an overview on how to parse and access your JSON information dynamically with a Dictionary data type:
import Foundation
let jsonData = """
{
"images": {
"750": {
"src": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"url": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"width": 750
}
}
}
"""
let json = jsonData.data(using: .utf8)!
public struct Results: Codable {
public var images: [String:Image] = [:]
enum CodingKeys: String, CodingKey {
case images = "images"
}
}
public struct Image: Codable {
public var src: String = ""
public var url: String = ""
public var width: Int = 0
enum CodingKeys: String, CodingKey {
case src = "src"
case url = "url"
case width = "width"
}
}
if let results = try? JSONDecoder().decode(Results.self, from: json) {
let imagesDict = results.images
for (key, value) in imagesDict {
print("Key: \(key)")
print("Value: \(value)")
}
}
If you try this snippet it will give you this output printed:
Key: 750
Value: Image(src: "https://some_website.co.uk/cms/product_image/some_image.jpg", url: "https://some_website.co.uk/cms/product_image/some_image.jpg", width: 750)
You can try out the snippet above online, if you copy paste it here and run it: http://online.swiftplayground.run/
### UPDATE (in response to comment)
In response to your comment, I found it easier to setup another example to show you how you can achieve that with your exact code sample that you shared in the comment itself.
I left everything as class and just added images in order to leave you an overview on how to achieve that.
In the end, I'd suggest to rename Products and Attributes into Product and Attribute. Also if there is no strong reason on why you choosed the model to be class, just change them to struct and as well if there is no strong reasons to keep most of the attributes of each model optional give them a default value as I did in the example above if you are always expecting some values/attributes to be there.
You can try and run this snippet as well in http://online.swiftplayground.run to try it out:
import Foundation
let jsonData = """
{
"data": [
{
"title": "titlex",
"description": "descx",
"list_price": "123,456",
"attributes": [
{
"title": "titlex",
"unit": "unitx",
"value": "valuex"
}
],
"images": {
"750": {
"src": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"url": "https:\\/\\/some_website.co.uk/cms/product_image/some_image.jpg",
"width": 750
}
}
}
]
}
"""
let json = jsonData.data(using: .utf8)!
class AllProducts: Codable {
let data: [Products]
init(data: [Products]) {
self.data = data
}
}
class Products: Codable {
let title: String?
let description: String?
let list_price: String?
let attributes: [Attributes]?
let images: [String:Image]?
init(title: String, description: String, list_price: String, attributes: [Attributes], images: [String:Image]) {
self.title = title
self.description = description
self.list_price = list_price
self.attributes = attributes
self.images = images
}
}
class Attributes: Codable {
let title: String?
let unit: String?
let value: String?
init(title: String, unit: String, value: String) {
self.title = title
self.unit = unit
self.value = value
}
}
class Image: Codable {
let src: String?
let url: String?
let width: Int?
init(src: String, url: String, width: Int) {
self.src = src
self.url = url
self.width = width
}
}
// parsing/decoding
if let results = try? JSONDecoder().decode(AllProducts.self, from: json) {
if let imagesDict = results.data[0].images {
// there is an "images" for product at position 0 (the only one in my json example)
for (key, value) in imagesDict {
print("Key: \(key)")
print("Value src: \(value.src)")
print("Value url: \(value.url)")
print("Value width: \(value.width)")
}
}
}
Output
Key: 750
Value src: Optional("https://some_website.co.uk/cms/product_image/some_image.jpg")
Value url: Optional("https://some_website.co.uk/cms/product_image/some_image.jpg")
Value width: Optional(750)
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