Swift 5: Decoding Nested JSON - ios

I'm having a little trouble decoding some JSON data into a struct. I've tried below methods and it doesn't work:
JSON:
{
"submission_date": "2020-02-28T14:21:46.000+08:00",
"status": "pending",
"requestor": {
"name": "Adam"
},
"claim_items": [
{
"date": "2020-02-20",
"description": "TV",
"currency": "MYR",
"amount": "103.0",
"amount_in_ringgit": "10.0"
},
{
"date": "2020-02-20",
"description": "Netflix",
"currency": "MYR",
"amount": "12.0",
"amount_in_ringgit": "10.0"
}
]
}
Struct Method 1:
struct ClaimDetail: Decodable {
let submission_date: String
let status: String
let requestor: Requestor
let claim_items: [ClaimItem]
}
struct Requestor: Decodable {
let name: String
init(json: [String:Any]) {
name = json["name"] as? String ?? ""
}
}
struct ClaimItem: Decodable {
let date: String
let description: String
let currency: String
let amount: String
let amount_in_ringgit: String
init(json: [String:Any]) {
date = json["date"] as? String ?? ""
description = json["description"] as? String ?? ""
currency = json["currency"] as? String ?? ""
amount = json["amount"] as? String ?? ""
amount_in_ringgit = json["amount_in_ringgit"] as? String ?? ""
}
}
Struct Method 2:
struct ClaimDetail: Decodable {
let submission_date: String
let status: String
let requestor: Requestor
let claim_items: [ClaimItem]
struct Requestor: Decodable {
let name: String
init(json: [String:Any]) {
name = json["name"] as? String ?? ""
}
}
struct ClaimItem: Decodable {
let date: String
let description: String
let currency: String
let amount: String
let amount_in_ringgit: String
init(json: [String:Any]) {
date = json["date"] as? String ?? ""
description = json["description"] as? String ?? ""
currency = json["currency"] as? String ?? ""
amount = json["amount"] as? String ?? ""
amount_in_ringgit = json["amount_in_ringgit"] as? String ?? ""
}
}
}
Struct Method 3 (via https://app.quicktype.io/):
// MARK: - ClaimDetail
struct ClaimDetail: Codable {
let submissionDate, status: String
let requestor: Requestor
let claimItems: [ClaimItem]
enum CodingKeys: String, CodingKey {
case submissionDate = "submission_date"
case status, requestor
case claimItems = "claim_items"
}
}
// MARK: - ClaimItem
struct ClaimItem: Codable {
let date, claimItemDescription, currency, amount: String
let amountInRinggit: String
enum CodingKeys: String, CodingKey {
case date
case claimItemDescription = "description"
case currency, amount
case amountInRinggit = "amount_in_ringgit"
}
}
// MARK: - Requestor
struct Requestor: Codable {
let name: String
}
URL Session
URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
if let data = data {
do {
let json = try JSONDecoder().decode(ClaimDetail.self, from: data)
print (json)
} catch let error {
print("Localized Error: \(error.localizedDescription)")
print("Error: \(error)")
}
}
}.resume()
All returns below error:
Localized Error: The data couldn’t be read because it isn’t in the correct format.
Error: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))

Solution:
I used struct method #1 and it wasn't the issue. The issue was with how I decoded the data in URLSession. For some reason, this works:
URLSession.shared.dataTask(with: requestAPI) { [weak self] (data, response, error) in
if let data = data {
do {
let dataString = String(data: data, encoding: .utf8)
let jsondata = dataString?.data(using: .utf8)
let result = try JSONDecoder().decode(ClaimDetail.self, from: jsondata!)
print(result)
} catch let error {
print("Localized Error: \(error.localizedDescription)")
print("Error: \(error)")
}
}
}.resume()
Screenshot:
I don't really understand but I guess I had to convert the data into a string, then decode that instead?
Thank you all for helping out.

Related

swift decode json data

