How to get JSON array value in Swift using Codable - arrays

I'm having trouble getting the direction values from the following JSON:
"routeOptions": [
{
"name": "Jubilee",
"directions": [
"Wembley Park Underground Station",
"Stanmore Underground Station"
],
"lineIdentifier": {
"id": "jubilee",
"name": "Jubilee",
"uri": "/Line/jubilee",
"type": "Line",
"routeType": "Unknown",
"status": "Unknown"
}
}
]
I believe the directions is a JSON array, which at the moment I'm using Codable as below. I've managed to get the routeOptions name but can't seem to figure out how to get the directions as there's no specific key variable. Please can someone help?
struct RouteOptions: Codable {
let name: String?
let directions: [Directions]?
init(name: String, directions: [Directions]) {
self.name = name
self.directions = directions
}}
struct Directions: Codable {}

You need to handle directions as an array of String
struct RouteOptions: Codable {
let name: String
let directions: [String]
}
Here is an example where I fixed the json to be correct
let data = """
{ "routeOptions": [
{
"name": "Jubilee",
"directions": [
"Wembley Park Underground Station",
"Stanmore Underground Station"
],
"lineIdentifier": {
"id": "jubilee",
"name": "Jubilee",
"uri": "/Line/jubilee",
"type": "Line",
"routeType": "Unknown",
"status": "Unknown"
}
}
]}
""".data(using: .utf8)!
struct Root: Decodable {
let routeOptions: [RouteOptions]
}
struct RouteOptions: Codable {
let name: String
let directions: [String]
}
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result.routeOptions)
} catch {
print(error)
}

Related

Assigning Alamofire result to array

I am trying to assign the result that is retrieved by Alamofire to an array, and have come across an issue.
I am using the Stripe API for products, it returns back a JSON object called "data:", I am trying to assign that data object only to that products array I have.
Here's my code
var products: [Product] = []
let stripeProducts = "stripe-products-api"
func createArray() {
let stripeAuthHeader: HTTPHeaders = []
AF.request(stripeProducts, headers: stripeAuthHeader).responseJSON {
switch response.result {
case .failure(let error):
print(error)
case .success:
self.products = response.data \\ trying to access the data object from the JSON data
print(self.products)
}
}
}
JSON:
"object": "list",
"data": [
{
"id": "prod_123456",
"object": "product",
"active": true,
"attributes": [
],
"created": 1590423835,
"description": "Test",
"images": [
""
],
"livemode": false,
"metadata": {
"address": "Test"
},
"name": "Blue Jeans",
"statement_descriptor": null,
"type": "service",
"unit_label": null,
"updated": 1590653248
}
]
Thank you for your help.
You need to have Struct of Products
struct Datum: Decodable {
let id, object: String
let active: Bool
let created: Int
let datumDescription: String
let images: [String]
let livemode: Bool
let metadata: Metadata
let name: String
let type: String
let updated: Int
enum CodingKeys: String, CodingKey {
case id, object, active, created
case datumDescription = "description"
case images, livemode, metadata, name
case type
case updated
}
}
// MARK: - Metadata
struct Metadata: Decodable {
let address: String
}
Then parse it like this
let products = try? newJSONDecoder().decode(Products.self, from: response.data)

Decode a JSON array

