Swift JSON with dynamic Keys - json

I am trying to parse JSON using Swift, which has dynamic keys. Tried several ways but still did not find the solution. Could you please help me ?
I am trying to parse NativeName, which is dynamic based on which language country name is present.
API: https://restcountries.com/v3.1/all
struct Objects: Codable {
let name: Name
let cca2 : String
let flag: String
}
struct Name: Codable {
let common, official: String
let nativeName: NativeName
}
struct NativeName: Codable {
var deu : Deu
}
struct Deu: Codable {
let official, common: String?
}
and here is JSON Model:
class ParsingService {
static let shared = ParsingService()
func fetchData() {
guard let url = URL(string: "https://restcountries.com/v3.1/all") else {
print("DEBUG: URL is nill")
return}
let session = URLSession.shared
let task = session.dataTask(with: url) { data, _, error in
guard let retrievedData = data, error == nil else {
print("DEBUG: Data is not available")
return}
print("DEBUG: Data is available \(retrievedData)")
guard let decodedData = self.JSONParsing(inputData: retrievedData) else {
print("DEBUG: Missing data")
return}
print("DEBUG: Data is there")
print("DEBUG: \(decodedData[0].cca2)")
print("DEBUG: \(decodedData[0].flag)")
print("DEBUG: \(decodedData[0].name.nativeName.deu.official)")
DispatchQueue.main.async {
print(decodedData.currencies)
}
}
task.resume()
}
func JSONParsing(inputData: Data)-> [Objects]? {
let decoder = JSONDecoder()
do {
let data = try? decoder.decode([Objects].self, from: inputData)
return data
} catch {
print("DEBUG: Cannot get data")
return nil
}
}
}

you could try this approach:
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
fetchData() { results in
print("---> results: \(results.count) \n")
for i in 0..<3 {
print("---> results[\(i)]: \(results[i].name.nativeName)")
}
}
}
}
// todo deal with errors
func fetchData(completion: #escaping ([Objects]) -> Void) {
let url = URL(string: "https://restcountries.com/v3.1/all")
guard let url = url else { completion([]); return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { completion([]); return }
do {
let results = try JSONDecoder().decode([Objects].self, from: data)
completion(results)
}
catch {
print("Error: \(error)")
completion([])
}
}.resume()
}
}
struct Objects: Codable {
let name: Name
let cca2 : String
let flag: String
}
struct Deu: Codable {
let official, common: String?
}
struct Name: Codable {
let common, official: String
let nativeName: NativeName? // <-- here
}
// -- here
struct NativeName: Codable {
var lang: [String: Deu]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
lang = try container.decode([String: Deu].self)
}
func encode(to encoder: Encoder) throws {
// todo
}
}
Note, you could also use a Tuple, such as var lang: (key: String, value: Deu)

Related

How to get json fields?

I follow a lesson from one course
And I need to get json, but i want get another json than in a lesson.
So this is my json:
https://api.scryfall.com/cards/search?q=half
And code:
struct Card {
var cardId: String
var name: String
var imageUrl: String
var text: String
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String,
let cardId = dict["cardId"] as? String,
let imageUrl = dict["imageUrl"] as? String,
let text = dict["text"] as? String else { return nil }
self.cardId = cardId
self.name = name
self.imageUrl = imageUrl
self.text = text
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping(GetCardResponse) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
do {
print ("ok1")
let response = try GetCardResponse(json: json)
print ("ok2")
completion(response)
} catch {
print(error)
}
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
print(json)
} catch {
print(error)
}
}.resume()
}
}
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let array = json as? [[String: AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in array {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
Problem in struct GetCardResponse and [[String: AnyObject]] because I dont know how to parse this type of json. I tried to change them in the likeness of json. But I dont really understand how it works and in which part of code i need to put json["data"] or something like this... Help pls. I just want get json fields tcgplayer_id, name, art_crop
As of your code, you can parse the required details as:
struct Card {
var cardId: String = ""
var name: String = ""
var imageUrl: String = ""
var text: String = ""
init(dict: [String: Any]) {
if let obj = dict["name"] {
self.name = "\(obj)"
}
if let obj = dict["tcgplayer_id"] {
self.cardId = "\(obj)"
}
if let obj = dict["image_uris"] as? [String:Any], let url = obj["art_crop"] {
self.imageUrl = "\(url)"
}
if let obj = dict["oracle_text"] {
self.text = "\(obj)"
}
}
static func models(array: [[String:Any]]) -> [Card] {
return array.map { Card(dict: $0) }
}
}
class CardNetworkService{
private init() {}
static func getCards(url: String, completion: #escaping([Card]?) -> ()) {
guard let url = URL(string: url) else { return }
NetworkService.shared.getData(url: url) { (json) in
print ("ok1")
if let jData = json as? [String:Any], let data = jData["data"] as? [[String:Any]] {
let response = Card.models(array: data)
completion(response)
}
completion(nil)
}
}
}
class NetworkService {
private init() {}
static let shared = NetworkService()
func getData(url: URL, completion: #escaping (Any) -> ()) {
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
DispatchQueue.main.async {
completion(json)
}
} catch {
print(error)
}
}.resume()
}
}
CardNetworkService.getCards(url: "https://api.scryfall.com/cards/search?q=half") { (res) in
print(res ?? [])
}
Just paste this code in playground and it'll work.
Happy Coding :)
You are wrong get entry of data field.
First you need get data field in json. And parse to deeper.
Try use the code.
struct GetCardResponse{
let cards: [Card]
init(json: Any) throws {
guard let jsonObject = json as? [String: Any], let data = jsonObject["data"] as? [[String:AnyObject]] else { throw NetworkError.failInternetError }
var cards = [Card]()
for dictionary in data {
guard let card = Card(dict: dictionary) else { continue }
cards.append(card)
}
self.cards = cards
}
}
UPDATE:
init function in Card has something wrong. In your json cardId is not found
Card class maybe like this because cardId, imageUrl, text maybe not found. It is optional
struct Card {
var cardId: String?
var name: String
var imageUrl: String?
var text: String?
init?(dict: [String: AnyObject]){
guard let name = dict["name"] as? String else { return nil }
self.cardId = dict["cardId"] as? String
self.name = name
self.imageUrl = dict["imageUrl"] as? String
self.text = dict["text"] as? String
}
}
Try using Codable to parse the JSON data like so,
Create the models like,
struct Root: Decodable {
let cards: [Card]
enum CodingKeys: String, CodingKey {
case cards = "data"
}
}
struct Card: Decodable {
let tcgplayerId: Int
let name: String
let artCrop: String
}
Now parse your JSON data using,
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
}
You can access the properties in cards of response like so,
response.cards.first?.tcgplayerId

