I have a simple array that I want to decode. I can deal with arrays in a JSON format with creating a new struct in the model class, and attributing that struct as an array in the main struct. But in this case, the json data is already on an array with 1 element. Therefore I should get the first element in the json response. I think I need to provide a decoder before I can access anything, but I don't know how that decoder model should be. The error I'm getting is:
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
JSON data I want to decode: (notice the data is on a array)
[
{
"name": "United States",
"topLevelDomain": [
".us"
],
"alpha2Code": "US",
"alpha3Code": "USA",
"callingCodes": [
"1"
],
"capital": "Washington, D.C.",
"altSpellings": [
"US",
"USA",
"United States of America"
],
"region": "Americas",
"subregion": "Northern America",
"population": 321645000,
"latlng": [
38,
-97
],
"demonym": "American",
"area": 9629091,
"gini": 48,
"timezones": [
"UTC-12:00",
"UTC-11:00",
"UTC-10:00",
"UTC-09:00",
"UTC-08:00",
"UTC-07:00",
"UTC-06:00",
"UTC-05:00",
"UTC-04:00",
"UTC+10:00",
"UTC+12:00"
],
"borders": [
"CAN",
"MEX"
],
"nativeName": "United States",
"numericCode": "840",
"currencies": [
"USD",
"USN",
"USS"
],
"languages": [
"en"
],
"translations": {
"de": "Vereinigte Staaten von Amerika",
"es": "Estados Unidos",
"fr": "États-Unis",
"ja": "アメリカ合衆国",
"it": "Stati Uniti D'America"
},
"relevance": "3.5"
} ]
model class:
struct CountryModel: Codable {
let country: [MyResponse]
}
struct MyResponse: Codable {
let name: String
let capital: String
}
Manager class:
struct CountryManager {
let countryURL = "https://restcountries-v1.p.rapidapi.com/name/"
func fetchData(_ countryName: String) {
let urlString = "\(countryURL)\(countryName)"
print(urlString)
performRequest(urlString)
}
func performRequest(_ urlString: String){
if let url = URL(string: urlString){
var request = URLRequest(url:url)
request.setValue("x-rapidapi-key", forHTTPHeaderField: "myapikey")
let session = URLSession(configuration: .default)
let task = session.dataTask(with: request) { (data, response, error) in
if let e = error {
print(e)
return
}
if let safeData = data {
self.parseJSON(safeData)
}
}
task.resume()
}
}
func parseJSON(_ countryData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([CountryModel].self, from: countryData)
print(decodedData[0].country)
}
catch {
print(error)
}
}
}
Most of the fields are missing from your model. Here is how it should look like instead:
struct Country: Codable {
let name: String
let topLevelDomains: [String]
let alpha2Code: String
let alpha3Code: String
let callingCodes: [String]
let capital: String
let altSpellings: [String]
let region: String
let subregion: String
let population: Int
let latlng: [Int]
let demonym: String
let area: Int
let gini: Int
let timezones: [String]
let borders: [String]
let nativeName: String
let numericCode: String
let currencies: [String]
let languages: [String]
let translation: Translation
let relevance: String
}
struct Translation: Codable {
let de: String
let es: String
let fr: String
let ja: String
let it: String
}
I found out that there was a problem in my http request. I used Alamofire in the request part and I don't experience the problem anymore. The issue seemed to be related to the decoding but I don't know. I'm posting the final code if anyone experiences the same issue.
import Foundation
import Alamofire
struct CountryManager {
let countryURL = "https://restcountries-v1.p.rapidapi.com/name/"
func fetchData(_ countryName: String) {
let urlString = "\(countryURL)\(countryName)"
print(urlString)
performRequest(urlString)
}
func performRequest(_ urlString: String){
let headers: HTTPHeaders = [
"x-rapidapi-host": "restcountries-v1.p.rapidapi.com",
"x-rapidapi-key": "apices"
]
AF.request(urlString, headers: headers).responseJSON { response in
self.parseJSON(response.data!)
}
}
func parseJSON(_ countryData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CountryAlias.self, from: countryData)
print(decodedData[0].name)
}
catch {
print(error)
}
}
}
Related
I'm trying to access an array response within array this is how I'm getting the friends array successfully
let url = URL(string: "http://xyz/api/get-friends-in-meetings")
AF.request(url!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON{ response in
switch response.result {
case .success:
let responseValue = response.value as! NSDictionary
let friends: [NSDictionary] = responseValue["friends"] as! [NSDictionary]
//here I want to store the "meeting array so that I can use it later"
break
case .failure(let error):
print(error)
break
}
}
This is the JSON response
{
"status": 0,
"message": "Friends found.",
"friends": [
{
"id": 24,
"name": "XYZ",
"email": "Z#Y.Z",
"meetings": [
{
"meeting_with": "X",
"meeting_person_screen_name": "Y",
"meeting_purpose": "Z",
}
]
}
]
}
how do I get the "meetings" array which is within the "friends" array? I have tried the same way as I did for friends but it show an error No exact matches in call to subscript
First you need to define these structs:
struct DataResult : Decodable {
let status: Int
let message: String
let friends: [FriendsResult]
}
struct FriendsResult : Decodable {
let id: Int
let name: String
let email: String
let meetings: [MeetingsResult]
}
struct MeetingsResult: Decodable {
let meeting_with: String
let meeting_person_screen_name: String
let meeting_purpose: String
}
So after that we need the JSON Example:
// Meetings
let meeting1: [String: Any] = [
"meeting_with": "X",
"meeting_person_screen_name": "Y",
"meeting_purpose": "Z"
]
// Friends
let friend1: [String: Any] = [
"id" : 24,
"name": "XYZ",
"email": "x#y.z",
"meetings": [meeting1]
]
let friend2: [String: Any] = [
"id" : 25,
"name": "John Doe",
"email": "jd#x.doe",
"meetings": [meeting1]
]
// Main JSON
let jsonExample: [String : Any] = [
"status": 0,
"message": "Friends found.",
"friends": [friend1, friend2]
]
Well, we continue to validate the JSON and decode to "DataResult":
let valid = JSONSerialization.isValidJSONObject(jsonExample)
if valid {
do {
let dataResult = try JSONSerialization.data(withJSONObject: jsonExample, options: JSONSerialization.WritingOptions())
let dataDecode = try JSONDecoder().decode(DataResult.self, from: dataResult)
let jsonString = String(data: dataResult, encoding: .utf8)
print("JSON: \(String(describing: jsonString))")
if dataDecode.friends.count > 0 {
// Get first friend you should use guard
let friend = dataDecode.friends[0]
let meeting = friend.meetings[0]
print("\(friend.name)")
print("\(meeting.meeting_with)")
}
}
catch let error {
print("ERROR: \(error.localizedDescription)")
}
}
else {
print("Invalid JSON")
}
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)
I am working on deserializing JSON with SwiftyJSON and Swift 5.1.
I have a set of data like below
"name": "American Standard",
"number": 1,
"subcategories": [
{
"name": "American Light Lager",
"letter": "A",
"guidelines": {
"overallImpression": "Some String",
"aroma": "Some String",
"appearance": "Some String",
"flavor": "Some String",
"mouthfeel": "Some String",
"comments": "Some String",
"history": "Some String",
"ingredients": "Some String",
"comparison": "Some String",
"vitalStatistics": {
"og": "1.028 - 1.040",
"fg": "0.998 - 1.008",
"abv": "2.8 - 4.2%",
"ibu": "8 - 12",
"srm": "2 - 3"
}
},
"commercialExamples": [
{
"name": "name1"
},
{
"name": "name2"
},
{
"name": "name3"
},
],
"tags": [
{
"tag": "tag1"
},
{
"tag": "tag2"
},
]
},
I am using struct to hold all the data, shown below.
struct Beers
{
var name: String
var number: Int
var subcategories: Subcategory
}
struct Subcategory
{
var name: String
var letter: String
var guidelines: Guidelines
var commercialExamples: [CommercialExample]
var tags: [Tag]
}
struct Guidelines
{
var overallImpression: String
var aroma: String
var appearance: String
var flavor: String
var mouthfeel: String
var comments: String
var history: String
var ingredients: String
var comparison: String
var vitalStatistics: VitalStatistics
}
struct VitalStatistics
{
var og: String
var fg: String
var abv: String
var ibu: String
var srm: String
}
struct CommercialExample : Hashable
{
var name: String
func hash(into hasher: inout Hasher)
{
hasher.combine(name)
}
}
struct Tag : Hashable
{
var tag: String
func hash(into hasher: inout Hasher)
{
hasher.combine(tag)
}
}
And for my deserialization code, I have this.
for (index, dict) in jsonObject
{
let thisBeer = Beers(name: dict["name"].stringValue, number: dict["number"].intValue, subcategories: Subcategory(name: dict["name"].stringValue, letter: dict["letter"].stringValue, guidelines: Guidelines(overallImpression: dict["overallImpression"].stringValue, aroma: dict["aroma"].stringValue, appearance: dict["appearance"].stringValue, flavor: dict["flavor"].stringValue, mouthfeel: dict["mouthfeel"].stringValue, comments: dict["comments"].stringValue, history: dict["history"].stringValue, ingredients: dict["ingredients"].stringValue, comparison: dict["comparison"].stringValue, vitalStatistics: VitalStatistics(og: dict["og"].stringValue, fg: dict["fg"].stringValue, abv: dict["abv"].stringValue, ibu: dict["ibu"].stringValue, srm: dict["srm"].stringValue)), commercialExamples: CommercialExample(name: dict["name"].stringValue), tags: Tag(tags: dict["tags"].stringValue)))
beers.append(thisBeer)
}
This is where I am stuck. I am much more familiar with C# and .net. I just don't know how to go about looping through the commercialExamples and tags and creating the objects from the data and populating the arrays with them. What is the proper way of doing this with Swift and SwiftyJSON?
You must first use JSONSerializable protocols to decode your JSON data into the object. For example in your case:
struct Beers: JSONSerializable {
var name: String?
var number: Int?
var subcategories: Subcategory?
enum CodingKeys: String, CodingKey {
case name
case number
case subcategories
}
}
extension Beers: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let value = try values.decodeIfPresent(String?.self, forKey: .name) {
if let value = value {
name = value
}
}
if let value = try values.decodeIfPresent(Int?.self, forKey: .number) {
if let value = value {
number = value
}
}
if let value = try values.decodeIfPresent(Subcategory?.self, forKey: .subcategories) {
if let value = value {
subcategories = value
}
}
}
}
Then you can make an object from JSON data like this:
do {
let model = try JSONDecoder().decode(Beers.self, from: jsonData)
} catch let error {
print(error)
}
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"
}
]
}
I have a .json file into my Xcode project using Swift.
I need load the content and parse to use in my controller, butn when I try serialize the file content to an jsonObject I'm getting errors parsing...
I have read another similar questions but I haven't found a similar Array of JSON to read with different structures contained and another Array of JSON into the object.
The format of array JSON is:
[
{
"title": "The App",
"description": "This is the description",
"friends": [
{
"name": "Gary",
"image": "http://",
"description": "Nice"
},
{
"name": "Patri",
"image": "http://",
"description": "Amazing"
},
{
"name": "Lucy",
"image": "http://",
"description": "Up"
}
]
}
]
I'm using this code to get the content of file from bundle path(data.json added to my proyect) and then serialize, but always get an error because Friends contains an array of json.
let path = Bundle.main.path(forResource: "data", ofType: "json")
let jsonData = try! Data(contentsOf: URL(fileURLWithPath: path!))
let jsonResult = try! JSONSerialization.jsonObject(with: jsonData, options: []) as? [[String:Any]] //Here is the error parsing the array of Friend of JSON
How can I parse this array of json that contains another array of jsonObjects?
You need to use Codable
// MARK: - Element
struct Root: Codable {
let title, purpleDescription: String
let friends: [Friend]
enum CodingKeys: String, CodingKey {
case title
case purpleDescription = "description"
case friends
}
}
// MARK: - Friend
struct Friend: Codable {
let name, image, friendDescription: String
enum CodingKeys: String, CodingKey {
case name, image
case friendDescription = "description"
}
}
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let jsonData = try! Data(contentsOf:url)
let res = try! JSONDecoder().decode([Root].self,from:data)
print(res)