I'm calling the API endpoint and the JSON response looks like this:
{
"id": 299536,
"cast": [
{
"cast_id": 1,
"character": "Tony Stark / Iron Man",
"credit_id": "54a9cfa29251414d5b00553d",
"gender": 2,
"id": 3223,
"name": "Robert Downey Jr.",
"order": 0,
"profile_path": "/1YjdSym1jTG7xjHSI0yGGWEsw5i.jpg"
},
{
"cast_id": 6,
"character": "Thor Odinson",
"credit_id": "54a9d012c3a3680c29005762",
"gender": 2,
"id": 74568,
"name": "Chris Hemsworth",
"order": 1,
"profile_path": "/lrhth7yK9p3vy6p7AabDUM1THKl.jpg"
},
{
"cast_id": 13,
"character": "Bruce Banner / Hulk",
"credit_id": "573fc00592514177ec00010a",
"gender": 2,
"id": 103,
"name": "Mark Ruffalo",
"order": 2,
"profile_path": "/isQ747u0MU8U9gdsNlPngjABclH.jpg"
}
]
This is the struct that I've made for the codable protocol.
struct MovieCast: Codable {
let id: Int
let cast: [Cast]
let crew: [Crew]
}
struct Cast: Codable {
let castID: Int
let character, creditID: String
let gender, id: Int
let name: String
let order: Int
let profilePath: String?
enum CodingKeys: String, CodingKey {
case castID = "cast_id"
case character
case creditID = "credit_id"
case gender, id, name, order
case profilePath = "profile_path"
}
}
struct Crew: Codable {
let creditID: String
let department: Department
let gender, id: Int
let job, name: String
let profilePath: String?
enum CodingKeys: String, CodingKey {
case creditID = "credit_id"
case department, gender, id, job, name
case profilePath = "profile_path"
}
}
enum Department: String, Codable {
case art = "Art"
case camera = "Camera"
case costumeMakeUp = "Costume & Make-Up"
case crew = "Crew"
case directing = "Directing"
case editing = "Editing"
case lighting = "Lighting"
case production = "Production"
case sound = "Sound"
case visualEffects = "Visual Effects"
case writing = "Writing"
}
My question is, how can I loop through the objects in a single array using Swift 4? I am using Decodable protocol and have struct that looks like this: (note that I let out some of the json response properties above so the snippet wouldn't be too long)
My goal is to get the
profile_path
out of every object and append it to the array of Strings.
After you get the response try this code in viewDidLoad()
if let dict = response.result.value as? [string:Anyobject]() //fetching the response
if let innerdict = dict["cast"] as? [[string:Anyobject]]()
for cast in innerdict {
if let profile_path = cast[ "profile_path"] as? String
print("profile_path") // to check the profilepath
}
// this is it to get the profile path then append it in the array of type string
Your MovieCast struct contains 2 arrays, one of Cast objects and one of Crew objects.
You could create a protocol Staff that has the shared properties from both (gender, name, profilePath), declare that both types conform to that protocol, and then combine your arrays of Cast and Crew objects into an array of Staff.
Then you could map the resulting array to an array of strings from the profilePath values of each entry in the array:
protocol Staff {
var gender: Int { get }
var name: String { get }
var profilePath: String? { get }
}
Change Cast and Crew like this:
struct Cast: Codable & Staff
struct Crew: Codable & Staff
if you have a crew:
var movieCrew: MovieCast
//Code to load a moview crew
Then
let castAndCrew = (movieCrew.cast as [Staff]) + ((movieCrew.crew as [Staff]?) ?? [])
let allProfilePaths = castAndCrew.compactMap { $0.profilePath }
(Note that I typed this into the SO editor, and my syntax is rarely perfect "out of the gate." It will likely need some minor cleanup.)
Related
This question already has answers here:
How do I use custom keys with Swift 4's Decodable protocol?
(4 answers)
Closed 1 year ago.
In my project I want to build a easy Crypto price app. So I want to get information from the Coinlore (https://www.coinlore.com/cryptocurrency-data-api) API. I made a request to the API and in the following I have to decode the JSON. But my problem is that the path to e.g. the price of the coin is "0.price_usd". But i can not create a struct named "0".
This is how the JSON looks:enter image description here
Can anyone please help me? Appreciate it!!!
Looking at their sample response
{
"data": [
{
"id": "90",
"symbol": "BTC",
"name": "Bitcoin",
"nameid": "bitcoin",
"rank": 1,
"price_usd": "6456.52",
"percent_change_24h": "-1.47",
"percent_change_1h": "0.05",
"percent_change_7d": "-1.07",
"price_btc": "1.00",
"market_cap_usd": "111586042785.56",
"volume24": 3997655362.9586277,
"volume24a": 3657294860.710187,
"csupply": "17282687.00",
"tsupply": "17282687",
"msupply": "21000000"
}],
"info": {
"coins_num": 1969,
"time": 1538560355
}
}
*note it has a closing ']' missing
you can create a swift model like so using
import Foundation
let json = "{\r\n \"data\": [\r\n {\r\n \"id\": \"90\",\r\n \"symbol\": \"BTC\",\r\n \"name\": \"Bitcoin\",\r\n \"nameid\": \"bitcoin\",\r\n \"rank\": 1,\r\n \"price_usd\": \"6456.52\",\r\n \"percent_change_24h\": \"-1.47\",\r\n \"percent_change_1h\": \"0.05\",\r\n \"percent_change_7d\": \"-1.07\",\r\n \"price_btc\": \"1.00\",\r\n \"market_cap_usd\": \"111586042785.56\",\r\n \"volume24\": 3997655362.9586277,\r\n \"volume24a\": 3657294860.710187,\r\n \"csupply\": \"17282687.00\",\r\n \"tsupply\": \"17282687\",\r\n \"msupply\": \"21000000\"\r\n }],\r\n \"info\": {\r\n \"coins_num\": 1969,\r\n \"time\": 1538560355\r\n }\r\n}"
struct Tokens: Codable {
let data: [SomeData]
}
struct SomeData: Codable {
let priceUsd: String
enum CodingKeys: String, CodingKey {
case priceUsd = "price_usd"
}
}
let tokens = try? JSONDecoder().decode(Tokens.self, from: json.data(using: .utf8)!)
print(tokens?.data.first?.priceUsd)
// edited my earlier answer, run the above it should get you your price
With Alamofire and Decodable protocol you can easily decode this:
func fetchExchangeData() {
let url = "https://api.coinlore.net/api/exchange/?id=5"
struct Root: Decodable {
let data: Datas
let pairs: [Pairs]
private enum CodingKeys : String, CodingKey {
case data = "0", pairs
}
}
struct Datas: Decodable {
let date: String
let name: String
let url: String
private enum CodingKeys : String, CodingKey {
case date = "date_live", name, url
}
}
struct Pairs: Decodable {
let base: String?
let price: Double?
let priceUsd: Double?
let quote: String?
let time: Double?
let volume: Double?
private enum CodingKeys : String, CodingKey {
case base, price, priceUsd = "price_usd", quote, time, volume
}
}
AF.request(url, method: .get).responseDecodable { (response: DataResponse<Root, AFError>) in
switch response.result {
case .success(let result):
for pair in result.pairs {
print(pair.priceUsd)
}
case .failure(let error):
print(error)
}
}
}
Just for the sample, we print out all the price_usd value. You can create a model for pairs, and save them into an array.
I am trying to parse the json. But the problem is I have json inside to it another json String. Like :
{
"count": 284,
"next": "http://X:X:X:X:8080/api/sensor/last5feed?page=2&search=XXXXX",
"previous": null,
"results": [
{
"id": 571,
"feed": "{'app_id': 'XXXXX', 'dev_id': 'XXXX', 'hardware_serial': 'XXXXX', 'port': 6, 'counter': 4290, 'payload_raw': 'AQEBIwXsF4IAAAA=', 'payload_fields': {'aamsg_type': 'weather', 'abstatus': 0, 'batteryV': 3.5, 'battery_low': 'no', 'humiP': 60.2, 'tempC': 15.2}, 'metadata': {'time': '2020-01-23T15:09:32.350967362Z', 'frequency': 868.1, 'modulation': 'LORA', 'data_rate': 'SF7BW125', 'airtime': 61696000, 'coding_rate': '4/5', 'gateways': [{'gtw_id': 'XXXXX', 'timestamp': 3227230963, 'time': '2020-01-23T15:09:32.326146Z', 'channel': 0, 'rssi': -98, 'snr': 4.8, 'rf_chain': 1, 'latitude': 57.124737, 'longitude': -2.1646452, 'altitude': 90, 'location_source': 'registry'}]}}",
"created_at": "2020-01-23T15:09:32.630326Z",
"sensor": 1
},
{
"id": 569,
"feed": "{'app_id': 'XXXXXX', 'dev_id': 'XXXX', 'hardware_serial': 'XXXX', 'port': 6, 'counter': 4289, 'payload_raw': 'XXXXX', 'payload_fields': {'aamsg_type': 'weather', 'abstatus': 0, 'batteryV': 3.5, 'battery_low': 'no', 'humiP': 57.6, 'tempC': 16.9}, 'metadata': {'time': '2020-01-23T14:09:32.132070865Z', 'frequency': 867.3, 'modulation': 'LORA', 'data_rate': 'SF7BW125', 'airtime': 61696000, 'coding_rate': '4/5', 'gateways': [{'gtw_id': 'XXXXXX', 'timestamp': 3921981659, 'time': '2020-01-23T14:09:32.104672Z', 'channel': 4, 'rssi': -107, 'snr': 8.2, 'rf_chain': 0, 'latitude': 57.124737, 'longitude': -2.1646452, 'altitude': 90, 'location_source': 'registry'}]}}",
"created_at": "2020-01-23T14:09:32.448929Z",
"sensor": 1
}
}
I am getting the values till feed. But I am not able to parse further, My code is :
if(status_code == 200){
if let json = response.data {
do{
let data = try JSON(data: json)
let result = data["results"].arrayObject! as NSArray
let ct = result.count
if(ct != 0 ) {
self.noDataFound.isHidden = true
for i in 0...ct-1 {
let data = result[i] as? NSDictionary
let feed = data?.value(forKey: "feed") as? NSString
let data3 = try JSON(data: feed as! Data) .
print(data3)
}
}
} catch {} }}
I need to get the hardware_serial from feed. Can any body please help me what i am doing wrong here!! Thanks!!!
The string for key feed is not valid JSON. You have to replace the single quotes with double quotes.
Create a Data object from the string (casting the type doesn't work).
Create a JSON object from the data.
Get the values you need.
Side note:
Don't use NS... collection types and NSString in Swift.
Use Codable to parse the above JSON response.
Models:
struct Root: Codable {
let count: Int
let next: String
let previous: String?
let results: [Result]
}
struct Result: Codable {
let id: Int
let feed, createdAt: String
let sensor: Int
}
Parse the JSON data like so,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
First, peel off the piece you want:
struct ResultWrapper: Decodable {
let results: [Result]
}
And then build a custom decoder to extract feed, which is malformed. Single-quotes are not legal JSON. As a hack, the following code just substitutes all single-quotes with double-quotes, but this won't work if there are any embedded quoted-single-quotes.
struct Result: Decodable {
enum CodingKeys: String, CodingKey { case feed }
let feed: Feed
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// inner JSON is mal-formed. Have to fix it.
let feedString = try container.decode(String.self, forKey: .feed)
.replacingOccurrences(of: "'", with: "\"")
feed = try JSONDecoder().decode(Feed.self, from: Data(feedString.utf8))
}
}
Finally, decoding Feed is mechanical, but requires custom CodingKeys. I recommend quicktype for that:
// https://app.quicktype.io?share=7O6iNJ3ugXb4mr84TIoG
struct Feed: Codable {
var appID, devID, hardwareSerial: String
var port, counter: Int
var payloadRaw: String
var payloadFields: PayloadFields
var metadata: Metadata
enum CodingKeys: String, CodingKey {
case appID = "app_id"
case devID = "dev_id"
case hardwareSerial = "hardware_serial"
case port, counter
case payloadRaw = "payload_raw"
case payloadFields = "payload_fields"
case metadata
}
}
// MARK: - Metadata
struct Metadata: Codable {
var time: String
var frequency: Double
var modulation, dataRate: String
var airtime: Int
var codingRate: String
var gateways: [Gateway]
enum CodingKeys: String, CodingKey {
case time, frequency, modulation
case dataRate = "data_rate"
case airtime
case codingRate = "coding_rate"
case gateways
}
}
// MARK: - Gateway
struct Gateway: Codable {
var gtwID: String
var timestamp: Int
var time: String
var channel, rssi: Int
var snr: Double
var rfChain: Int
var latitude, longitude: Double
var altitude: Int
var locationSource: String
enum CodingKeys: String, CodingKey {
case gtwID = "gtw_id"
case timestamp, time, channel, rssi, snr
case rfChain = "rf_chain"
case latitude, longitude, altitude
case locationSource = "location_source"
}
}
// MARK: - PayloadFields
struct PayloadFields: Codable {
var aamsgType: String
var abstatus: Int
var batteryV: Double
var batteryLow: String
var humiP, tempC: Double
enum CodingKeys: String, CodingKey {
case aamsgType = "aamsg_type"
case abstatus, batteryV
case batteryLow = "battery_low"
case humiP, tempC
}
}
I have a very long Json array that is full of items that look like this:
[
{
"id": "sm10-1",
"name": "Pheromosa & Buzzwole-GX",
"imageUrl": "https://images.pokemontcg.io/sm10/1.png",
"subtype": "TAG TEAM",
"supertype": "Pokémon",
"hp": "260",
"retreatCost": [
"Colorless",
"Colorless"
],
"convertedRetreatCost": 2,
"number": "1",
"artist": "Mitsuhiro Arita",
"rarity": "Rare Holo GX",
"series": "Sun & Moon",
"set": "Unbroken Bonds",
"setCode": "sm10",
"text": [
"When your TAG TEAM is knocked out, your opponent takes 3 Prize Cards."
],
"types": [
"Grass"
],
"attacks": [
{
"name": "Jet Punch",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "30",
"text": "This attack does 30 damage to 1 of your opponent's Benched Pokémon. (Don't apply Weakness and Resistance for Benched Pokémon.)"
},
{
"name": "Elegant Sole",
"cost": [
"Grass",
"Grass",
"Colorless"
],
"convertedEnergyCost": 3,
"damage": "190",
"text": "During your next turn, this Pokémon's Elegant Sole attack's base damage is 60."
},
{
"name": "Beast Game-GX",
"cost": [
"Grass"
],
"convertedEnergyCost": 1,
"damage": "50",
"text": "If your opponent's Pokémon is Knocked Out by damage from this attack, take 1 more Prize card. If this Pokémon has at least 7 extra Energy attached to it (in addition to this attack's cost), take 3 more Prize cards instead. (You can't use more than 1 GX attack in a game.)"
}
],
"weaknesses": [
{
"type": "Fire",
"value": "×2"
}
],
"imageUrlHiRes": "https://images.pokemontcg.io/sm10/1_hires.png",
"nationalPokedexNumber": 794
}
]
That is just one item of hundreds in the array. What I want to do is grab specific values from each item (i.e. name, imageUrl, supertype, hp, rarity, set) and send them to a struct which will then be added to an array of such structs.
What I currently have prints just prints out all of the json data and I can not figure out how to get individual data and create an array of structs for each individual card.
Here is the code I have currently:
//[TEST] READING JSON FILE LOCALLY
struct card: Decodable {
let name: String
let imageUrl: String
let supertype: String
let artist: String
let rarity: String
let set: String
let types: Array<String>
}
func loadJsonInfo() {
do{
let data = try Data.init(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json")!))
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(json)
} catch {
print(error)
}
}
Also, the json file is locally stored in my appData. Thanks in advance for your help!
Give a try to https://quicktype.io/
You put json there. And get all necessary data structures to decode json
To decode a JSON with Decodable type, you need to use JSONDecoder's decode(_:from:) method.
Update your loadJsonInfo() method to,
func loadJsonInfo() {
if let file = Bundle.main.url(forResource: "Unbroken Bonds", withExtension: "json") {
do {
let data = try Data(contentsOf: file)
let arr = try JSONDecoder().decode([Card].self, from: data)
print(arr)
} catch {
print(error)
}
}
}
Note: Use first letter capital while creating a type, i.e. use Card instead of card.
Parsing Code when you have DATA from server. I also removed force unwrapped ! so it won't crash in absence of file
func loadJsonInfo() {
if let path = Bundle.main.path(forResource: "Unbroken Bonds", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
let result = try JSONDecoder().decode(ResultElement.self, from: data)
} catch let error {
print(error)
}
}
}
Your Model
import Foundation
// MARK: - ResultElement
struct ResultElement: Codable {
let id, name: String?
let imageURL: String?
let subtype, supertype, hp: String?
let retreatCost: [String]?
let convertedRetreatCost: Int?
let number, artist, rarity, series: String?
let resultSet, setCode: String?
let text, types: [String]?
let attacks: [Attack]?
let weaknesses: [Weakness]?
let imageURLHiRes: String?
let nationalPokedexNumber: Int?
enum CodingKeys: String, CodingKey {
case id, name
case imageURL = "imageUrl"
case subtype, supertype, hp, retreatCost, convertedRetreatCost, number, artist, rarity, series
case resultSet = "set"
case setCode, text, types, attacks, weaknesses
case imageURLHiRes = "imageUrlHiRes"
case nationalPokedexNumber
}
}
// MARK: - Attack
struct Attack: Codable {
let name: String?
let cost: [String]?
let convertedEnergyCost: Int?
let damage, text: String?
}
// MARK: - Weakness
struct Weakness: Codable {
let type, value: String?
}
typealias Result = [ResultElement]
I am using codable and trying to get data from JSON response. Here, I can’t able to print particular value into viewDidload using swift 4.2 and I am using search but I want to assign filteredData with name values.
my code
struct Root: Codable {
let status: Int
let message: String
let country: [Country]
let cost: Double
let data: [Datum]
}
struct Country: Codable {
let id: Int
let master: String
let type, active: Int
}
struct Datum: Codable {
let id, userID, country, card: Int
let category: Int
let title, description: String
let cost: Double
let attachment, createDate, deviceID, appType: String
let location: String
let user: User
let appTransCard: AppTransCard
let appTransMaster: AppTransMaster
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case country, card, category, title, description, cost, attachment
case createDate = "create_date"
case deviceID = "device_id"
case appType = "app_type"
case location, user
case appTransCard = "app_trans_card"
case appTransMaster = "app_trans_master"
}
}
struct AppTransCard: Codable {
let card: String
}
struct AppTransMaster: Codable {
let master: String
}
struct User: Codable {
let firstname, lastname: String
}
I need to get values within viewdidload from Root.cost.
override func viewDidLoad() {
super.viewDidLoad()
// here I need to print Root.cost value
}
You need to create a new variable of type Root
for example var root : Root
and on viewDidLoad you make the request (decode)
depend if it's from API or local JSON file.
and then the result from the decode will save inside root variable
root = result
then you can access root.cost
I also recommend you to see this video
it will help you understand how to use decodable
New on here and to Swift so please go easy on me..
Am a bit stuck when trying to parse JSON which contains nested dictionaries. I imagine its something wrong with the data strutures I have created and I have tryed everthing to rectify but still getting the same issue.
This is the JSON api I am trying to work with:
https://api.coindesk.com/v1/bpi/currentprice.json
These are the data structures I have created to model this:
struct base: Decodable {
let disclaimer: String
let bpi: [Bpi]
}
struct Bpi: Decodable {
let USD: [USD]
}
struct USD: Decodable {
let rate_float: Float
}
And here is my code in the VC :
override func viewDidLoad() {
super.viewDidLoad()
let jsonURLString = "https://api.coindesk.com/v1/bpi/currentprice.json"
guard let url = URL(string: jsonURLString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let bitcoinData = try JSONDecoder().decode(base.self, from: data)
print(bitcoinData.bpi)
} catch {
print("error")
}
} .resume() // Fires off the session
}
I can grab the data from the disclaimer string or the other strings in the root dictionary but that is it. I cannot parse anything further with the nested dictonaries - it just throws back the catch error.
Here is the JSON:
{
"time": {
"updated": "Nov 2, 2017 06:08:00 UTC",
"updatedISO": "2017-11-02T06:08:00+00:00",
"updateduk": "Nov 2, 2017 at 06:08 GMT"
},
"disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
"chartName": "Bitcoin",
"bpi": {
"USD": {
"code": "USD",
"symbol": "$",
"rate": "6,889.4013",
"description": "United States Dollar",
"rate_float": 6889.4013
},
"GBP": {
"code": "GBP",
"symbol": "£",
"rate": "5,184.4053",
"description": "British Pound Sterling",
"rate_float": 5184.4053
},
"EUR": {
"code": "EUR",
"symbol": "€",
"rate": "5,910.4587",
"description": "Euro",
"rate_float": 5910.4587
}
}
}
Is there something I am clearly doing wrong here?
Thanks for the help in advance and sorry if my formatting sucks!
Try following model, with this it works - both bpi and USD are not arrays, just single values:
struct base: Decodable {
let disclaimer: String
let bpi: Bpi
}
struct Bpi: Decodable {
let USD: USD
}
struct USD: Decodable {
let rate_float: Float
}
Dictionaries (Dictionary<K,V>) are implicitly Decodable compliant if both generic types K and V are decodable.
Assuming you create a struct Coin for the currencies
struct Coin: Decodable {
private enum CodingKeys : String, CodingKey {
case code, symbol, rate, description, rateFloat = "rate_float"
}
let code : String
let symbol : String
let rate : String
let description : String
let rateFloat : Float
}
you can easily decode the currency dictionaries as [String:Coin] without any additional code
struct Base: Decodable {
private enum CodingKeys : String, CodingKey {
case disclaimer, coins = "bpi"
}
let disclaimer: String
let coins: [String:Coin]
}
And use it
let bitcoinData = try JSONDecoder().decode(Base.self, from: data)
print(bitcoinData.coins)
Alternatively if you want the currencies as an array of Coin you can write a custom initializer and map the dictionary values to an array.
This example decodes also the updatedISO value in the time dictionary
struct Base: Decodable {
struct Time : Decodable {
private enum CodingKeys : String, CodingKey {
case updated = "updatedISO"
}
let updated : Date
}
private enum CodingKeys : String, CodingKey {
case disclaimer, bpi, time
}
let disclaimer: String
let coins: [Coin]
let updated : Date
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
disclaimer = try container.decode(String.self, forKey: .disclaimer)
let bpi = try container.decode([String:Coin].self, forKey: .bpi)
coins = Array(bpi.values.sorted(by: {$0.code < $1.code}))
let time = try container.decode(Time.self, forKey: .time)
updated = time.updated
}
}
And use this example
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let bitcoinData = try decoder.decode(Base.self, from: data)
print(bitcoinData.coins)
You declared your bpi & use properties as arrays but they were dictionaries (nested json objects). If you have sample JSON you can try this converter next time: https://danieltmbr.github.io/JsonCodeGenerator/
It generates the following output:
struct Root: Codable {
let time: Time
let disclaimer: String
let chartName: String
let bpi: Bpi
}
struct Time: Codable {
let updated: String
let updatedISO: String
let updateduk: String
}
struct Bpi: Codable {
let USD: USD
let GBP: USD
let EUR: USD
}
struct USD: Codable {
let code: String
let symbol: String
let rate: String
let description: String
let rateFloat: Double
private enum CodingKeys: String, CodingKey {
case code
case symbol
case rate
case description
case rateFloat = "rate_float"
}
}