How to get value in json Swift

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

Need help by parsing with JSON

I am a total beginner with programming and Swift. One of the features my app should have is a label of the current temperature in New York City. The problem is I don't know if I am on the right path with my code. I tried things from many videos and articles but nothing seems to work. I am sure the answer is very easy. Thanks for any help!
I use the darksky api. This is my current code.
import UIKit
struct MyGitHub: Codable {
let temperature: Int
private enum CodingKeys: String, CodingKey {
case temperature
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
guard let gitUrl = URL(string: "here is my darksky api") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(MyGitHub.self, from: data)
print (gitData.temperature)
} catch let err {
print ("Err", err)
}
} .resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You need an umbrella struct for the root object and the value for key temperature is Double, not Int
struct Root: Decodable {
let currently : MyGitHub
}
struct MyGitHub: Decodable {
let temperature: Double
}
...
let gitData = try decoder.decode(Root.self, from: data)
print(gitData.currently.temperature)
You can try this
Alamofire.request(urlStr, method: .get, parameters:nil, encoding: JSONEncoding.default).responseJSON { response in
if let json = response.result.value as? [String:Any] {
if let main = json["currently"] as? [String:Any] {
if let temp = main["temperature"] as? NSNumber
{
// set lbl here
print(temp)
}
}
}
}
OR
struct Currently : Codable {
var currently:InnerItem
}
struct InnerItem : Codable {
var temperature:Double
}
with
guard let gitUrl = URL(string:urlStr) else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(Currently.self, from: data)
print ("sdhjsdjhjhdshjshjsjhddhsj" , gitData.currently.temperature)
} catch let err {
print ("Err", err)
}
} .resume()

How to parse JSON using swift 4

