I am able to parse JSON using JSONSerialization, but unable to parse with Codable.
the json look like this:
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere#april.biz",
}
Please help me with the code.
able to parse using JSONSerialization: data coming
Unable to parse JSON with Codable: data not coming
struct jsonDataModel: Codable{
var name: String
var userName: String
var email: String
init(name: String, username: String, email: String){
self.name = name
self.userName = username
self.email = email
}
}
class WebviewViewController: UIViewController, WKNavigationDelegate {
#IBOutlet weak var testWebView: WKWebView!
//var itemsArray = [jsonDataModel]()
override func viewDidLoad() {
super.viewDidLoad()
serviceCall()
}
func serviceCall()
{
let jsonString = "https://jsonplaceholder.typicode.com/users"
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(jsonDataModel.self, from: jsonData)
print("all data \(user)")
print("Hello \(user.name), \(user.userName), \(user.email) ")
} catch {
print("Unexpected error: \(error).")
}
}
}
Please help me to parse json with codable.
Try this example.
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do{
let jsonDataModels = try JSONDecoder().decode([JSONDataModel].self, from: data)
print(String(data: data, encoding: .utf8)!)
print("jsonDataModels: \(jsonDataModels)")
}catch{}
}
task.resume()
}
}
struct JSONDataModel: Codable {
let id: Int
let name, username, email: String
let address: Address
let phone, website: String
let company: Company
}
struct Address: Codable {
let street, suite, city, zipcode: String
let geo: Geo
}
struct Geo: Codable {
let lat, lng: String
}
struct Company: Codable {
let name, catchPhrase, bs: String
}
First of all, if you're using a URL, then to get data you need to use a networking api. URLSession is the iOS provided api to perform network operations like download/upload.
So, just using Codable doesn't make any sense. You need to first have the data in order to parse it with Codable.
Here is the model,
struct Model: Codable {
let id: Int
let name, username, email: String
}
And you can use it in your controller's viewDidLoad() method,
if let url = URL(string: "https://jsonplaceholder.typicode.com/users") {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
do {
let response = try JSONDecoder().decode([Model].self, from: data)
print(response.map({ $0.name })) //prints all the names
} catch {
print(error)
}
}
}.resume()
}
Related
I study SwiftUI and I have an interesting task.
I need to get data from a site and store it in CoreData. Then, I need to get them from CoreData and show it in my View if I don't have internet.
I created my data model:
struct User: Codable, Identifiable, Hashable {
struct Friend: Codable, Identifiable, Hashable {
let id: String
let name: String
}
let id: String
let name: String
let isActive: Bool
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: Date
let friends: [Friend]
}
This is how I get the data form the site:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
//#FetchRequest(entity: CDUser.entity(), sortDescriptors: []) var users: FetchedResults<CDUser>
#State private var users = [User]()
// some code
// .............
func loadData() {
guard let url = URL(string: "https://****************.json") else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let decoded = try decoder.decode([User].self, from: data)
DispatchQueue.main.async {
self.users = decoded
return
}
} catch {
print("Decode error:", error)
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
}
So now I don't know how to store a variable which will contains data from the site and from CoreData when it needed.
Can anyone give some advice?
User entity
Friend entity
I'm using Codable for the first time and want to output the json result of Google Places details as a label.
However, when I print it, the console says "The data could n’t be read because it isn’t in the correct format.”.
I can't solve it by myself, so please tell me how to write it correctly.
Thanks.
The result of json
{
"html_attributions": [],
"result": {
"formatted_phone_number": "XXXX-XXX-XXX",
"website": "https://www.xxxxx.com/xxxxxx/"
},
"status": "OK"
}
Detail.swift
import Foundation
struct Details : Codable {
var formatted_phone_number : String!
var website : String!
}
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
fetchDetailData {(details) in
for detail in details{
print(detail.website)
}
}
}
func fetchDetailData(completionHandler: #escaping ([Details]) -> Void){
let url = URL(string: "https://maps.googleapis.com/maps/api/place/details/json?place_id=\(place_id)&fields=formatted_phone_number,website&key=\(apikey)")!
let task = URLSession.shared.dataTask(with: url){
(data,respose, error)in
guard let data = data else{ return }
do {
let detailsData = try JSONDecoder().decode([Details].self, from: data)
completionHandler(detailsData)
}
catch{
let error = error
print(error.localizedDescription)
}
}.resume()
}
One of the issues there is that result is a dictionary not an array. You need also to decode the root structure to extract the result from it. Note that you can also change the website type from String to URL:
struct Root: Codable {
let htmlAttributions: [String] // make sure to define the proper type in case the collection is not empty
let result: Result
let status: String
}
struct Result: Codable {
let formattedPhoneNumber: String
let website: URL
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Root.self, from: data).result
print(result)
} catch {
print(error)
}
This will print
Result(formattedPhoneNumber: "XXXX-XXX-XXX", website: https://www.xxxxx.com/xxxxxx/)
could you try this;
struct Place {
let result: Details?
}
struct Details: Codable {
let phoneNumber: String?
let website: String?
enum CodingKeys: String, CodingKey {
case website
case phoneNumber = "formatted_phone_number"
}
}
and parse Place.self
you will also need to change "#escaping ([Details])" to "#escaping (Place)"
This is json code:
{
"status":"success",
"data":
[
{"id":"3",
"city_name":"Delhi",
"city_image":"delhi.png"},
{"id":"4",
"city_name":"Mumbai",
"city_image":"tickmark.png"}
]
}
My Swift Code :
struct city: Decodable{
let status : String
let id: String
let data : String
let city_name: String
let city_image: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrl = "http://parking.onlinekiduniya.org/api/cityList.php"
let url = URL(string: jsonUrl)
URLSession.shared.dataTask(with: url!) {(data, response, error) in
do {
let cities = try JSONDecoder().decode([city].self, from: data!)
for city in cities {
print(city.id)
}
}
catch {
print("we got error")
}
}.resume()
}
}
Replace
let cities = try JSONDecoder().decode([city].self, from: data!)
with
let root = try JSONDecoder().decode(Root.self, from: data!)
let cities = root.data
cities.forEach {
print($0.id)
}
struct Root: Codable {
let status: String
let data: [City]
}
struct City: Codable {
let id, cityName, cityImage: String // you can use snake case also
enum CodingKeys: String, CodingKey {
case id
case cityName = "city_name"
case cityImage = "city_image"
}
}
Your root is a dictionary not an array
I hava a json file:
jsonpElixo({
"response":{
"parks":[
{
"Park":{
"id":"2",
"name":"PARQUE VILLA-LOBOS",
"type":"Urbano"
},
"Address":{
"lat":"-23.547245206920508",
"long":"-46.71627699999999",
"cep":null,
"street":"Avenida Professor Fonseca Rodrigues",
"number":"1025",
"neighborhood":"Alto Pinheiros",
"city":"S\u00e3o Paulo",
"state":"SP",
"id":"9"
}
}
]
}
})
But I can't get the elements inside the {}. Because the "jsonpElixo(" is breaking during the decodable.
How can I fix that?
The func to get info about json file.
func getParks() {
var request = URLRequest(url:gitUrl)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let welcome = try? decoder.decode(Welcome.self, from: data)
} catch let err {
print("Err", err)
}
}.resume()
}
The structs to Decodable the elements. But I dont know how can I scape this first element ("jsonpElixo(")
struct Welcome: Decodable {
let response: Response
}
struct Response: Decodable {
let parks: [Park]
}
struct Park: Decodable {
let park: ParkClass
let address: Address
enum CodingKeys: String, CodingKey {
case park = "Park"
case address = "Address"
}
}
struct Address: Decodable {
let lat, long: String
let cep: String?
let street, number, neighborhood, city: String
let state, id: String
}
struct ParkClass: Decodable {
let id, name, type: String
}
You can create a function that will remove the outer jsonpElixo() object and return the json to work with.
Start with an extension on Data so we can create something similar to this:
extension Data {
func decodeJsonpElixo() -> Data {
guard let jsonpString = String(data: self, encoding: .utf8) else {return self}
if jsonpString.hasPrefix("jsonpElixo(") && jsonpString.hasSuffix(")") {
var decoderString = jsonpString.replacingOccurrences(of: "jsonpElixo(", with: "")
decoderString.remove(at: String.Index(encodedOffset: decoderString.endIndex.encodedOffset - 1))
return Data(decoderString.utf8)
}
return self
}
}
Then you can use this in your URLSession closure like this:
guard let data = data else { return }
let decoderData = data.decodeJsonpElixo()
let decoder = JSONDecoder()
do {
let welcome = try decoder.decode(Welcome.self, from: decoderData)
} catch let err {
print(err)
}
I have two JSON call functions that get clients data and invoice data. In my clients JSON response I have ID and name, etc. In my invoice response I have client ID.
I need to get the client ID from invoice and convert to the client name string.
In my ClientsViewController I've made a function called downloadClientsJSON
func downloadClientsJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://ex.com/app/api/clientList/get")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.clients.removeAll()
self.clients = try JSONDecoder().decode([ClientsList].self, from: data!)
DispatchQueue.main.async {
completed()
}
} catch {
print ("Error")
}
}
}.resume()
}
And here is the ClientsList structure:
import Foundation
struct ClientsList:Decodable {
let name: String
let vatNumber: String
let address: String
let cap: String
let city: String
let prov: String
let tel: String
let email: String
}
Here is the JSON Response:
[{"id":1,"name":"DemoClient","vatNumber":"010101010101","address":"Demo Address, 1","cap":"01010","city":"DemoCity","prov":"DP","tel":"555-1112223","email":"demo#example.com"}]
In my InvoiceViewController I've made a function called downloadInvoicesJSON
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://ex.com/app/api/invoiceList/get")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.invoices.removeAll()
self.invoices = try JSONDecoder().decode([InvoiceList].self, from: data!)
DispatchQueue.main.async {
completed()
}
} catch {
print (error)
}
}
}.resume()
}
Here is the InvoiceList structure:
import Foundation
struct InvoiceList:Decodable {
let id: NSInteger
let date: String
let expiration: String
let client: NSInteger
let status: NSInteger
let lines: String
let total: Float
}
Here is the JSON Response:
[{"id":1,"date":"01-01-2018","expiration":"01-01-2018","client":3,"status":3,"lines":"1:1","total":700},{"id":2,"date":"30-01-2018","expiration":"30-01-2018","client":4,"status":2,"lines":"1:1","total":100},{"id":3,"date":"15-02-2018","expiration":"15-02-2018","client":3,"status":3,"lines":"1:1","total":700}]
The function should be in the InvoiceViewController. How do I get the result?
JSON object you are getting from http://ex.com/app/api/clientList/get should be an array and parameter keys should be same as property name of ClientsList.
struct ClientsList: Decodable {
let name: String
let vatNumber: String
let address: String
let cap: String
let city: String
let prov: String
let tel: String
let email: String
}
If you are not sure that you will receive all properties in the response JSON, then make that particular property optional. like:
struct ClientsList: Decodable {
let name: String
let vatNumber: String
let address: String
let cap: String
let city: String? // Not sure about receiving this property
let prov: String? // Not sure about receiving this property
let tel: String? // Not sure about receiving this property
let email: String
}
Same pattern you need to follow for http://ex.com/app/api/invoiceList/get and InvoiceList.