I'm just learning the Swift Decodable protocol and am running into a problem. I am able to decode one json object into a swift object, but am stuck with decoding an array.
What goes well:
imagine following json:
let json = """
{
"all" : {
"_id": "59951d5ef2db18002031693c",
"text": "America’s cats, including housecats that adventure outdoors and feral cats, kill between 1.3 billion and 4.0 billion birds in a year.",
"type": "cat",
"user": {
"_id": "5a9ac18c7478810ea6c06381",
"name": {
"first": "Alex",
"last": "Wohlbruck"
}
},
"upvotes": 4,
"userUpvoted": null
}
}
"""
let jsonData = json.data(using: .utf8)
I can decode it to a Fact object with following code:
enum Type: String, Decodable {
case cat = "cat"
}
struct Fact {
let id: String
let text: String
let type: Type
let upvotes: Int
enum CodingKeys: CodingKey {
case all
}
enum FactKeys: CodingKey {
case _id, text, type, upvotes
}
}
extension Fact: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let allContainer = try container.nestedContainer(keyedBy: FactKeys.self, forKey: .all)
id = try allContainer.decode(String.self, forKey: ._id)
text = try allContainer.decode(String.self, forKey: .text)
type = try allContainer.decode(Type.self, forKey: .type)
upvotes = try allContainer.decode(Int.self, forKey: .upvotes)
}
}
let decoder = JSONDecoder()
let fact = try decoder.decode(Fact.self, from: jsonData!)
But the api is giving me an array of objects:
let json = """
{
"all": [
{
"_id": "59951d5ef2db18002031693c",
"text": "America’s cats, including housecats that adventure outdoors and feral cats, kill between 1.3 billion and 4.0 billion birds in a year.",
"type": "cat",
"user": {
"_id": "5a9ac18c7478810ea6c06381",
"name": {
"first": "Alex",
"last": "Wohlbruck"
}
},
"upvotes": 4,
"userUpvoted": null
},
{
"_id": "5b01a447c6914f0014cc9a30",
"text": "The special sensory organ called the Jacobson's organ allows a cat to have 14 times the sense of smell of a human. It consists of two fluid-filled sacs that connect to the cat's nasal cavity and is located on the roof of their mouth behind their teeth.",
"type": "cat",
"user": {
"_id": "5a9ac18c7478810ea6c06381",
"name": {
"first": "Alex",
"last": "Wohlbruck"
}
},
"upvotes": 4,
"userUpvoted": null
}
]
}
"""
let jsonData = json.data(using: .utf8)
And I want to store that in an allFacts array that hold my Fact objects
class Facts: ObservableObject {
#Published var allFacts = [Fact]()
}
let decoder = JSONDecoder()
let allFacts = try decoder.decode([Fact].self, from: jsonData!)
I'm using the same extension on my Fact struct. But it's giving me an error and I am totally lost for a second. Any idea how I can solve this ?
Do I need to create codingKeys for the class as well ?
Expected to decode Array<Any> but found a dictionary instead."
I recommend not to mess around with nested containers. This is less efficient than the default stuff. In your case you would have to use nestedUnkeyedContainer and iterate the array which is still more expensive.
Instead just add a root struct
let json = """
{
"all": [
{
"_id": "59951d5ef2db18002031693c",
"text": "America’s cats, including housecats that adventure outdoors and feral cats, kill between 1.3 billion and 4.0 billion birds in a year.",
"type": "cat",
"user": {
"_id": "5a9ac18c7478810ea6c06381",
"name": {
"first": "Alex",
"last": "Wohlbruck"
}
},
"upvotes": 4,
"userUpvoted": null
},
{
"_id": "5b01a447c6914f0014cc9a30",
"text": "The special sensory organ called the Jacobson's organ allows a cat to have 14 times the sense of smell of a human. It consists of two fluid-filled sacs that connect to the cat's nasal cavity and is located on the roof of their mouth behind their teeth.",
"type": "cat",
"user": {
"_id": "5a9ac18c7478810ea6c06381",
"name": {
"first": "Alex",
"last": "Wohlbruck"
}
},
"upvotes": 4,
"userUpvoted": null
}
]
}
"""
let jsonData = Data(json.utf8)
enum Type: String, Decodable {
case cat
}
struct Root : Decodable {
let all : [Fact]
}
struct Fact : Decodable {
let id: String
let text: String
let type: Type
let upvotes: Int
enum CodingKeys : String, CodingKey {
case id = "_id", text, type, upvotes
}
}
let decoder = JSONDecoder()
let root = try decoder.decode(Root.self, from: jsonData)
print(root.all)

I don't understand why my code works for an array of my struct but not with a struct containing an array of this struct?

So basically I'm trying to read a local json file about some spendings. I have a struct "Spending" and a struct "Spendings" that holds an array of Spending. I can't access the data from my json when I decode with the type Spendings.
I tried to decode with [Spending.self] which is working but I want to use my struct Spendings and I can't figure why it doesn't work?
[
{
"id": 1,
"name": "Métro 052",
"price": 8.97,
"date": "22/07/2019",
"category": "Transport"
},
{
"id": 2,
"name": "National Geographic Museum",
"price": 10.77,
"date": "22/07/2019",
"category": "Museum"
}
]
enum Categories: String, Codable {
case Transport
case Food
case Museum
case Mobile
case Housing
case Gifts
case Shopping
}
struct Spending: Codable {
var id: Int
var name: String
var price: Float
var date: String
var category: Categories
}
struct Spendings: Codable {
let list: [Spending]
}
//Not working
class SpendingController {
static let shared = SpendingController()
func fetchSpendings(completion: #escaping ([Spending]?) -> Void) {
if let filepath = Bundle.main.path(forResource: "spending", ofType: "json") {
let jsonDecoder = JSONDecoder()
if let data = try? Data(contentsOf: URL(fileURLWithPath: filepath)), let spendings = try? jsonDecoder.decode(Spendings.self, from: data) {
completion(spendings.list)
}
}
}
}
//Working
class SpendingController {
static let shared = SpendingController()
func fetchSpendings(completion: #escaping ([Spending]?) -> Void) {
if let filepath = Bundle.main.path(forResource: "spending", ofType: "json") {
let jsonDecoder = JSONDecoder()
if let data = try? Data(contentsOf: URL(fileURLWithPath: filepath)), let spendings = try? jsonDecoder.decode([Spending].self, from: data) {
completion(spendings)
}
}
}
}
I don't have any error messages but in my completion when I print the result nothing is printed contrary to when I use [Spending].self.
Decoding a [Spending].self is indeed correct here because the root of your JSON is an array, which means that the type you use to decode should be [XXX].self.
Decoding a Spendings.self would be incorrect here because it would mean that you are a decoding an object root, as opposed to an array root. The Spendings struct has a single property list, so the JSON's root object would need to have a key of "list" in order for decoding Spendings.self to work correctly, like this:
{
"list":
[
{
"id": 1,
"name": "Métro 052",
"price": 8.97,
"date": "22/07/2019",
"category": "Transport"
},
{
"id": 2,
"name": "National Geographic Museum",
"price": 10.77,
"date": "22/07/2019",
"category": "Museum"
}
]
}

