"collection_listings" = (
{
"body_html" = "";
"collection_id" = 57229082710;
"default_product_image" = "<null>";
handle = men;
image = {
"created_at" = "2018-05-02T01:34:16-04:00";
src = "https://cdn.shopify.com/s/files/1/2331/3377/collections/men.jpg?v=1525239256";
};
"published_at" = "2018-05-02T01:34:16-04:00";
"sort_order" = manual;
title = Men;
"updated_at" = "2018-05-02T08:01:58-04:00";
}
How to print this data in the simulator using swift 4?
While I'm trying to print this data, I get this error:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath:
[], debugDescription: "Expected to decode Array but found a
dictionary instead.", underlyingError: nil))
Here is my updated code:
import UIKit
struct product: Decodable
{
let product_id : String
let title : String
let image : String
}
class ViewController: UIViewController,UICollectionViewDataSource
{
var products = product
#IBOutlet weak var productCell: UICollectionView!
override func viewDidLoad()
{
super.viewDidLoad()
productCell.dataSource = self
guard let url = URL(string: "https://psofttech-test.myshopify.com/admin/collection_listings.json") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if error == nil
{
do
{
let json = try JSONSerialization.jsonObject(with: data!) as? [String: Any]
self.products = try JSONDecoder().decode([product].self, from: data!)
print(self.products, "dddd")
for info in self.products
{
self.productCell.reloadData()
}
}
catch
{
print(error)
}
}
}.resume()
print(products.self,"0000")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productCollection", for: indexPath) as! productCollectionViewCell
cell.proLBL.text = products[indexPath.row].title
return cell
}
}
See if this code helps you..
if let data = data as? [String: Any] {
if let data = data["collection_listings"] as? NSArray {
for data in data {
if let data = data as [String: Any] {
}
}
}
}
Now you can use codable to parse json data in Swift 4.0.
Using Codable, we can model JSONObject or PropertyList file into
equivalent Struct or Classes by writing very few lines of code. We
don’t have to write the constructor for the properties in the
objects. It’s all handed by Codable. We just need to extend our model
to conform to the Codable, Decodable or Encodable protocol.
Mismatch between the strong data types of Swift and lose data types
of JSON has been internally handled by Swift compiler. We can now
handle Swift Data types like Date, URL, Float etc
Complex JSON can be modelled easily using Nesting Structs for
readability.
Parsing actual JSON become one-liner using JSONDecoder
You can refer to this app for using the codable : Demo App for Codable Swift
You're using the wrong object to decode. Basically it's telling you that the object type you sent to the decoder does not match the JSON. In your case (without seeing your code it's hard to tell exactly) it seems the object you provided is of type of array, and the JSON is a dictionary. This is the method I typically use:
Specify a struct of the type you want to decode:
struct Response {
var collection_listings: Listing
}
struct Listing {
var collection_id: String
}
In your decoder specific something like:
let decoder = JSONDecoder()
let apiResponse = try decoder.decode(Response.self, from: data)
Related
In my app, I have a song-searching feature where I call the Happi API (https://happi.dev/docs/music) and it will return songs with song info that matches a search keyword passed in.
Here is my code for accessing the data:
func getSongs(completion: #escaping(Result<[SongInfo], SongError>) -> Void) {
let dataTask = URLSession.shared.dataTask(with: resourceURL) { data, _, _ in
guard let jsonData = data else {
completion(.failure(.noDataAvailable))
return
}
do {
let decoder = JSONDecoder()
let songResponse = try decoder.decode(SongResponse.self, from: jsonData)
//print("decoded")
let songInfos = songResponse.response.results
completion(.success(songInfos))
}catch{
completion(.failure(.canNotProcessData))
}
}
dataTask.resume()
}
the let dataTask portion of the code is successful, and does not return the noDataAvailable error. However, I do receive a canNotProcessData error when I go decode the JSON data, and I assuming it's because of an error in my structure, but not entirely sure. Here's my structure:
struct SongResponse:Decodable {
var response:Results
}
struct Results:Decodable {
var results: [SongInfo]
}
struct SongInfo:Decodable {
var songName:String
var artist:String
}
Here are photos of my JSON tree from the API:image 1
image 2
Essentially, all I want is to retrieve each array in the results:[] dictionary, then for each array, I want to get the values for track and artist. For example in the image there are 5 results, and for each result i want to create a song class with "track" and "artist" attributes. These song classes would be stored in an array. Please let me know how to change my code, or of an easier way to do this!
EDIT: error message:
keyNotFound(CodingKeys(stringValue: "response", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "response", intValue: nil) ("response").", underlyingError: nil))
Following is the correct structure
do {
let decoder = JSONDecoder()
let songResponse = try decoder.decode(SongResponse.self, from: jsonData)
let songInfos = songResponse.result ?? []
completion(.success(songInfos))
}catch{
completion(.failure(.canNotProcessData))
}
// Models
struct SongResponse: Decodable {
var success: Bool?
var result: [SongInfo]?
}
struct SongInfo: Decodable {
var track: String?
var artist: String?
}
To mirror the json structure, you have to change the key in SongResponse.
struct SongResponse: Decodable {
var result: [SongInfo]
}
My json response is ,
{ resultCount = 32;
results = (
{
artistId = 909253;
artistName = "Jack Johnson";
country = USA;
currency = USD;
kind = "music-video";
},
{
artistId = 909253;
artistName = "Jack Johnson";
country = UK;
currency = USD;
kind = "music-video";
}
I have written code for view load to call the get method,
Alamofire.request("https://itunes.apple.com/search?term=jackjohnson&entity=musicVideo").responseJSON { response in
debugPrint(response)
if let json = response.result.value //getting json
{
print(json)
let jobsArray : NSArray = json as! AnyHashable as! NSArray //converting json to NSArray
if jobsArray.count > 0
{
for object in jobsArray
{
if let singleDict = object as? NSDictionary
{
self.arrFavAdsList.add(singleDict)
print(self.arrFavAdsList)
}
}
DispatchQueue.main.async() {
self.collectionView.reloadData()
}
//displaying data in tableview
}
}
}
But its showing json array error.. I need to get the array response as dictionary and show it in my Collection View
First of all declare variables in your view controller or elsewhere
var mArray:[Music] = []
Try to use Codable/Decodable Json parse in order to fetch ApiResults,
struct ApiResults:Decodable {
let resultCount: Int
let results: [Music]
}
struct Music:Decodable {
let artistId: Int?
let artistName: String?
let country: String?
let currency: String?
let kind: String?
}
Now, try in your viewdidload or call it as function where ever wish to be...
func callAPI() {
guard let url = URL(string: "https://itunes.apple.com/search?term=jackjohnson&entity=musicVideo") else {return}
URLSession.shared.dataTask(with: url){(data, response, error) in
guard let data = data else {return}
do
{
let apiressults = try JSONDecoder().decode(ApiResults.self, from: data)
for item in apiressults.results
{if let track_Name = item.trackName, let artist_Name = item.artistName, let country = item.country, let currency = item.currency, let kind = item.kind
{let musics = Music(artistId: artist_Id, artistName: artist_Name, country: country, currency: currency, kind: kind)
self.mArray = apiressults.results
}
}
DispatchQueue.main.async
{
self.collectionView.reloadData()
} }
catch let jsonError
{
print("Error:", jsonError)
}
}.resume()
}
Now at last, as u mentioned in order to show it in collection view as array/list.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return mArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell // your cell identifier..
cell.label1.text = mArray[indexPath.row].trackName
cell.label2.text = mArray[indexPath.row].trackId
cell.label3.text = mArray[indexPath.row].currency.
///////like so for other stuffs which u need to show in collection view.
}
return cell
}
Your full JSON content is the dictionary, So you need to convert first JSON string to a dictionary, Use following code to convert JSON into a dictionary, and then extract result array from dictionary and load into your UICollectionview
func convertToDictionary(text: String) -> [String: Any]? {
if let data = text.data(using: .utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
} catch {
print(error.localizedDescription)
}
}
return nil
}
Your JSON's content is a Dictionary and you cannot convert it that way. Here is a working version:
Alamofire.request("https://itunes.apple.com/search?term=jackjohnson&entity=musicVideo").responseJSON { response in
if let json = response.result.value as? [String: Any], let tracks = json["results"] as? Array<[String: Any]>, tracks.count > 0 {
for track in tracks {
// do your stuffs with track here.
}
}
}
However, I'd prefer you to use Codable/Decodable to parse JSON in Swift. You can take a look at following example for reference:
struct APIResult: Codable {
struct Track: Codable {
let kind: String
let artistName: String
let name: String
enum CodingKeys : String, CodingKey {
case kind
case artistName
case name = "trackName"
}
}
let resultCount: Int
let tracks: [Track]
enum CodingKeys : String, CodingKey {
case resultCount
case tracks = "results"
}
}
// and then use it like following:
Alamofire.request("https://itunes.apple.com/search?term=jackjohnson&entity=musicVideo").response { response in
let decoder = JSONDecoder()
let tracks = try! decoder.decode(APIResult.self, from: response.data!).tracks
for track in tracks {
// do your stuffs with track here.
}
}
Happy coding!
I'm making a small project to practice serializing JSON in Swift 4 using structs... it gets top stories from the New York Times API and puts them into a table view. Currently I'm getting the data I need from the JSON and filling some arrays with the stuff I need (headlines, abstracts, etc).
Someone advised me to skip that step and instead populate the table view directly from the structs.
struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]
private enum CodingKeys: String, CodingKey {
case title
case abstract
case url
case multimedia
}
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
abstract = try container.decode(String.self, forKey: .abstract)
url = try container.decode(String.self, forKey: .url)
multimedia = (try? container.decode([Multimedia].self, forKey: .multimedia)) ?? []
}
}
struct Multimedia: Decodable {
let url: String
let type: String
}
var storyData = [Story]()
And in my cellForRowAt method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "storyCell", for: indexPath) as! StoryTableViewCell
let stories = storyData[indexPath.row]
print("Titles: \(stories.title)")
cell.headlineLabel.text = stories.title
cell.abstractLabel.text = stories.abstract
return cell
}
When I run the app, the table view is empty and my print statement confirmed that stories.title is empty (everything showed up before when I was using arrays).
This is the function that grabs the data if it's applicable here, i'll scrap all the code that passes the data into arrays if I can use the structs instead:
func getJSON(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(TopStoriesResponse.self, from: data)
// Pass results into arrays (title, abstract, url, image)
for result in response.results {
let headlines = result.title
let abstracts = result.abstract
let url = result.url
self.headlines.append(headlines)
self.abstracts.append(abstracts)
self.urls.append(url)
for imageResults in result.multimedia {
let images = imageResults.url
self.images.append(images)
}
}
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
Do I need to pass the data back into the structs the way I was doing it with the arrays? I was under the impression that let response = try JSONDecoder().decode(TopStoriesResponse.self, from: data) did that already.
You need
let response = try JSONDecoder().decode(TopStoriesResponse.self, from: data)
self.storyData = response.results
DispatchQueue.main.async {
self.tableView.reloadData()
}
the other arrays content is irrelevant here as you don't use them as the dataSource of the table
There seems to be a mismatch in your data models. On the one hand, your JSON data is being put into four arrays: self.headlines, self.abstracts, self.urls, and self.images. But your table view knows nothing about any of that; it depends entirely on a different array, storyData. You need to bring those two data models together, if you see what I mean. Download the data, rejigger it into storyData, and then tell the table view to reloadData.
I'm struggling to get my CoreData objects into JSON so that I can use it to send to a web server.
This is how I currently fetch my objects from CoreData:
func fetchRecord() -> [Record] {
do {
records = try context.fetch(Record.fetchRequest())
} catch {
print("Error fetching data from CoreData")
}
return records
}
I am able to display this on to my tableView this way:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "recordCell", for: indexPath) as! RecordCell
cell.nameLbl.text = records[indexPath.row].name
cell.quantityLbl.text = "Quantity: \(String(records[indexPath.row].quantity))"
cell.dateLbl.text = dateString(date: records[indexPath.row].date)
return cell
}
I have attempted to loop inside my request like this:
for rec in records {
print(rec)
}
that gives out this:
I have read a lot about ways to achieve this but none of them seem to really be of beneficial to me. Most of the examples out there shows how to get JSON to CoreData and not the other way. Does anyone know any good tutorials or documentation that can help me achieve this?
In Swift 4+ you can take advantage of the Encodable protocol and add the functionality directly to your Core Data object.
Assuming your NSManagedObject subclass extension looks like
extension Record {
#NSManaged public var date: Date
#NSManaged public var name: String
#NSManaged public var quantity: Int32
#NSManaged public var synched: Bool
#NSManaged public var uuid: String
...
Adopt Encodable
extension Record : Encodable {
and add
private enum CodingKeys: String, CodingKey { case date, name, quantity, synched, uuid }
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(date, forKey: .date)
try container.encode(name, forKey: .name)
try container.encode(quantity, forKey: .quantity)
try container.encode(synched, forKey: .synched)
try container.encode(uuid, forKey: .uuid)
}
Then you can easily encode the records to JSON
do {
records = try context.fetch(Record.fetchRequest())
let jsonData = try JSONEncoder().encode(records)
} catch {
print("Error fetching data from CoreData", error)
}
Here the code as an extension.
Based on KavyaKavita's answer.
extension NSManagedObject {
func toJSON() -> String? {
let keys = Array(self.entity.attributesByName.keys)
let dict = self.dictionaryWithValues(forKeys: keys)
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
let reqJSONStr = String(data: jsonData, encoding: .utf8)
return reqJSONStr
}
catch{}
return nil
}
}
Usage:
let jsonString = YourCoreDataObject.toJSON()
print(jsonString)
You can convert your NSManageObject subclass object into dictionary by using following code
let record = recArray[index]
let keys = Array(record.entity.attributesByName.keys)
let dict = record.dictionaryWithValues(forKeys: keys)
After that you can use jsonserialization to convert that dictionary into json object
do{
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
let reqJSONStr = String(data: jsonData, encoding: .utf8)
print(reqJSONStr!)
}catch{
}
Hope this will help.
If you want you can get the results in dictionary format from core data using below :
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName:"Record")
fetchRequest.resultType = .dictionaryResultType
do {
records = try context.fetch(fetchRequest)
} catch {
print("Error fetching data from CoreData")
}
im really new to swift and without your help i cant move forward guys
can someone please tell me how can i fix this error?
i put the image of error below
import UIKit
import Alamofire
import SwiftyJSON
class AppCategory : NSObject {
var name : String?
var apps : [App]?
var type : String?
override func setValue(_ value: Any?, forKey key: String) {
if key == "apps " {
var apps = [App]()
for dict in value as! [[String:AnyObject]] {
let app = App()
app.setValuesForKeys(dict)
apps.append(app)
}
}else{
super.setValue(value, forKey: key)
}
}
static func fetchFeaturedApps (completionHandler : #escaping ([AppCategory]) -> ()) {
let urlRequest = URLRequest(url:URL(string: "http://www.statsallday.com/appstore/featured")!)
URLSession.shared.dataTask(with: urlRequest as URLRequest) { (data, response, error) -> Void in
if error != nil {
print(error!)
return
}
do {
let json = try( JSONSerialization.jsonObject(with: data!, options: .mutableContainers)) as! [String : Any]
var appCategories = [AppCategory]()
for dict in json["categories"] as! [[String:Any]]{
let appcategory = AppCategory()
appcategory.setValuesForKeys(dict)
appCategories.append(appcategory)
}
DispatchQueue.main.async {
completionHandler(appCategories)
}
}catch let err {
print(err)
}
}.resume()
}
here is my class
class App : NSObject {
var Id : NSNumber?
var Name : String?
var ImageName : String?
var Category : String?
var Price : NSNumber?
}
the JSON link
this is a fuction that should pass back cell but gives me the mismatching on NSArray error
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! AppCell
cell.apps = appCategory?.apps?[indexPath.item]
return cell
}
i dont know how to findout which types are not match
for the second error
check this line if key == "apps " { remove space in word apps
you should use this
for dict in json["categories"] as! [[String:Any]]{
}
// Any is an alias for any data type.
// AnyObject is an alias for any data type derived from a class.
but better to write it
guard let categories = json["categories"] as? [[String:Any]] else {
return
}
for dict in categories {
}