Error trying to parse JSON using URLSession in swift - ios

I'm trying to parse a test JSON from a http adress, but I get an error saying that
"No value associated with key CodingKeys(stringValue: \"name\", intValue: nil)
The JSON looks like this. It has been validated, so it should work ok:
{
"work": [
{
"name": "Jocke",
"job": "Developer",
"date": "1985-12-30T00:00:00+0000",
"best_book": {
"id": 33245,
"title": "DiscWorld",
"author": {
"id": 345,
"name": "Terry Prattchet"
}
}
},
{
"name": "Bob",
"job": "Construction worker",
"date": "2010-01-30T00:00:00+0000",
"best_book": {
"id": 375802,
"title": "Ender's Game (Ender's Saga, #1)",
"author": {
"id": 589,
"name": "Orson Scott Card"
}
}
}
]
}
The code looks like this:
struct People: Codable {
let name: String
let job: String
enum OuterKey: String, CodingKey {
case work = "work"
}
enum codingKeys: String, CodingKey {
case name = "name"
case job = "job"
}
init(decoder: Decoder) throws {
let outerContainer = try decoder.container(keyedBy: OuterKey.self)
let innerContainer = try outerContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .work)
self.name = try innerContainer.decode(String.self, forKey: .name)
self.job = try innerContainer.decode(String.self, forKey: .job)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://api.myjson.com/bins/fe2eo") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedJson = try! jsonDecoder.decode([People].self, from: data)
}
}.resume()
}
}
I'm just trying to grasp the first two keys as of now, just to see if it works. But it doesn't even get past name.

Your api returns
[{"firstName":"Jocke","job":"developer"},{"firstName":"Anna","job":"construction"},{"firstName":"Peter","job":"pilot"}]
do {
let jsonDecoder = JSONDecoder()
let decodedJson = try jsonDecoder.decode([People].self, from: data)
}
catch {
print(error)
}
struct People: Codable {
let firstName, job: String
}

just try this
struct Work:Codeable {
let work:[People]
}
struct People: Codable {
let name: String
let job: String
}
do {
let jsonDecoder = JSONDecoder()
let decodedJson = try jsonDecoder.decode(Work.self, from: data)
}
catch {
print(error)
}
if you have same name as json keys you don't need to use codingkeys

Related

Swift Decode Array of Dictionaries inside String using Decodable Protocol

