Using JSON Decodable in Swift 4.1 - json

{
"count":30,
"recipes":[
{
"publisher":"Closet Cooking",
"f2f_url":"http://food2fork.com/view/35382",
"title":"Jalapeno Popper Grilled Cheese Sandwich",
"source_url":"http://www.closetcooking.com/2011/04/jalapeno-popper-grilled-cheese-sandwich.html",
"recipe_id":"35382",
"image_url":"http://static.food2fork.com/Jalapeno2BPopper2BGrilled2BCheese2BSandwich2B12B500fd186186.jpg",
"social_rank":100.0,
"publisher_url":"http://closetcooking.com"
}
]
}
How can I parse this JSON using Swift 4.1 Decodable please?

Your previous question is quite close but you have to add the struct for the root object
Declare the struct members non-optional as much as possible. The URLs can be decoded as URL
struct Root : Decodable {
let count : Int
let recipes : [Recipe]
}
struct Recipe : Decodable { // It's highly recommended to declare Recipe in singular form
let recipeId : String
let imageUrl, sourceUrl, f2fUrl : URL
let title : String
let publisher : String
let socialRank : Double
let page : Int?
let ingredients : [String]?
}
Now decode
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data)
self.recipes = result.recipes

below are the models for your JSON:
struct Recipe: Codable{
let publisher: String
let f2f_url: String
let title: String
let source_url: String
let recipe_id: String
let image_url: String
let social_rank: Float
let publisher_url: String
}
struct Model: Codable {
let count: Int
let recipes: [Recipe]
}
and below is JSON decodable:
let json = """
{
"count":30,
"recipes":[
{
"publisher":"Closet Cooking",
"f2f_url":"http://food2fork.com/view/35382",
"title":"Jalapeno Popper Grilled Cheese Sandwich",
"source_url":"http://www.closetcooking.com/2011/04/jalapeno-popper-grilled-cheese-sandwich.html",
"recipe_id":"35382",
"image_url":"http://static.food2fork.com/Jalapeno2BPopper2BGrilled2BCheese2BSandwich2B12B500fd186186.jpg",
"social_rank":100.0,
"publisher_url":"http://closetcooking.com"
}
]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let model = try decoder.decode(Model.self, from: json) //Decode JSON Response Data
print(model)
} catch let parsingError {
print("Error", parsingError)
}

Related

Generic decoding json into object with optionals

I wrote an extension to Decodable with the hopes of having a generic constructor for objects from json strings, it looks like this:
extension Decodable {
init?(with dictionary: [String: Any]) {
guard let data = try? JSONSerialization.data(
withJSONObject: dictionary,
options: .prettyPrinted
) else {
return nil
}
guard let result = try? JSONDecoder().decode(
Self.self,
from: data
) else {
return nil
}
self = result
}
}
An example use case looks like this:
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
guard var goer = Goer(with: json) else { return }
And the object I'm trying to decode into looks like this:
struct Goer: Codable {
let goerId: String
let created: Double
let username: String
let firstName: String
let lastName: String
let city: String
let bio: String
let isPrivate: Bool
let numFollowers: Int
let numFollowing: Int
let followers: [GoerFollow]
let following: [GoerFollow]
}
My issue is that I want to introduce some optionals to these objects that the json strings I'm trying to decode may or may not have keys for. This generic constructor fails in the case where there is no key:value in the json for an optional variable in the object.
I've seen that I can write a custom constructor with a decoder for each object and use the decodeIfPresent function but I wonder if there is a way to do it generically.
if let jsonData = data {
do {
var model = try decoder.decode(Goer.self, from: jsonData)
print("model:\(model)")
} catch {
print("error:\(error)")
}
}
struct Goer: Codable {
let goerId: String?
let created: Double?
let username: String?
let firstName: String?
let lastName: String?
let city: String?
let bio: String?
let isPrivate: Bool?
let numFollowers: Int?
let numFollowing: Int?
let followers: [GoerFollow]?
let following: [GoerFollow]?
}

Swift Parsing with Json

I'm trying to parsing Json using swift and this is the json I get
{"Data":{"KanBan":[{"Sdate":"2020/06/22","Stype":"上班卡","Stime":"09:31:11","Ip":"xxx.xxx.xxx.xx"},{"Sdate":"2020/06/20","Stype":"下班卡","Stime":"20:53:43","Ip":"xxx.xxx.xxx.xx"},{"Sdate":"2020/06/20","Stype":"下班卡","Stime":"20:48:25","Ip":"xxx.xxx.xxx.xx"},{"Sdate":"2020/06/20","Stype":"下班卡","Stime":"18:53:57","Ip":"xxx.xxx.xxx.xx"},{"Sdate":"2020/06/20","Stype":"上班卡","Stime":"18:43:54","Ip":"xxx.xxx.xxx.xx "}]},"IsSuccess":true,"Message":null,"ErrorCode":null,"ErrorMessage":null}
Below is the struct I defined and use it with Swift JsonDecoder :
// inside viewController
let decoder = JSONDecoder()
let historys = try decoder.decode([Datas].self, from: data)
// struct I defined myself
struct Datas : Codable {
let Data: KanBan
let IsSuccess: Bool
let Message: String
let ErrorCode: String
let ErrorMessage: String
init( Data: KanBan, IsSuccess: Bool, Message: String, ErrorCode: String, ErrorMessage: String) {
self.Data = Data
self.IsSuccess = IsSuccess
self.Message = Message
self.ErrorCode = ErrorCode
self.ErrorMessage = ErrorMessage
}
}
struct KanBan : Codable {
let KanBan: [details]
init( kanbans : [details]) {
self.KanBan = kanbans
}
}
struct details: Codable {
let Sdate: Date
let Stype: String
let Stime : String
let Ip: String
init(Sdate: Date, Stype: String, Stime : String, Ip: String ) {
self.Sdate = Sdate
self.Stype = Stype
self.Stime = Stime
self.Ip = Ip
}
}
I keep get error that said parsing failed .
Whats wrong with the struct I defined ? Plz help and thanks!!!
You need to fix 3 things:
You're trying to decode [Datas] whereas it should be Datas.
If the keys are optional you should declare them as optional. eg: ErrorMessage, ErrorCode.
Provide the DateFormatter if you're trying to decode Dates.
Here's the code you need: -
Model:
struct Datas: Codable {
let Data: KanBan
let IsSuccess: Bool
let Message: String?
let ErrorCode: String?
let ErrorMessage: String?
}
struct KanBan: Codable {
let KanBan: [details]
}
struct details: Codable {
let Sdate: Date
let Stype: String
let Stime : String
let Ip: String
}
Decoding:
do {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let historys = try decoder.decode(Datas.self, from: data)
print(historys)
} catch { print(error) }

Deserialization JSON swift 4.2

I try to deserialize my JSON by using Decodable protocol, also i use enum with CodingKey, but it doesn't work. I need only nested array (start with "indicator"), and only few fields (all of them in struct). I tried a lot of different options, but unfortunately..
P.S. Also i tried to do it without CodingKey. Anyway response was: "Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "country", intValue: nil)" Ofc i read this, maybe array is a reason(i mean this strange intValue)?
JSON
[
{
"page":1,
"pages":2,
"per_page":50,
"total":59,
"sourceid":"2",
"lastupdated":"2019-03-21"
},
[
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2018",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2017",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
...
]
]
My code
struct CountryObject: Decodable{
var country: CountryInfo
var date: Int
var value: Int?
private enum RawValues: String, Decodable{
case date = "date"
case vallue = "value"
}
}
struct CountryInfo: Decodable{//Country names
var id: String?
var value: String?
private enum RawValues: String, Decodable{
case id = "id"
case value = "value"
}
}//
let urlString = "https://api.worldbank.org/v2/country/SS/indicator/IP.PAT.RESD?format=json"
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) {(data,response,error) in
guard let data = data else {return}
guard error == nil else {return}
do{
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let countryObject = try! decoder.decode([CountryObject].self, from: data)
print(countryObject)
}catch let error{
print(error)
}
}.resume()
Create a root struct and decode the array with unkeyedContainer
struct Root : Decodable {
let info : Info
let countryObjects : [CountryObject]
init(from decoder: Decoder) throws {
var arrayContrainer = try decoder.unkeyedContainer()
info = try arrayContrainer.decode(Info.self)
countryObject = try arrayContrainer.decode([CountryObject].self)
}
}
struct Info : Decodable {
let page, pages, perPage: Int
let lastupdated: String
}
struct CountryObject : Decodable {
let country: CountryInfo
let date: String
let value: Int?
}
struct CountryInfo : Decodable { //Country names
let id: String
let value: String
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(Root.self, from: data)
let countryObjects = root.countryObjects
print(countryObjects)
} catch { print(error) }
(De)serializing the JSON twice is unnecessarily expensive.

debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil)

I want to load an online json file into my application, but I am running into this error:
typeMismatch(Swift.Array,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Array but found a dictionary instead.",
underlyingError: nil))
I have looked on stackoverflow but other sollutions didn't help to solve mine.
My JSON:
{
"copyright" : "NHL and the NHL Shield are registered trademarks of the National Hockey League. NHL and NHL team marks are the property of the NHL and its teams. © NHL 2022. All Rights Reserved.",
"totalItems" : 0,
"totalEvents" : 0,
"totalGames" : 0,
"totalMatches" : 0,
"metaData" : {
"timeStamp" : "20220813_172145"
},
"wait" : 10,
"dates" : [ ]
}
My datamodel:
import Foundation
struct Initial: Codable {
let copyright: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let wait: Int
let dates: [Dates]
}
struct Dates: Codable {
let date: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let games: [Game]
}
struct Game: Codable {
let gamePk: Int
let link: String
let gameType: String
let season: String
let gameDate: String
let status: Status
let teams: Team
let venue: Venue
let content: Content
}
struct Status: Codable {
let abstractGameState: String
let codedGameState: Int
let detailedState: String
let statusCode: Int
let startTimeTBD: Bool
}
struct Team: Codable {
let away: Away
let home: Home
}
struct Away: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct Home: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct LeagueRecord: Codable {
let wins: Int
let losses: Int
let type: String
}
struct TeamInfo: Codable {
let id: Int
let name: String
let link: String
}
struct Venue: Codable {
let name: String
let link: String
}
struct Content: Codable {
let link: String
}
and here is my viewcontroller
import UIKit
class TodaysGamesTableViewController: UITableViewController {
var todaysGamesURL: URL = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule")!
var gameData: [Dates] = []
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
loadTodaysGames()
}
func loadTodaysGames(){
print("load Games")
view.addSubview(activityIndicator)
activityIndicator.frame = view.bounds
activityIndicator.startAnimating()
let todaysGamesDatatask = URLSession.shared.dataTask(with: todaysGamesURL, completionHandler: dataLoaded)
todaysGamesDatatask.resume()
}
func dataLoaded(data:Data?,response:URLResponse?,error:Error?){
if let detailData = data{
print("detaildata", detailData)
let decoder = JSONDecoder()
do {
let jsondata = try decoder.decode([Dates].self, from: detailData)
gameData = jsondata //Hier .instantie wil doen krijg ik ook een error
DispatchQueue.main.async{
self.tableView.reloadData()
}
}catch let error{
print(error)
}
}else{
print(error!)
}
}
Please learn to understand the decoding error messages, they are very descriptive.
The error says you are going to decode an array but the actual object is a dictionary (the target struct).
First take a look at the beginning of the JSON
{
"copyright" : "NHL and the NHL Shield are registered trademarks of the National Hockey League. NHL and NHL team marks are the property of the NHL and its teams. © NHL 2018. All Rights Reserved.",
"totalItems" : 2,
"totalEvents" : 0,
"totalGames" : 2,
"totalMatches" : 0,
"wait" : 10,
"dates" : [ {
"date" : "2018-05-04",
It starts with a { which is a dictionary (an array is [) but you want to decode an array ([Dates]), that's the type mismatch the error message is referring to.
But this is only half the solution. After changing the line to try decoder.decode(Dates.self you will get another error that there is no value for key copyright.
Look again at the JSON and compare the keys with the struct members. The struct whose members match the JSON keys is Initial and you have to get the dates array to populate gameData.
let jsondata = try decoder.decode(Initial.self, from: detailData)
gameData = jsondata.dates
The JSON is represented by your Initial struct, not an array of Dates.
Change:
let jsondata = try decoder.decode([Dates].self, from: detailData)
to:
let jsondata = try decoder.decode(Initial.self, from: detailData)
Correct Answer is done previously from my two friends
but you have to do it better i will provide solution for you to make code more clean and will give you array of Dates
here is your model with codable
import Foundation
struct Initial: Codable {
let copyright: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let wait: Int
let dates: [Dates]
}
struct Dates: Codable {
let date: String
let totalItems: Int
let totalEvents: Int
let totalGames: Int
let totalMatches: Int
let games: [Game]
}
struct Game: Codable {
let gamePk: Int
let link: String
let gameType: String
let season: String
let gameDate: String
let status: Status
let teams: Team
let venue: Venue
let content: Content
}
struct Status: Codable {
let abstractGameState: String
let codedGameState: Int
let detailedState: String
let statusCode: Int
let startTimeTBD: Bool
}
struct Team: Codable {
let away: Away
let home: Home
}
struct Away: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct Home: Codable {
let leagueRecord: LeagueRecord
let score: Int
let team: TeamInfo
}
struct LeagueRecord: Codable {
let wins: Int
let losses: Int
let type: String
}
struct TeamInfo: Codable {
let id: Int
let name: String
let link: String
}
struct Venue: Codable {
let name: String
let link: String
}
struct Content: Codable {
let link: String
}
// MARK: Convenience initializers
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And Here is Controller Will make solution more easy
class ViewController: UIViewController {
var todaysGamesURL: URL = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule")!
var gameData: Initial?
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
override func viewDidLoad() {
super.viewDidLoad()
self.loadTodaysGames()
}
func loadTodaysGames(){
print("load Games")
let todaysGamesDatatask = URLSession.shared.dataTask(with: todaysGamesURL, completionHandler: dataLoaded)
todaysGamesDatatask.resume()
}
func dataLoaded(data:Data?,response:URLResponse?,error:Error?){
if let detailData = data {
if let inital = try? Initial.init(data: detailData){
print(inital.dates)
}else{
// print("Initial")
}
}else{
print(error!)
}
}
}

How to parsing Json response to Swift objects

Hi i am beginner for swift language and in my project i am using web services and after got response how can i parse below response to Swift object can some on help me please
response:-
[
{
"id" : 1,
"first_name": "John",
"last_name": "Smith",
"age": 25,
"address": {
"id": 1,
"street_address": "2nd Street",
"city": "Bakersfield",
"state": "CA",
"postal_code": 93309
}
}
]
ModelClass:-
class Address:NSObject{
struct Address {
let objID: Int?
let streetAddress: String?
let city: String?
let state: String?
let postalCode: String?
}
struct User {
let objID: Int?
let firstName: String?
let lastName: String?
let age: Int?
let address : Address?
}
}
ViewController:-
func finalResponse(response : AnyObject){
let addressArray = response as! NSArray;
for items in addressArray{
}
}
In swift 4 it get lot easier
Your Model class look like this
Key should be same as json response or make enum for changing the name
struct Address: Decodable {
let objID: Int?
let streetAddress: String?
let city: String?
let state: String?
let postalCode: String?
}
struct User: Decodable {
let objID: Int?
let firstName: String?
let lastName: String?
let age: Int?
let address : Address?
}
}
Your view Controller class look like this
try decoder.decode([User.self], from: jsonData)
This is olden days method in objective-c swift 1,2,3
This is model Class
class SafeJson: NSObject{
override func setValue(_ value: Any?, forKey key: String) {
let firstCharacter = String(key.characters.first!).uppercased()
let range = NSMakeRange(0,1)
let valuex = NSString(string: key).replacingCharacters(in: range, with: firstCharacter)
// let valuex = key.replacingCharacters(in: range, offsetBy: 0), with: firstCharacter)
let selector = NSSelectorFromString("set\(valuex):")
let respond = self.responds(to: selector)
if !respond{
return
}
super.setValue(value, forKey: key)
}
}
class Model:SafeJson{
// var thumbnail_image_name: String?
var title: String?
var number_of_views: NSNumber?
var channel: Channel?
override func setValue(_ value: Any?, forKey key: String) {
if key == "channel"{
self.channel = Channel()
let dictionary = value as! [String: AnyObject]
self.channel?.setValuesForKeys(dictionary)
}else{
super.setValue(value, forKey: key)
}
}
init(dictionary: [String: AnyObject]) {
super.init()
setValuesForKeys(dictionary)
}
}
class Channel:SafeJson{
var name: String?
var profile_image_name: String?
}
In your View controller class You have to pass your response to Model.init it automatically save to model
But in swift 4 setValueForKey is depricated
You have to use decodable for it
warning, did not get to test it so let me know for any warnings and will adjust
For Swift3
func finalResponse(response : AnyObject){
var result: [Address] = []
let json = response as! [String:AnyObject]
// ITERATE THROUGH THE ARRAY OF DICTIONARIES
for item in json {
// a WILL BE THE MAIN OBJECT YOU'RE CREATING
var a = Adress()
a.objID = item["id"] as! Int
.....
// REPEAT FOR EVERY ELEMENT UNTIL YOU REACH NEXT SUBCLASS
.....
// CREATE A NEW DICTIONARY FOR THE SUBCLASS Address
var b = item["address"] as! Dictionary<String, String>
a.address.objID = b["id"] as! Int
// IF YOU DON'T WANT TO CREATE var b YOU CAN WRITE
// a.address.objID = a["address"]["id"] INSTEAD
// ADD YOUR OBJECT TO RESULT
result.append(a)
}
}
Herr is the code to parse data from your JSON data. Create struct at the location that I commented in the code.
do{
let json = try JSONSerialization.jsonObject(with: yourJSONData!, options: []) as? [Any]
let firstUser = json?[0] as? [String: Any]
let id = firstUser?["id"] as? Int
let firstName = firstUser?["first_name"] as? String
//etc... for other keys
let address = firstUser?["address"] as? [String, Any]
let streetAddress = address?["street_address"] as? String
let state = address?["state"] as? String
//etc... create your address struct here and then create the user struct
dump(firstPerson?["lastName"])
}catch let error{
}