Swift - Use dictionary to pass data to params

I'm trying to pass some datas in my parameters to send to a server. But I'm facing some difficulties to build the params.
I have tried to loop through the array to add the dictionary which consists of the mobile and name but I'm not sure on how to build it properly as it return only first element of the array. I don't know how to append everything in the array.
var parameters: [String: Any] = ["invitations": [
["mobile": "1234567",
"name": "John1"]
]
]
for (item1, item2) in zip(arrName, arrNumber){
parameters = ["invitations": [
"mobile" : "\(item2)",
"name" : "\(item1)"]
]
}
This is the JSON I'm trying to build in the params.
{
"invitations": [
{
"mobile": "1234456",
"name": "Paul"
},
{
"mobile": "1234456",
"name": "Paul1"
},
{
"mobile": "1234456",
"name": "Paul2"
}
]
}
let arr = zip(arrNumber,arrName).map { ["mobile":$0,"name":$1] }
var parameters: [String: Any] = ["invitations": arr]
print(parameters)//["invitations": [["name": "Paul", "mobile": "1234456"], ["name": "Paul1", "mobile": "1234456"], ["name": "Paul2", "mobile": "1234456"]]]
do {
let json = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
let convertedString = String(data: json, encoding: .utf8)
print(convertedString)
} catch let error {
print(error.localizedDescription)
}
{ "invitations": [
{
"name": "Paul",
"mobile": "1234456"
},
{
"name": "Paul1",
"mobile": "1234456"
},
{
"name": "Paul2",
"mobile": "1234456"
} ] }

Swift Codable: Array of Dictionaries

I have a JSON object from Yelp and I cannot figure out how to access the title in categories using Swift Codable. This is the JSON (with some elements removed for ease of reading):
{
"businesses": [
{
"id": "fob-poke-bar-seattle-2",
"name": "FOB Poke Bar",
"is_closed": false,
"review_count": 421,
"categories": [
{
"alias": "poke",
"title": "Poke"
},
{
"alias": "salad",
"title": "Salad"
},
{
"alias": "hawaiian",
"title": "Hawaiian"
}
],
"rating": 5,
"coordinates": {
"latitude": 47.6138005187095,
"longitude": -122.343868017197
},
"price": "$$",
"location": {
"city": "Seattle",
"zip_code": "98121",
"country": "US",
"state": "WA",
"display_address": [
"220 Blanchard St",
"Seattle, WA 98121"
]
},
}
Here it is in JSON Viewer
I access first level properties like name easily, and I access lattitude and longitude like so:
class Business: Codable {
var name: String
var rating: Double
var coordinates: Coordinates
struct Coordinates: Codable {
var latitude: Double
var longitude: Double
init(lat: Double, long: Double) {
self.latitude = lat
self.longitude = long
}
}
init(name: String, rating: Double, coordinates: Coordinates) {
self.name = name
self.rating = rating
self.coordinates = coordinates
self.categories = categories
}
}
Could anyone please point me in the right direction towards accessing categories -> title? Coordinates was easy to access but categories is an array of dictionaries. Thank you!
It's the same pattern like Coordinates except the value for categories is an array:
struct Root : Decodable {
let businesses : [Business]
}
struct Business: Decodable {
let name: String
let rating: Int
let coordinates: Coordinates
let categories : [Category]
struct Coordinates: Codable {
let latitude: Double
let longitude: Double
}
struct Category: Codable {
let alias: String
let title: String
}
}
let root = try decoder.decode(Root.self, from: data)
for business in root.businesses {
for category in business.categories {
print(category.title)
}
}

Resources