The JSON response is:-
{
"id" = "1"
"message" = "SUCCESS"
"data" = "[{\"name\":"FirstName",\"office_id\":1111,\"days_name\":\"Mon\"},
{\"name\":"SecondName:,\"office_id\":1112,\"days_name\":\"Tue\"}]"
}
I don't seems to understand how to approach decoding "data", In data model shall the data be declared as String? and I have been trying to figure out but don't seem to get any clue and stuck here for a while, if anyone can please shed some light to it, it would help a lot. The only problem I am facing is how to deal with "" double quotes wrapped around data array as shown in above response.
Data model and URLSession code is below:
struct Root : Codable {
let id : String?
let message : String?
let data : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case message = "message"
case data = "data"
}
}
struct insideData: Codable {
let name: String?
let officeId : Int?
let daysName: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case officeId = "office_id"
case daysName = "days_name"
}
}
URLSession.shared.dataTask(with: url!) { (responseData, httpUrlResponse , error) in
if(error == nil && responseData != nil && responseData?.count != 0){
let decoder = JSONDecoder()
do{
let result = try decoder.decode(Root.self, from: responseData!)
print(result.data!)
}
catch let error {
debugPrint("Error occured while decoding = \(error.localizedDescription)")
}
}
}.resume()
I save result.data! in a new variable and convert it to data and again use JSONDecoder but now with insideData.self struct but don't get desired output, not getting mapped with keys inside insideData struct.
I am just getting started with learning networking in swift so please pardon me for silly mistakes.
data value is JSON with JSON, ie it's a JSONString.
A way to parse it is to parse again the JSON String. To do so, you need to override init(from decoder:) of Root.
Let's first fix your JSON which isn't valid, to be able to use it in Playgrounds.
let jsonString = #"""
{
"id": "1",
"message": "SUCCESS",
"data": "[{\"name\":\"FirstName\",\"office_id\":1111,\"days_name\":\"Mon\"}, {\"name\":\"SecondName:\", \"office_id\":1112,\"days_name\":\"Tue\"}]"
}
"""#
Then, change data: let data : [InsideData]
You could then:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.message = try container.decodeIfPresent(String.self, forKey: .message)
guard let dataString = try container.decodeIfPresent(String.self, forKey: .data) else {
self.data = []
return
}
self.data = try JSONDecoder().decode([InsideData].self, from: Data(dataString.utf8))
}
If you don't like creating a new decoder, you can pass one in userInfo of the decoder:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decodeIfPresent(String.self, forKey: .id)
self.message = try container.decodeIfPresent(String.self, forKey: .message)
guard let dataString = try container.decodeIfPresent(String.self, forKey: .data) else {
self.data = []
return
}
guard let insideDecoder = decoder.userInfo[CodingUserInfoKey(rawValue: "InsideDecoder")!] as? JSONDecoder else {
self.data = []
return
}
self.data = try insideDecoder.decode([InsideData].self, from: Data(dataString.utf8))
}
Then the root decoding:
let decoder = JSONDecoder()
let insideDecoder = JSONDecoder()
decoder.userInfo = [CodingUserInfoKey(rawValue: "InsideDecoder")!: insideDecoder]
do {
let root = try decoder.decode(Root.self, from: Data(jsonString.utf8))
print(root)
} catch {
print("Error: \(error)")
}
The provided JSON looks to be invalid.
In json you don't use the = sign but the : sign between a key and value.
You are missing , behind each value.
There is a typo behind SecondName :, should be ",
It's weird to have quotes around your data array. It can be easier decoded when you have them removed.
Suggested JSON changes:
{
"id": "1",
"message": "SUCCESS",
"data": [
{"name":"FirstName","office_id":1111,"days_name":"Mon"},
{"name":"SecondName","office_id":1112,"days_name":"Tue"}
]
}
I've tested decoding this json in Playground and it seems to work:
struct DataEntity: Decodable {
let name: String
let officeId: Int
let daysName: String
enum CodingKeys: String, CodingKey {
case name = "name"
case officeId = "office_id"
case daysName = "days_name"
}
}
struct RootEntity: Decodable {
let id: String
let message: String
let data: [DataEntity]
}
struct Mapper {
func map(json: String) -> RootEntity {
guard let rootJsonData = json.data(using: .utf8) else {
fatalError("Couldn't convert json string to data")
}
do {
let rootEntity = try JSONDecoder().decode(RootEntity.self, from: rootJsonData)
return rootEntity
} catch let error {
fatalError(error.localizedDescription)
}
}
}
let jsonToDecode = """
{
"id": "1",
"message": "SUCCESS",
"data": [
{"name":"FirstName","office_id":1111,"days_name":"Mon"},
{"name":"SecondName","office_id":1112,"days_name":"Tue"}
]
}
"""
let rootEntity = Mapper().map(json: jsonToDecode)
print(rootEntity)
Print output:
RootEntity(id: "1", message: "SUCCESS", data: [DataEntity(name: "FirstName", officeId: 1111, daysName: "Mon"), DataEntity(name: "SecondName", officeId: 1112, daysName: "Tue")])

Get dataset from JSON by parameter

Trying to find unique dataset from JSON using my parameter.
I've got JSON "userDetails":
{
"userDetails": [
{
"name": "Sabine",
"imageUrl" :"https://randomuser.me/api/portraits/men/82.jpg",
"content": [
{
"type": "video",
"url": "https://storage.googleapis.com/coverr-main/mp4/Travaho.mp4"
},
{
"type": "image",
"url": "https://picsum.photos/640/1136/?image=281"
}
]
},
{"name": "Keila Maney",
"imageUrl" :"https://randomuser.me/api/portraits/women/81.jpg",
"content": [
{
"type": "image",
"url": "https://picsum.photos/640/1136/?image=273"
},
{
"type": "video",
"url": "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4"
}
]
}
]
}
I know that my user is Keila Maney (I've got that users attribute in my code) and need to send that parameter to JSON, and receive back ONLY Keila Maney attributes (it's imageUrl, content (type, url)) and then parse them.
Are there any standard methods for searching in JSON?
Thanks in advance!
Just create object of UserDetails like: var userData: [UserDetails]?
and check this in a loop userData[x].name == "Keila Maney". then this x will be your index for Keila Maney and your can fetch its all data. After binding your json data to UserModel object. These are the three classes you will create from your json
UserModel
struct UserModel : Codable {
let userDetails : [UserDetails]?
enum CodingKeys: String, CodingKey {
case userDetails = "userDetails"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userDetails = try values.decodeIfPresent([UserDetails].self, forKey:.userDetails)
}
}
UserDetails
struct UserDetails : Codable {
let name : String?
let imageUrl : String?
let content : [Content]?
enum CodingKeys: String, CodingKey {
case name = "name"
case imageUrl = "imageUrl"
case content = "content"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
imageUrl = try values.decodeIfPresent(String.self, forKey: .imageUrl)
content = try values.decodeIfPresent([Content].self, forKey: .content)
}
}
Content
struct Content : Codable {
let type : String?
let url : String?
enum CodingKeys: String, CodingKey {
case type = "type"
case url = "url"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decodeIfPresent(String.self, forKey: .type)
url = try values.decodeIfPresent(String.self, forKey: .url)
}
}
Example of Json:
{
"places": [
{
"name": "Ulrikh",
"id": 555,
},
{
"name": "Place 2",
"id": 1,
}
]
}
Call:
self.getDataByID(id: 555)
Swift Methods:
func getDataByID(id : Int) {
let data = self.getData()
for i in 0..<data.count
{
if(Int((((data as NSDictionary)["places"] as! NSArray)[i] as! NSDictionary)["id"] as! Int) == id)
{
print("here is result:")
print((((data as NSDictionary)["places"] as! NSArray)[i]))
}
}
}
func getData() -> NSDictionary {
do{
let path = Bundle.main.path(forResource: "test", ofType: "json")
let jsonData = NSData(contentsOfFile: path!)
let jsonResult:NSDictionary! = try JSONSerialization.jsonObject(with: jsonData! as Data , options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
return jsonResult
} catch let error as NSError {
print(error)
}
return NSDictionary()
}
don't thank :)

How do I parse this nested JSON using Codable with Swift?

I am trying to parse this JSON using Codable:
{
"users": [
{
"id": 1,
"name": "Allen Carslake",
"userName": "acarslake0",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-07-08T00:00:00.000+0000"
},
{
"id": 2,
"name": "Revkah Antuk",
"userName": "rantuk1",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-07-05T00:00:00.000+0000"
},
{
"id": 3,
"name": "Mirna Saffrin",
"userName": "msaffrin2",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-05-19T00:00:00.000+0000"
},
{
"id": 4,
"name": "Haily Eilers",
"userName": "heilers3",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-06-28T00:00:00.000+0000"
},
{
"id": 5,
"name": "Oralie Polkinhorn",
"userName": "opolkinhorn4",
"profileImage": "https://source.unsplash.com/random/400x400",
"createdDate": "2019-06-04T00:00:00.000+0000"
}
]
}
I am keeping the URL private on here but it is returning JSON above. So far this is my code:
import UIKit
struct User: Codable {
let id: Int
let name: String
let userName: String
let profileImage: String
let createdDate: String
}
struct Users: Codable {
let users: String
}
let url = URL(string: "")!
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data {
let users = try? JSONDecoder().decode([User].self, from: data)
print(users)
}
}.resume()
I need to be able to access the User properties but I think the nesting is making it difficult for me. Any help is awesome!! Thank you!!
First of all: Catch always the DecodingError and print it. It tells you exactly what's wrong.
The error occurs because you are ignoring the root object Users. Your code works if you decode(Users.self.
My suggestions:
Decode createdDate as Date adding a appropriate date decoding strategy.
Decode profileImage as URL (for free).
Handle all errors.
struct Root : Decodable { // `Users` and `User` is too confusing
let users: [User]
}
struct User : Decodable {
let id: Int
let name: String
let userName: String
let profileImage: URL
let createdDate: Date
}
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
for user in result.users {
print(user.userName, user.id, user.createdDate)
}
} catch {
print(error)
}
}.resume()
The root of the json is a dictionary not an array you can write a root class but it will be useless , so you need
URLSession.shared.dataTask(with: url) { data, _, _ in
do {
if let data = data {
let res = try JSONSerialization.jsonObject(with: data) as! [String:Any]
let usersData = try JSONSerialization.data(withJSONObject: res["users"])
let users = try JSONDecoder().decode([User].self, from: usersData)
print(users)
}
}
catch {
print(error)
}
}.resume()
Your Users struct (I renamed it to UsersResponse) should contain a users property of type [User]. Then you can do the following:
struct UsersResponse: Codable {
let users: [User]
}
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data = data else { return }
if let users = try? JSONDecoder().decode(Users.self, from: data).users {
users.forEach { user in
print("A user called \(user.name) with an id of \(user.id).")
}
}
}.resume()
Please try the below code. This is working for me.
Model Class:
struct UsersResponse: Codable {
let users: [User]
}
struct User: Codable {
let id: Int
let name: String
let userName: String
let profileImage: String?
let createdDate: String
}
Network class:
public enum EndPoints: String {
case prod = "ProdURL"
case test = "testURL"
}
public enum Result<T> {
case success(T)
case failure(Error)
}
final public class Networking: NSObject {
// MARK: - Private functions
private static func getData(url: URL,
completion: #escaping (Data?, URLResponse?, Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: completion).resume()
}
/// fetchUsersResponse function will fetch the User Response and returns
/// Result<UsersResponse> as completion handler
public static func fetchUsersResponse(shouldFail: Bool = false, completion: #escaping (Result<UsersResponse>) -> Void) {
var urlString: String?
if shouldFail {
urlString = EndPoints.test.rawValue
} else {
urlString = EndPoints.prod.rawValue
}
guard let mainUrlString = urlString, let url = URL(string: mainUrlString) else { return }
Networking.getData(url: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
}
guard let data = data, error == nil else { return }
do {
let decoder = JSONDecoder()
//decoder.dateDecodingStrategy = .millisecondsSince1970
decoder.dateDecodingStrategy = .formatted(setDateFormat())
let json = try decoder.decode(UsersResponse.self, from: data)
completion(.success(json))
} catch let error {
completion(.failure(error))
}
}
}
func setDateFormat() -> DateFormatter {
let dateFormat = DateFormatter()
dateFormat.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return dateFormat
}

Not able to parse api response

I have an api response that looks like so…I get the response from the api properly..
{
"status": "success",
"data": {
"meta": {
"url": "htt..://www.abc.com",
"title": “ASD - Log In or Sign Up",
"description": "Create an account or log in….”,
"display_url": "htt..://www.abc.com/",
"video_url": "",
"image": "htt..://www.asd.com/images/asds_325x325.png",
"img_wxh": "325x325"
}
}
}
The model class with which I parse this data is given like so..
struct MetaData: Codable {
let status: String?
let data: DataClass?
}
struct DataClass: Codable {
let meta: Meta
}
struct Meta: Codable {
let url: String
let title, description: String
let displayURL: String
let videoURL: String
let image: String
let imgWxh: String
enum CodingKeys: String, CodingKey {
case url = "url"
case title = "title"
case description = "description"
case displayURL = "display_url"
case videoURL = "video_url"
case image = "image"
case imgWxh = "img_wxh"
}
}
The api call that is being made is gives as below...
WebServiceClient.shared.getMeta(withParameters: parameters) { [weak self] (isSuccess, result) in
guard let `self` = self else { return }
if isSuccess, result != nil {
if let jsonData = try? JSONSerialization.data(withJSONObject: result as Any, options: []) {
do {
let metaData = try JSONDecoder().decode(MetaData.self, from: jsonData)
self.metaDataImageView.sd_setImage(with: URL(string: metaData.data?.meta.image ?? ""), completed: nil)
self.urlLabel.text = metaData.data?.meta.url
self.titleLabel.text = metaData.data?.meta.title
self.urlDescriptionLabel.text = metaData.data?.meta.description
} catch {
print("error \(error)")
}
}
}
But I get all data as nil...what could be the reason..?
I get nothing in metaData...
Here is the code I tried to parse your data
struct MetaData: Codable {
let status: String?
let data: DataClass?
}
struct DataClass: Codable {
let meta: Meta
}
struct Meta: Codable {
let url: String
let title, description: String
let displayURL: String
let videoURL: String
let image: String
let imgWxh: String
enum CodingKeys: String, CodingKey {
case url = "url"
case title = "title"
case description = "description"
case displayURL = "display_url"
case videoURL = "video_url"
case image = "image"
case imgWxh = "img_wxh"
}
}
let jsonString = """
{
"status": "success",
"data": {
"meta": {
"url": "htt..://www.abc.com",
"title": "ASD - Log In or Sign Up ",
"description": "Create an account or log in….",
"display_url": "htt..://www.abc.com/",
"video_url": "",
"image": "htt..://www.asd.com/images/asds_325x325.png",
"img_wxh": "325x325"
}
}
}
"""
let jsonData = jsonString.data(using: .utf8)
do {
let parsedData = try JSONDecoder().decode(MetaData.self, from: jsonData!)
print(parsedData)
} catch {
print(error.localizedDescription)
}
And it works.
Also your json have some issue so make sure you validate your json format.
you can use jsonlint.com for validating json.

Value of type '[Root]' has no member 'commit'. How can i decode JSON root array

I am trying to parse JSON using codable. I am able to decode it but it is in a root array and i am unable to print each value on by it self. the compiler is complaining saying Value of type '[Root]' has no member 'commit'. How can i change this to print the values. Below is the JSON
[
{
"sha": "3f4227ec2894bb354b145deff9dbc1adc6b6d6f2",
"node_id": "MDY6Q29tbWl0NDQ4Mzg5NDk6M2Y0MjI3ZWMyODk0YmIzNTRiMTQ1ZGVmZjlkYmMxYWRjNmI2ZDZmMg==",
"commit": {
"author": {
"name": "Slava Pestov",
"email": "sviatoslav.pestov#gmail.com",
"date": "2018-08-12T08:09:22Z"
}
}
},
{
"sha": "3f4227ec2894bb354b145deff9dbc1adc6b6d6f2",
"node_id": "MDY6Q29tbWl0NDQ4Mzg5NDk6M2Y0MjI3ZWMyODk0YmIzNTRiMTQ1ZGVmZjlkYmMxYWRjNmI2ZDZmMg==",
"commit": {
"author": {
"name": "Slava Pestov",
"email": "sviatoslav.pestov#gmail.com",
"date": "2018-08-12T08:09:22Z"
}
}
}
]
I decode it here
struct Root: Decodable {
let commit: Author
}
struct Author: Decodable {
let author: People
}
struct People: Decodable {
let name: String?
let date: String?
let email: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getCommits()
}
func getCommits() {
let urlString = "https://api.github.com/repos/apple/swift/commits"
guard let url = URL(string: urlString) else {
print("Couldn't fetch JSON")
return
}
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { (data, response, error) in
guard data != nil && error == nil else {
print(data ?? "")
return
}
do {
let decoder = JSONDecoder()
let result = try decoder.decode([Root].self, from: data!)
print(result.commit)
print(result.commit.author.name)
} catch let decodeError {
print("Failed to decode json:", decodeError)
}
}
dataTask.resume()
}
}
And here is my output in the console. I would like to be able to print only the name, date and email.
[Gihhub.Commits(commit: Gihhub.Author(author: Gihhub.People(name: Optional("Slava Pestov"), date: Optional("2018-08-12T08:09:22Z"), email: Optional("sviatoslav.pestov#gmail.com")))), Gihhub.Commits(commit: Gihhub.Author(author: Gihhub.People(name: Optional("Slava Pestov"), date: Optional("2018-08-12T03:47:22Z"), email: Optional("spestov#apple.com")))), Gihhub.Commits(commit: Gihhub.Author(author: Gihhub.People(name: Optional("Slava Pestov"), date: Optional("2018-08-12T03:47:08Z"), email: Optional("spestov#apple.com"))))]
result is an array you need
result.forEach {
print($0.commit.author.name)
print($0.commit.author.date)
print($0.commit.author.email)
}

Resources