How to extract the result of json with Codable - json

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)"

Related

How would I print the title property in this returned JSON in Swift using URLSession and dataTask?

I am working with an api and getting back some strangely formatted JSON.
[
{
"title": "Wales' new £2bn space strategy hopes",
"url": "https://www.bbc.co.uk/news/uk-wales-60433763",
"source": "bbc"
},
{
"title": "Could Port Talbot become a centre of space tech? Video, 00:02:02Could Port Talbot become a centre of space tech?",
"url": "https://www.bbc.co.uk/news/uk-wales-60471170",
"source": "bbc"
},
]
As you can see, there is no object name I can latch on to. I've tried making a model like this
struct SpaceNewsModel: Identifiable, Codable {
var id = UUID()
let title: String
let url: String
let source: String
}
But once I get to using JSONDeocder() with the following code
let decoder = JSONDecoder()
if let safeData = data {
do {
let astroNews = try decoder.decode(SpaceNewsModel.self, from: safeData)
print(astroNews)
} catch {
print("DEBUG: Error getting news articles \(error.localizedDescription)")
}
}
I get the error DEBUG: Error getting news articles The data couldn’t be read because it isn’t in the correct format.
So how would you go about printing out each title, url, or source to the console? I've worked with JSON before and they are usually formatted differently.
I found a workaround for this. Removing the identifiable protocol lets me access the data. Like so
struct SpaceNewsModel: Codable {
let title: String
let url: String
let source: String
}
Then I can use decoder as normal
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
if error != nil {
print(error!)
} else {
let decoder = JSONDecoder()
if let safeData = data {
do {
let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
print(astroNews[0].title)
} catch {
print("DEBUG: Error getting news articles \(error)")
}
}
I did change let astroNews = try decoder.decode(SpaceNewsModel.self, from: safeData) to let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
Even with changing it to array type or not, as long as I had my SpaceNewsModel following the identifiable protocol, it would NOT work. It's a strange workaround, but it works for now.
in addition to
let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
use this common approach for your SpaceNewsModel. Having it Identifiable is very useful in SwiftUI.
struct SpaceNewsModel: Identifiable, Codable {
let id = UUID() // <-- here, a let
let title: String
let url: String
let source: String
enum CodingKeys: String, CodingKey { // <-- here
case title,url,source
}
}

json file is missing/ struct is wrong

I have been trying to get this code to work for like 6 hours. I get the error: "failed to convert The data couldn’t be read because it is missing." I don't know while the File is missing is there something wrong in my models(structs). Do I need to write a struct for very json dictionary? Currently I have only made those JSON dictionaries to a struct, which I actually need. The full JSON file can be found at https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00 . I want to be able to print the time of the sunrise, sunset and solar noon as well as the elevation of the sun at solar noon. It's currently 1 am and I am desperate. Good Night!
class ViewController: NSViewController {
#IBOutlet weak var sunriseField: NSTextField!
#IBOutlet weak var sunsetField: NSTextField!
#IBOutlet weak var daylengthField: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00"
getData(from: url)
// Do any additional setup after loading the view.
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
var result: MyTime?
do {
result = try JSONDecoder().decode(MyTime.self, from: data)
}
catch {
print("failed to convert \(error.localizedDescription)")
}
guard let json = result else {
return
}
let sunrise1 = json.sunrise.time
DispatchQueue.main.async { [weak self] in
self?.sunriseField.stringValue = sunrise1
}
print(json)
})
task.resume()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
struct MyData : Codable {
let location : Location
let meta : Meta
}
struct MyTime : Codable {
let solarnoon : Solarnoon
let sunset : Sunset
let sunrise : Sunrise
}
struct Location : Codable {
let height : String
let time : [MyTime]
let longitude : String
let latitude : String
}
struct Meta : Codable {
let licenseurl : String
}
struct Solarnoon : Codable {
let desc : String
let time : String
let elevation : String
}
struct Sunrise : Codable {
let desc : String
let time : String
}
struct Sunset : Codable {
let time : String
let desc : String
}
You don't really have a SwiftUI class, but that is a different question. I am going to work on fixing getData(). I have tried to comment it extensively, but let me know if you have any questions.
private func getData(from url: String) {
// Personally I like converting the string to a URL to unwrap it and make sure it is valid:
guard let url = URL(string: urlString) else {
print("Bad URL: \(urlString)")
return
}
let config = URLSessionConfiguration.default
// This will hold the request until you have internet
config.waitsForConnectivity = true
URLSession.shared.dataTask(with: url) { data, response, error in
// A check for a bad response
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
print("Bad Server Response")
return
}
if let data = data {
// You can print(data) here that will shown you the number of bytes returned for debugging.
//This work needs to be done on the main thread:
DispatchQueue.main.async {
let decoder = JSONDecoder()
if let json = try? decoder.decode(MetDecoder.self, from: data){
print(json)
//At this point, you have your data in a struct
self.sunriseTime = json.dailyData?.solarData?.first?.sunrise?.time
}
}
}
}
.resume()
}
With regard to your structs, you only need them for the data you are trying to parse. If you don't need it, don't worry about it. I would make this a separate class named MetDecoder or something that makes sense to you and indicates the decoder for your JSON. You will also note that I changed the names of some of the variables. You can do that so long as you use a CodingKeys enum to translate your JSON to your struct as in the case of dailyData = "location", etc. This is ugly JSON, and I am not sure why the Met decided everything should be a string, but this decoder is tested and it works:
import Foundation
// MARK: - MetDecoder
struct MetDecoder: Codable {
let dailyData: DailyData?
enum CodingKeys: String, CodingKey {
case dailyData = "location"
}
}
// MARK: - Location
struct DailyData: Codable {
let solarData: [SolarData]?
enum CodingKeys: String, CodingKey {
case solarData = "time"
}
}
// MARK: - Time
struct SolarData: Codable {
let sunrise, sunset: RiseSet?
let solarnoon: Position?
let date: String?
enum CodingKeys: String, CodingKey {
case sunrise, sunset, solarnoon, date
}
}
// MARK: - HighMoon
struct Position: Codable {
let time: String?
let desc, elevation, azimuth: String?
}
// MARK: - Moonrise
struct RiseSet: Codable {
let time: String?
let desc: String?
}
You should see what the National Weather Service does to us in the US to get the JSON. Lastly, when working on JSON I find the following pages VERY helpful:
JSON Formatter & Validator which will help you parse out the wall of text that gets returned in a browser, and
quicktype which will parse JSON into a programming language like Swift. I will warn you that the parsing can give some very ugly structs in Swift, but it gives you a nice start. I used both sites for this answer.
Apple's new framework, Combine, helps to simplify the code needed for async fetch requests. I have used the MetDecoder in #Yrb's response above (you can accept his answer) and altered the getData() function. Just make sure you import Combine at the top.
import Combine
var sunriseTime: String?
var sunsetTime: String?
var solarNoonTime: String?
var solarNoonElevation: String?
func getData() {
let url = URL(string: "https://api.met.no/weatherapi/sunrise/2.0/.json?lat=40.7127&lon=-74.0059&date=2020-12-22&offset=-05:00")!
URLSession.shared.dataTaskPublisher(for: url)
// fetch on background thread
.subscribe(on: DispatchQueue.global(qos: .background))
// recieve response on main thread
.receive(on: DispatchQueue.main)
// ensure there is data
.tryMap { (data, response) in
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
// decode JSON data to MetDecoder
.decode(type: MetDecoder.self, decoder: JSONDecoder())
// Handle results
.sink { (result) in
// will return success or failure
print("completion: \(result)")
} receiveValue: { (value) in
// if success, will return MetDecoder
// here you can update your view
print("value: \(value)")
if let solarData = value.dailyData?.solarData?.first {
self.sunriseTime = solarData.sunrise?.time
self.sunsetTime = solarData.sunset?.time
self.solarNoonTime = solarData.solarnoon?.time
self.solarNoonElevation = solarData.solarnoon?.elevation
}
}
// After recieving response, the URLSession is no longer needed & we can cancel the publisher
.cancel()
}
from the json data (2nd entry), looks like you need at least:
struct MyTime : Codable {
let solarnoon : Solarnoon?
let sunset : Sunset?
let sunrise : Sunrise?
}
and you need:
var result: MyData?
do {
result = try JSONDecoder().decode(MyData.self, from: data)
}
catch {
print("----> error failed to convert \(error)")
}

How do I parse the nested JSON data from Brawl Stars api in Swift?

The API website I used. I made an account -> Documentation -> Brawlers if you want to try it out
Here is part of the JSON I want to parse. I want to print the name inside the "starPowers" array as well as the "gadgets" array.
{
"items":[
{
"id":16000014,
"name":"BO",
"starPowers":[
{
"id":23000090,
"name":"CIRCLING EAGLE"
},
{
"id":23000148,
"name":"SNARE A BEAR"
}
],
"gadgets":[
{
"id":23000263,
"name":"SUPER TOTEM"
},
{
"id":23000289,
"name":"TRIPWIRE"
}
]
}
]
}
I tried this first way of parsing the JSON which worked but I couldn't find a way to print the "name" inside the "starPower" or "gadgets" array.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let apiToken = "abcdefg"
if let url = URL(string: "https://api.brawlstars.com/v1/brawlers") {
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("Bearer \(apiToken)", forHTTPHeaderField: "authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
} else {
guard let data = data else {return}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as AnyObject
print(jsonResult)
if let items = jsonResult["items"] as? NSArray {
for item in items as [AnyObject] {
if let name = item["name"] {
print(name!)
}
if let gadgets = item["gadgets"] {
print(gadgets!)
}
if let starPowers = item["starPowers"]{
print(starPowers!)
}
}
}
} catch {
print("JSON processing failed: \(error.localizedDescription)")
}
}
}.resume()
} else {
print("Something went wrong")
}
}
}
So I added another file with this data:
struct StarPowers: Codable {
let starPowerName: String
}
struct Gadgets: Codable {
let gadgetName: String
}
struct Items: Codable {
let name: String
let starPowers: [StarPowers]
let gadgets: [Gadgets]
}
And replaced the code inside the do statement with this but it returned "JSON processing failed: The data couldn’t be read because it is missing." (Catch statement)
let items = try JSONDecoder().decode(Items.self, from: data)
print(items.name.first ?? "")
I'm still fairly new to Swift in general as well as StackOverflow, so any help or feedback will be greatly appreciated. Thanks!
When using the Codable APIs you want to model all the data you see. So this JSON:
{
"items":[
{
"id":16000014,
"name":"BO",
"starPowers":[
{
"id":23000090,
"name":"CIRCLING EAGLE"
},
{
"id":23000148,
"name":"SNARE A BEAR"
}
],
"gadgets":[
{
"id":23000263,
"name":"SUPER TOTEM"
},
{
"id":23000289,
"name":"TRIPWIRE"
}
]
}
]
}
should result in these structs:
//This represents one star power
struct StarPower: Codable {
let id: Int
let name: String
}
//This represents one gadget
struct Gadget: Codable {
let id: Int
let name: String
}
//This represents one "item" (I think they're brawlers, but I didn't make an account so I can't confirm what the API calls them
struct Item: Codable {
let id: Int
let name: String
let starPowers: [StarPower]
let gadgets: [Gadget]
}
However, the excerpt that you provided is actually of type [String:[Item]] (a dictionary (aka a JSON object) with one string key, with the value being an array of items. You could make a Codable struct to handle that, or you can just do this:
let decoded = try! JSONDecoder().decode([String:[Item]].self, from: data)
let items = decoded["items"]!
//items is an array of type [Item]
// Using your example, it would only have one element.
let element = items.first!
for starPower in element.starPowers {
print(starPower.name)
}
for gadget in element.gadgets {
print(gadget.name)
}
(keep in mind that I'm force-unwrapping and force-trying because I'm assuming that fetching the data worked fine and that it is correct. You should probably check these assumptions and use constructs such as if-let and do-catch)
Name the properties of your codable structs exactly as the keys in the corresponding JSON:
struct StarPower: Codable {
let name: String
}
struct Gadget: Codable {
let name: String
}
struct Item: Codable {
let name: String
let starPowers: [StarPower]
let gadgets: [Gadget]
}
The decoder couldn't find the data, because "gadgetName" and "starPowerName" are not part of the JSON you provided.
Another tip: use singular while naming structs, so one element of gadgets is a gadget. the decoder doesn't care how you name your structs, it only cares about the properties

Syntax for accessing struct property with enum type in JSON model

I am trying to access a url string contained within some JSON data.
The string is contained within the "urls" array with type "detail" as can be seen below.
JSON DATA
I used quicktype to construct my model as below:
struct Response: Codable {
let data: DataClass
}
struct DataClass: Codable {
let results: [Result]
}
struct Result: Codable {
let name: String
let description: String
let thumbnail: Thumbnail
let urls: [URLElement]
}
struct Thumbnail: Codable {
let path: String
let thumbnailExtension: Extension
enum CodingKeys: String, CodingKey {
case path
case thumbnailExtension = "extension"
}
}
enum Extension: String, Codable {
case jpg = "jpg"
}
struct URLElement: Codable {
let type: URLType
let url: String
}
enum URLType: String, Codable {
case comiclink = "comiclink"
case detail = "detail"
case wiki = "wiki"
}
I have tried accessing it by declaring it like so...
var urlelement: URLElement!
override func viewDidLoad() {
super.viewDidLoad()
let detailurl = urlelement.url
print(detailurl)
... but it always returns an empty string. Any suggestions will be most welcome. Thanks!
First Download the JSON then user JSONDecoder
let url = URL(string: "your url")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("error \(error.localizedDescription)")
return
}
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(Response.self, from: data)
// use this
response?.data.results.forEach({ (rsl) in
rsl.urls.forEach({ (element) in
print(element.type, element.url)
})
})
// or this one
for rsl in response!.data.results {
for element in rsl.urls {
print(element.type, element.url)
}
}
} catch let error {
print("error while decoding the json \(error.localizedDescription)")
}
}.resume()