I have following json data returns form php.
{"Response":"OK","Data":[{"id":"1","organization_name":"Organization","description":"Description","address":"Address1, Ny, USA"}]}
In need to decode it using swift
Below is my code.
struct OrgData: Decodable {
let data: [Data]
enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
struct Data: Decodable {
let id: String
let address: String
let description: String
let organization_name: String
}
and I am decoding it using
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
guard let dataObj = try? JSONDecoder().decode(OrgData.self, from: data) else {
print("Error: Couldn't decode data ")
return
}
..................
But no data I am getting in dataObj.
I am referring this article
https://roadfiresoftware.com/2018/02/how-to-parse-json-with-swift-4/
Create a function in a helper class
func decodedObject<T: Decodable>(_ type: T.Type, dictionaryData: JSONDictionary) throws -> T? {
guard let jsonData = try? JSONSerialization.data(withJSONObject: dictionaryData,
options: JSONSerialization.WritingOptions.prettyPrinted) else {
return nil
}
let decodedData = try self.decode(type, from: jsonData)
return decodedData
}
Now call it like follows:
guard let dataObj = try? JSONDecoder().decodedObject(OrgData.self, dictionaryData: data) else {
print("Error: Couldn't decode data ")
return
}
There were some issues with how your structs were configured:
There is no field for the "Response" value
Data is an array, not a String
The following appears to properly output the json you give:
import Foundation
let json =
"""
{"Response":"OK","Data":[{"id":"1","organization_name":"Organization","description":"Description","address":"Address1, Ny, USA"}]}
"""
// MARK: - OrgData
struct OrgData: Codable {
let response: String
let data: [Datum]
enum CodingKeys: String, CodingKey {
case response = "Response"
case data = "Data"
}
}
// MARK: - Datum
struct Datum: Codable {
let id, organizationName, datumDescription, address: String
enum CodingKeys: String, CodingKey {
case id
case organizationName = "organization_name"
case datumDescription = "description"
case address
}
}
guard let data = json.data(using: .utf8) else {
return
}
do {
let dataObj = try JSONDecoder().decode(OrgData.self,
from: data)
print(dataObj)
// Optional(__lldb_expr_1.OrgData(response: "OK", data: [__lldb_expr_1.Datum(id: "1", organizationName: "Organization", datumDescription: "Description", address: "Address1, Ny, USA")]))
} catch {
print(error)
}

The data couldn’t be read because it isn’t in the correct format. Swift 5

I am trying to decode data from https://swapi.dev/. I get json correctly with response code 200, but the decoder could not read the data because the format is incorrect. I tried it in a lot of different ways. I am trying to get info on peoples.
Here is my code:
Model File
struct people: Codable {
let count: Int
let next: String?
let previous: String?
let results: [result]
}
struct result: Codable{
let name: String
let height: Int
let mass: Int
let hair_color: String
let skin_color: String
let eye_color: String
let birth_year: String
let gender: String
let homeworld: String
let films: [String]
let species: [String]
let vehicles: [String]
let starships: [String]
let created: String
let edited: String
let url: String
}
struct APIError: Codable {
let detail: String
}
Network Services
typealias OnApiSucces = (people) -> Void
typealias OnApiError = (String) -> Void
struct ApiService {
static let shared = ApiService()
let URL_BASE = "https://swapi.dev/api"
let URL_PEOPLE = "/people"
let session = URLSession(configuration: .default)
func getResults(onSuccess: #escaping OnApiSucces, onError: #escaping OnApiError) {
let url = URL(string: "\(URL_BASE)\(URL_PEOPLE)")!
var request = URLRequest(url: url)
request.httpMethod = "GET" // GET, PUT, POST, DELETE for some different api
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request) { (data, response, error) in
if let error = error {
onError(error.localizedDescription)
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
onError("Invalid data or response")
return
}
do{
if response.statusCode == 200 {
print("Code is \(response.statusCode)")
let results = try JSONDecoder().decode(people.self, from: data)
onSuccess(results)
} else {
let err = try JSONDecoder().decode(APIError.self, from: data)
print("Code is \(response.statusCode)")
onError(err.detail)
}
}
catch {
onError(error.localizedDescription)
}
}
task.resume()
}
}
** Getting data on ViewController**
func getResults() {
ApiService.shared.getResults { (people) in
self.results = people.results
} onError: { (error) in
debugPrint(error.description)
}
}
First, your data can't be read because height and mass are represented as String in the Star Wars API, while you represent them as Int in your Codable struct.
Also, try adding CodingKeys to your Codable struct so your struct adheres to naming conventions (specifically regarding your attribute_color variants) e.g.
struct result: Codable{
let name: String
let height: String
let mass: String
let hairColor: String // changed from hair_color
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
let homeworld: String
let films: [String]
let species: [String]
let vehicles: [String]
let starships: [String]
let created: String
let edited: String
let url: String
enums CodingKeys: String, CodingKey {
case name = "name"
case height = "height"
case mass = "mass"
case hairColor = "hair_color"
case skinColor = "skin_color"
case eyeColor = "eye_color"
case birthYear = "birth_year"
case gender = "gender"
case homeworld = "homeworld"
case films = "films"
case species = "species"
case vehicles = "vehicles"
case starships = "starships"
case created = "created"
case edited = "edited"
case url = "url"
}
}