I am confusing to getting detail of fruit
{
"fruits": [
{
"id": "1",
"image": "https://cdn1.medicalnewstoday.com/content/images/headlines/271/271157/bananas.jpg",
"name": "Banana"
},
{
"id": "2",
"image": "http://soappotions.com/wp-content/uploads/2017/10/orange.jpg",
"title": "Orange"
}
]
}
Want to parse JSON using "Decodable"
struct Fruits: Decodable {
let Fruits: [fruit]
}
struct fruit: Decodable {
let id: Int?
let image: String?
let name: String?
}
let url = URL(string: "https://www.JSONData.com/fruits")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do{
let fruits = try JSONDecoder().decode(Fruits.self, from: data)
print(Fruits)
}catch {
print("Parse Error")
}
also can you please suggest me cocoapod library for fastly download images
The issue you are facing is because your JSON is returning different data for your Fruits.
For the 1st ID it returns a String called name, but in the 2nd it returns a String called title.
In addition when parsing the JSON the ID appears to be a String and not an Int.
Thus you have two optional values from your data.
As such your Decodable Structure should look something like this:
struct Response: Decodable {
let fruits: [Fruits]
}
struct Fruits: Decodable {
let id: String
let image: String
let name: String?
let title: String?
}
Since your URL doesn't seem to be valid, I created the JSON file in my main bundle and was able to parse it correctly like so:
/// Parses The JSON
func parseJSON(){
if let path = Bundle.main.path(forResource: "fruits", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONDecoder().decode(Response.self, from: data)
let fruitsArray = jsonResult.fruits
for fruit in fruitsArray{
print("""
ID = \(fruit.id)
Image = \(fruit.image)
""")
if let validName = fruit.name{
print("Name = \(validName)")
}
if let validTitle = fruit.title{
print("Title = \(validTitle)")
}
}
} catch {
print(error)
}
}
}
Hope it helps...
// Parse Json using decodable
// First in create Structure depends on json
//
//
//
struct Countory : Decodable {
let name: String
let capital: String
let region: String
}
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
var countories = try JSONDecoder().decode([Countory].self, from: data!)
for country in countories {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
}
} catch {
print(" not ")
}
}.resume()
Model sample:
public struct JsonData: Codable{
let data: [Data]?
let meta: MetaValue?
let linksData: LinksValue?
private enum CodingKeys: String, CodingKey{
case data
case meta
case linksData = "links"
}
}
enum BackendError: Error {
case urlError(reason: String)
case objectSerialization(reason: String)
}
struct APIServiceRequest {
static func serviceRequest<T>(reqURLString: String,
resultStruct: T.Type,
completionHandler:#escaping ((Any?, Error?) -> ())) where T : Decodable {
guard let url = URL(string: reqURLString) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
guard let responseData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let books = try decoder.decode(resultStruct, from: responseData)
completionHandler(books, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
}
To Access:
let apiService = APIServiceRequest()
var dataArray: [String: Any]? //global var
apiService.serviceRequest(reqURLString: endPoint, resultStruct: VariantsModel.self, completionHandler: {dataArray,Error in})
POST Method
func loginWS(endpoint: String, completionHandler: #escaping (Any?) -> Swift.Void) {
guard let sourceUrl = URL(string: endpoint) else { return }
let request = NSMutableURLRequest(url: sourceUrl)
let session = URLSession.shared
request.httpMethod = "POST"
request.addValue(vehiceHeader, forHTTPHeaderField: "X-Vehicle-Type")
request.addValue(contentHeader, forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data else { return }
do {
let responseData = try JSONDecoder().decode(JsonData.self, from: data)
print("response data:", responseData)
completionHandler(responseData)
} catch let err {
print("Err", err)
}
}.resume()
}

Parsing JSON with Swift 4 Decodable

I have been getting the following error every time I try to parse JSON in my program. I can't seem to figure it out.
"Expected to decode String but found an array instead.", underlyingError: nil
Here is the code I have been struggling with:
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]?
}
struct Page: Decodable {
let id: Int
let text: [String]
}
struct Chapter: Decodable {
var chapterNumber: Int
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
if let err = err {
print("Failed to fetch data from", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let books = try decoder.decode([Book].self, from: data)
books.forEach({print($0.title)})
} catch let jsonErr {
print("Failed to parse json:", jsonErr)
}
}.resume()
}
Are you sure that's the real error message?
Actually the error is supposed to be
"Expected to decode String but found a dictionary instead."
The value for key text is not an array of strings, it's an array of dictionaries
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
The struct Chapter is not needed.
Alternatively write a custom initializer and decode the dictionaries containing the chapter number as key and the text as value into an array of Chapter
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
let pages: [Page]
}
struct Page: Decodable {
let id: Int
var chapters = [Chapter]()
private enum CodingKeys : String, CodingKey { case id, chapters = "text" }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .chapters)
while !arrayContainer.isAtEnd {
let chapterDict = try arrayContainer.decode([String:String].self)
for (key, value) in chapterDict {
chapters.append(Chapter(number: Int(key)!, text: value))
}
}
}
}
struct Chapter: Decodable {
let number : Int
let text : String
}
This is working:
import UIKit
class ViewController: UIViewController {
private var books = [Book]()
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]
}
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.books = try decoder.decode([Book].self, from: data)
DispatchQueue.main.async {
for info in self.books {
print(info.title)
print(info.chapters)
print(info.pages[0].id)
print(info.pages[0].text)
print("-------------------")
}
}
} catch let jsonErr {
print("something wrong after downloaded: \(jsonErr) ")
}
}.resume()
}
}
// print
//Genesis
//50
//1
//[["1": "In the beg...]]
//-------------------
//Exodus
//40
//2
//[["1": "In the beginning God created...]]
//
If you need to print the value of each chapter in the book, I can use something like this:
import UIKit
class ViewController: UIViewController {
private var books = [Book]()
struct Book: Decodable {
let id: Int
let title: String
let chapters: Int
var pages: [Page]
}
struct Page: Decodable {
let id: Int
let text: [[String:String]]
}
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
func fetchJSON() {
let urlString = "https://api.myjson.com/bins/kzqh3"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, response, error in
if error != nil {
print(error!.localizedDescription)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
self.books = try decoder.decode([Book].self, from: data)
DispatchQueue.main.async {
for info in self.books {
print(info.title)
print(info.chapters)
print(info.pages[0].id)
//print(info.pages[0].text)
for cc in info.pages[0].text {
for (key, value) in cc {
print("\(key) : \(value)")
}
}
print("-------------------")
}
}
} catch let jsonErr {
print("something wrong after downloaded: \(jsonErr) ")
}
}.resume()
}
}
//Genesis
//50
//1
//1 : In the beginning God ...
//2 : But the earth became waste...
//.
//.
//.
//31 : And God saw everything...
//-------------------
//Exodus
//40
//2
//1 : In the beginning God...
//2 : But the earth became...
//.
//.
//.
//31 : And God saw everything