decoding a Codable in Swift

Having troubles getting this to work: I am trying to abstract the JSON-decoding into a function, taking as arguments a Codable plus some Data.
Therefore, I need to have the following function-signature if possible for this:
func doTheJSONDecoding(cdbl: Codable, data: Data) {...}
Here is my code, starting with the data-model. There are two examples below....
import UIKit
import Foundation
struct MyStructCodable : Codable {
let items : [MyValue]?
}
struct MyValue : Codable {
let value : String?
}
let dta: Data = """
{
"items": [
{
"value": "Hello1"
}
]
}
""".data(using: .utf8)!
Then the two examples:
// Example 1: this code works fine !!!!!!!!!!!!!!!!!!!!!!!!
let decoder = JSONDecoder()
do {
let result = try decoder.decode(MyStructCodable.self, from: dta)
print(result.items?[0].value ?? "")
} catch {
print(error)
}
// above code prints: Hello1
// Example 2: this code does not work - WHY ???????????????
func doTheJSONDecoding(cdbl: Codable, data: Data) {
let decoder = JSONDecoder()
do {
let result = try decoder.decode(cdbl, from: data)
print(result.items?[0].value ?? "")
} catch {
print(error)
}
}
let myValue = MyValue(value: "Hello2")
let myStructyCodable = MyStructCodable(items: [myValue])
doTheJSONEncoding(cdbl: myStructyCodable, data: dta)
The error thrown is inside the function, it says:
Is there any way so that I can keep the function signature (i.e. func doTheJSONDecoding(cdbl: Codable, data: Data) and still getting this to work ?? Any help appreciated.
Here is my attempt to get your func to work, it can probably be improve but it does return a properly decoded object. Note that it takes the type of object rather than an object and it is that type T that implement Decodable.
func doTheJSONEncoding<T: Decodable>(cdbl: T.Type, data: Data) -> T? {
let decoder = JSONDecoder()
do {
let result = try decoder.decode(cdbl.self, from: data)
return result
} catch {
print(error)
}
return nil
}
//testing it
let myValue = MyValue(value: "Hello2")
let myStructyCodable = MyStructCodable(items: [myValue])
let decoded = doTheJSONEncoding(cdbl: MyStructCodable.self, data: dta)
print(decoded?.items?[0].value ?? "")