Making an api request using URLSession.shared.dataTask

I'm making an api request:
var urlRaw = bookSummaryReadsDomainUrl + apiKey;
let url = URL(string: urlRaw.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
let task = URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching book summary reads: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let flurryItems = try? JSONDecoder().decode(FlurrySummary.self, from: data) {
completionHandler(flurryItems.rows ?? [])
}
})
task.resume()
to an endpoint that returns the following data
{
"rows": [
{
"dateTime": "2020-07-04 00:00:00.000-07:00",
"event|name": "BookSummaryRead",
"paramName|name": "bookId",
"paramValue|name": "elon-musk",
"count": 12
},
...
]
import Foundation
struct FlurrySummary: Codable {
var rows: [FlurryItem]?
enum CodingKeys: String, CodingKey {
case rows = "rows"
}
}
struct FlurryItem: Codable {
var name: String?
var event: String?
var value: String?
var count: String?
var date: String?
enum CodingKeys: String, CodingKey {
case name = "paramName|name"
case event = "event|name"
case value = "paramValue|name"
case count = "count"
case date = "dateTime"
}
}
For some reason the JSONDecoder.decode part is not working. It's not filling up the flurryItems and flurryItems.rows = nil. What am I doing wrong?
The property count in FlurryItem has to be of type Int.
var count: Int?
You have to catch the Error that are thrown.
do {
if let data = data,
let flurryItems = try JSONDecoder().decode(FlurrySummary.self, from: data) {
completionHandler(flurryItems.rows ?? [])
}
} catch { print(error) }
Also, you don't need the CodingKeys in FlurrySummary since the property name is the same.
struct FlurrySummary: Codable {
var rows: [FlurryItem]?
}
Note: Also, avoid using optional declaration if the property never becomes null.

Parsing json data using Codable

I am new to using Codable for parsing data from JSON and I am having trouble with the format of my JSON. I am not able to parse the correct fields into my Employee object. This is my first time using codable and dealing with a complex URL. This is how my JSON url is structured: https://ibb.co/WgDNMNT
{
"students": [
{
"uuid": "0djkdjjf734783749c",
"full_name": "Joe Morris",
"phone_number": "44445399",
"email_address": "jm99#jfgj.com",
"biography": "student of arts"
},
{
"uuid": "0djkdjjf734783749c",
"full_name": "Joe Morris",
"phone_number": "44445399",
"email_address": "jm99#jfgj.com",
"biography": "student of arts"
}
]
}
Here is my code:
struct Students: Codable {
var uuid: String?
var fullName: String?
var phoneNumber: String?
var emailAddress: String?
var biography: String?
}
//Custom Keys
enum CodingKeys: String, CodingKey{
case uuid
case fullname = "full_name"
case phoneNumber = "phone_number"
case emailAddress = "email_address"
case biography = "biography"
}
func parseData(){
guard let url = URL(string: "xxxxxxxxxx") else {return}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data,
error == nil else {
print(error?.localizedDescription ?? "Error")
return }
do{
let decoder = JSONDecoder()
let model = try decoder.decode([Students].self, from: dataResponse)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
}
Replace
let model = try decoder.decode([Students].self, from: dataResponse)
With
let model = try decoder.decode([String:[Students]].self, from: dataResponse)
print(model["students"])

how yo access item key in swift 4

if let url = URL(string: "https://mysit.com") {
URLSession.shared.dataTask(with: url) {
data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let data = data, error == nil,
let valueEncoding = response?.textEncodingName,
let getContent = String(data: data, encoding: valueEncoding.textEncodingToStringEncoding)
else { return }
print(getContent)
}.resume()
}
my Data
{"Regions":null,"Cities":[{"Id":"9605","Name":"YANBAA AS SENAYAH"},{"Id":"15","Name":"ABHA"},{"Id":"13","Name":"AD DAMMAM"},{"Id":"1542","Name":"AL BAHA"},{"Id":"14","Name":"AL MADINAH AL MUNAWWARAH"},{"Id":"2213","Name":"AR'AR"},{"Id":"11","Name":"BURAYDAH"},{"Id":"10","Name":"HAIL"},{"Id":"17","Name":"JAZAN"},{"Id":"6","Name":"MAKKAH AL MUKARRAMAH"},{"Id":"3417","Name":"NAJRAN"},{"Id":"3","Name":"RIYADH"},{"Id":"2237","Name":"SAKAKA"},{"Id":"1","Name":"TABUK"},
how to get an array list of values "Name" ,can you help me?
You can try
struct Root :Decodable{
let Cities:[InnerItem]
}
struct InnerItem :Decodable{
let Id:String
let Name:String
}
do {
let arr = try JSONDecoder().decode(Root.self, from: data)
print(arr.Cities)
}
catch {
print(error)
}
//
Note : This is the correct json structure
{"Regions":null,"Cities":[{"Id":"9605","Name":"YANBAA AS SENAYAH"},{"Id":"15","Name":"ABHA"},{"Id":"13","Name":"AD DAMMAM"},{"Id":"1542","Name":"AL BAHA"},{"Id":"14","Name":"AL MADINAH AL MUNAWWARAH"},{"Id":"2213","Name":"AR'AR"},{"Id":"11","Name":"BURAYDAH"},{"Id":"10","Name":"HAIL"},{"Id":"17","Name":"JAZAN"},{"Id":"6","Name":"MAKKAH AL MUKARRAMAH"},{"Id":"3417","Name":"NAJRAN"},{"Id":"3","Name":"RIYADH"},{"Id":"2237","Name":"SAKAKA"},{"Id":"1","Name":"TABUK"}]}
let responseData = try JSONSerialization.jsonObject(with: (response["Cities"] as! String).data(using: String.Encoding.utf8)!, options: []) as! [[String: Any]]
for item in responseData{
let name = item["Name"] as! String
}
Together with the decoding step. I added several guards to print an error if one comes up. It is generally good practice to throw the error and handle it on the appropriate level.
func work() {
guard let url = URL(string: "https://mysit.com") else {
fatalError("url is nil.")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard error == nil else {
fatalError("\(error!)")
}
guard let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
fatalError("Response is nil.")
}
guard let data = data else {
fatalError("data is nil.")
}
decode(data: data)
}.resume()
}
func decode(data: Data) {
let decoder = JSONDecoder.init()
let welcome = try! decoder.decode(Welcome.self, from: data)
print(welcome.cities.first!)
}
The decoding helpers. enum CodingKeys are used to convert the lowercase attributes to the uppercase JSON attributes and back.
struct Welcome: Codable {
var regions: [Region]?
let cities: [City]
enum CodingKeys: String, CodingKey {
case regions = "Regions"
case cities = "Cities"
}
}
struct City: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
struct Region: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
Some use services like Quicktype to convert JSON strings to the specific programming language. It makes things faster and simpler.

Resources