Problem converting JSON to Array with Swift - arrays

I try to convert a JSON to an Array but I face some issue and do not know how to sort it out.
I use Swift 5 and Xcode 12.2.
Here is the JSON from my PHP query:
[
{
Crewcode = AAA;
Phone = 5553216789;
Firstname = Philip;
Lastname = MILLER;
email = "pmiller#xxx.com";
},
{
Crewcode = BBB;
Phone = 5557861243;
Firstname = Andrew;
Lastname = DEAN;
email = "adean#xxx.com";
}
]
And here is my Swift code :
let url: URL = URL(string: "https://xxx.php")!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}
else {
print("Data downloaded")
do {
if let jsondata = (try? JSONSerialization.jsonObject(with: data!, options: [])) {
print(jsondata)
struct Crew: Decodable {
var Code: String
var Phone: String
var Firstname: String
var Lastname: String
var email: String
}
let decoder = JSONDecoder()
do {
let people = try decoder.decode([Crew].self, from: jsondata as! Data)
print(people)
}
catch {
print(error)
}
}
}
}
}
task.resume()
When I run my code I get the following error:
Could not cast value of type '__NSArrayI' (0x7fff86b930b0) to 'NSData' (0x7fff86b911e8).
2020-12-09 14:52:48.988468+0100 FTL[57659:3019805] Could not cast value of type '__NSArrayI' (0x7fff86b930b0) to 'NSData' (0x7fff86b911e8).
Could not cast value of type '__NSArrayI' (0x7fff86b930b0) to 'NSData' (0x7fff86b911e8).
Should you have any idea to get it right, I thank you in advance for your assistance on this !

You are deserializing the JSON twice by mixing up JSONSerialization and JSONDecoder.
Delete the first one
if let jsondata = (try? JSONSerialization.jsonObject(with: data!, options: [])) {
– By the way the JSON in the question is neither fish nor fowl, neither JSON nor an NS.. collection type dump –
and replace
let people = try decoder.decode([Crew].self, from: jsondata as! Data)
with
let people = try decoder.decode([Crew].self, from: data!)
and the struct member names must match the keys otherwise you have to add CodingKeys

Related

How to correctly parse JSON with root element as an array in Swift?

I've the following issue, where I don't get the structure right to decode a response from node-red for the status of my sonos players:
[[{"urlHostname":"192.168.1.1","urlPort":1400,"baseUrl":"http://192.168.1.1:1400","sonosName":"Sonos1","uuid":"RINCON_SONOS14001400","invisible":false},{"urlHostname":"192.168.1.2","urlPort":"1400","baseUrl":"http://192.168.1.2:1400","sonosName":"Sonos2","uuid":"RINCON_SONOS21400","invisible":false},{"urlHostname":"192.168.1.3","urlPort":"1400","baseUrl":"http://192.168.1.3:1400","sonosName":"Sonos3","uuid":"RINCON_SONOS31400","invisible":false},{"urlHostname":"192.168.1.4","urlPort":"1400","baseUrl":"http://192.168.1.4:1400","sonosName":"Sonos4","uuid":"RINCON_SONOS41400","invisible":false},{"urlHostname":"192.168.1.5","urlPort":"1400","baseUrl":"http://192.168.1.5:1400","sonosName":"Sonos5","uuid":"RINCON_SONOS51400","invisible":false},{"urlHostname":"192.168.1.6","urlPort":"1400","baseUrl":"http://192.168.1.6:1400","sonosName":"Sonos6","uuid":"RINCON_SONOS61400","invisible":false}]]
My structure & the call to decode looks as follows:
typealias Response = [sonosData]
struct sonosData: Codable {
let sonos: [sonosStatusEntry]?
}
struct sonosStatusEntry: Codable {
let status: [sonosStatus]?
}
struct sonosStatus: Codable {
let urlHostname: String
let urlPort: Int
let baseUrl: String
let sonosName: String
let uuid: String
let invisible: Bool
}
let response = try JSONDecoder().decode(Response.self, from: data)
I get the following error in Swift:
Failed to load: The data couldn’t be read because it isn’t in the correct format.
Any suggestions?
You have 2 errors with your code:
The data you are tring to decode is [[sonosStatus]]. so you need to decode as:
let response = try JSONDecoder().decode([[sonosStatus]].self, from: data)
urlPort represents both String and Int. So you need to either correct your JSON format or support both formats with a custom decoder.
You can achieve that with this simple Property wrapper:
#propertyWrapper
struct SomeKindOfInt: Codable {
var wrappedValue: Int?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringifiedValue = try? container.decode(String.self) {
wrappedValue = Int(stringifiedValue)
} else {
wrappedValue = try container.decode(Int.self)
}
}
}
And then using it like:
struct sonosStatus: Codable {
let urlHostname: String
#SomeKindOfInt var urlPort: Int?
let baseUrl: String
let sonosName: String
let uuid: String
let invisible: Bool
}
One of the the problem is related to urlPort. Your json have both Int and String values for urlPort. You can define custom init to handle that.
struct sonosStatus: Codable {
let urlHostname: String
let urlPort: Int
let baseUrl: String
let sonosName: String
let uuid: String
let invisible: Bool
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
urlHostname = try values.decode(String.self, forKey: .urlHostname)
baseUrl = try values.decode(String.self, forKey: .baseUrl)
sonosName = try values.decode(String.self, forKey: .sonosName)
uuid = try values.decode(String.self, forKey: .uuid)
invisible = try values.decode(Bool.self, forKey: .invisible)
// you can define urlPort as optional or unwrap it.
if let intVal = try? values.decode(Int.self, forKey: .urlPort) {
urlPort = intVal
} else if let stringVal = try? values.decode(String.self, forKey: .urlPort) {
urlPort = Int(stringVal) ?? 0
} else {
urlPort = ""
}
}
}
And also your json is an array of sonosStatus array.
So you need yo modify Response typealias like below:
typealias Response = [[sonosStatus]]
You can use JSONSerialization with the .allowFragments reading option.
let objects = (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)) as? [[NSDictionary]]
And it was fixed on latest Swift version: https://bugs.swift.org/browse/SR-6163

parsing array of objects to array of string elements in swift JSON object

I am trying to parse the JSON object received from Web service which gives the result as JSON object of status and data.data again is a array of objects, from this object I want to take one element on the basis of which I have to fill a tableview.
web Service results comes as
{"status":1,"data":[{"service_id":"1","service_name":"Painter"},{"service_id":"2","service_name":"Plumber"},{"service_id":"3","service_name":"Electrician"},{"service_id":"4","service_name":"Handyman"},{"service_id":"5","service_name":"Carpenter"},{"service_id":"6","service_name":"Mason"}]}
parsing in swift I did as:--
created one class
class ABC: NSObject {
var service_name:String?
var service_id : Int?
init(service_name:String,service_id:Int) {
self.service_name = service_name
self.service_id = service_id
}
let myUrl = URL(string: "services.php");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json
{
let status=parseJSON["status"] as? Int
let newdata : NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
self.model=(newdata.value(forKey: "data") as? [ABC])!
My problem is I am getting an array of objects in self.model as service_name and service_id keys.Now I want to take out one array of strings that contains all the service_name object values.Its saying not able to convert NSArray to swift array.
As already mentioned by others use (De)codable. It's easy to use and very comfortable. No type cast and no literal keys.
Create two structs, declare the members as non-optional constants with camelCased names and omit the initializer.
struct Root : Decodable {
let status : Int
let data : [Service]
}
struct Service : Decodable {
let serviceName : String
let serviceId : String
}
Then decode the JSON in another class or struct
let myUrl = URL(string: "services.php")
var request = URLRequest(url: myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data!)
let status = json.status
let newdata = json.data
} catch { print(error))
}
task.resume()
I would recommend to drop JSONSerialization and use Codable protocol instead with CodingKeys.
Here is a simple example to see how it works.
struct Service : Codable {
let id : Int
let name : String
// keys
private enum CodingKeys: String, CodingKey {
case id = "service_id"
case name = "service_name"
}
}
...
// assuming our data comes from server side
let jsonString = "..."
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(Service.self, from: jsonData)
print("Getting service: \(service.id) \(service.name)")
} catch {
print("Unexpected error: \(error).")
}
More documentation here.
Use native Swift type Dictionary everywhere you use NSDictionary now
Then get certain value for key by specifing key in dictionary's subscript
if let model = newData["data"] as? [ABC] {
self.model = model
}
Anyway, I would suggest you to start using Codable instead of JSONSerialization
struct Response: Decodable {
let status: Int
let data: [ABC]
}
struct ABC: Decodable {
let serviceName: String
let serviceId: String // note that `serviceId` isn’t `Int` But `String`
}
and then decode your data using JSONDecoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(Response.self, from: data!)
self.model = response.data
} catch { print(error) }

Error on Serialization

I'm getting this error when I try to proceed on parsing the JSON. Does anyone know what I gotta do to fix it?
Error 1: Cannot subscript a value of type 'AudiobookJSON' (aka 'Array<Dictionary<String, Any>>') with an index of type 'String'
Error on Print
File Model: Audiobook.swift:
import Foundation
struct Audiobook : Codable {
let id: Int
let descricao: String
let urlImagem: String
init(dictionary: AudiobookJSON) {
self.id = dictionary["id"] as! Int//////ERROR MESSAGE ////////
self.descricao = dictionary["descricao"] as! String/////ERROR MESSAGE
self.urlImagem = dictionary["urlImagem"] as! String////ERROR MESSAGE
}
}
Folder Networking: APIClient.swift:
import Foundation
typealias AudiobookJSON = [[String: Any]]
struct APIClient {
static func getAudiobooksAPI(completion: #escaping ([Audiobook]?) -> Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/categoria")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error unwrapping URL"); return }
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
//let json = try JSONSerialization.jsonObject(with: unwrappedDAta, options: .allowFragments) as! [String:Any]
//let posts = json["data"] as? AudiobookJSON
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
}
As I can see that AudiobookJSON is an array of key-value pairs that's why you are getting error: So you have to use codable like that:
First: you have to make Codable type struct like that(your codable struct variable names should be same as you are getting in response):
struct Audiobook: Codable {
let id: Int?
let descricao: String?
let urlImagem: String?
}
Second: when you get the response then parse directly using codale like that:
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
You can directly use the response like:
for audio in posts {
print("audio.id")
print("audio.descricao")
print("audio.urlImagem")
}

Getting Data from JSON Swift

Can Anyone Help me with this
my data after parsing a JSON URL is
{
AREA = (
{
"area_name" = "Bhaktamadhu Nagar";
"city_id" = 4;
id = 31;
price = "100.00";
},
{
"area_name" = "Gandamunda";
"city_id" = 4;
id = 32;
price = "100.00";
}
);
}
and there is a lot more.
I want to fetch only area_name and price values in an array
my code is something like that
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSDictionary
print(parsedData)}
I am getting my Upper format in the parsedData
What is the exact code for getting my area_name and price which should store in two separate arrays as aname[] and price[]
Please don't mark it as a duplicate already searched a lot before posting this.
Your JSON data is converted into [String: AnyObject].
AREA data is [[String: AnyObject]] so create a [String: AnyObject] array. and getting a one by one value from array.
How to fetch JSON data from a url using URLSession?
try this code. it's helpfull
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let jsonData = data {
do {
let parsedData = try JSONSerialization.jsonObject(with: jsonData, options: .mutableLeaves) as! [String: AnyObject]
if let area = parsedData["AREA"] as? [[String: AnyObject]] {
for a in area {
areaNameArr.append(a["area_name"])
priceArr.append(a["price"])
print(a)
}
}
}
catch let error {
debugPrint(error)
}
}
else {
debugPrint(error as Any)
}
}.resume()
Use the SwiftyJSON Lib.
It’s easy and fast.
I am using it and it’s very helpful in this way:
let session = URLSession(configuration: URLSessionConfiguration.ephemeral)
self.Task = session.dataTask(with: RequestLink as URLRequest , completionHandler: { (data,response,error) in
if error != nil {
print(error as Any)
}
let ReadJson4Rest = JSON(data: data!)
if let Rest_Details = ReadJson4Rest["Result"].array{
for Details in Rest_Details {
let Comment = Details.dictionaryValue["Comment"]!
let UserName = Details.dictionaryValue["User_ID"]!
if Comment != nil {
let FirstChar = UserName.description.characters.first
self.GetUserImage(UserName: UserName.string! ,AlphabetCat: (FirstChar?.description)!)
DispatchQueue.main.async {
self.CommentValue.append(Comment.string!)
self.UserNames.append(UserName.string!)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "Load"), object: nil)
}
}
}
}
})
self.Task?.resume()
}

struggling with JSON parsing in swift

I am trying to load data in JSON format from my server into IOS application.
Here is my JSON:
[
{
"BankName": "bank1",
"CurrencyName": "cur1",
"SellRate": "0.65",
"BuyRate": "0.55",
"OfficialRate": "0.6"
},
{
"BankName": "bank1",
"CurrencyName": "cur2",
"SellRate": "1.65",
"BuyRate": "1.55",
"OfficialRate": "1.6"
}
]
There are 2 files in my project:
1:
import Foundation
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
2:
import Foundation
class JsonTest {
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://a.com/g.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
var shots = [Shot]()
println(jsonData)
for shot in jsonData{
let shot = Shot(data: shot as NSDictionary)
shots.append(shot)
}
println(shots) //[jsontest.Shot, jsontest.Shot]
}
}
task.resume()
}
}
I am trying to populate array automatically when my app starts. To do it I have a code in my mainViewController class.
override func viewDidLoad() {
super.viewDidLoad()
let api = JsonTest()
api.loadJson(nil)
}
The problem occurs when I try to print shots variable in the second file.
it returns [jsontest.Shot, jsontest.Shot] when I was expecting the array of dictionaries.
println(jsonData) works fine and shows JSON data from URL.
Can anybody advise what is wrong in my program?
"shots" is an array of instances of Shot, not a dictionary:
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
let sourceUrl = NSURL(string: urlString)
// Using NSData instead of NSURLSession for experimenting in Playground
let data = NSData(contentsOfURL: sourceUrl!)
var error: NSError?
// As I'm using Swift 1.2 I had to change "as" with "as!"
let jsonData = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers, error: &error) as! NSArray
for shot in jsonData{
let shot = Shot(data: shot as! NSDictionary)
shots.append(shot)
}
println(shots[0].bankName)
Update for Swift 2
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
// Using NSData instead of NSURLSession for experimenting in Playground
if let sourceUrl = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(sourceUrl, completionHandler: { (data, response, error) in
if error == nil {
if let data = data, jsonData = try? NSJSONSerialization.JSONObjectWithData(data, options: []), jsonArray = jsonData as? [NSDictionary] {
for item in jsonArray {
let shot = Shot(data: item)
shots.append(shot)
}
print(shots[0].bankName)
} else {
print("no JSON data")
}
} else {
print(error!.localizedDescription)
}
}).resume()
}
Seems like there are two problems:
You're trying to use println to debug instead of setting a breakpoint and checking your objects values.
You have not created a description or debugDescription property for your object, so println on your object is just using some default implementation.
shots is an array of your custom object, so when you call println, it's using the description for Array, which prints out the objects in the array, comma separated, and within square brackets.
The default description property for classes in Swift just prints the class name.
Ideally, you should just use a break point to check the values of your object to be certain it initialized correctly, but if it's actually important to get them to print right, it's only a matter of implementing the description property:
override var description: String {
get {
// build and return some string that represents your Shot object
}
}

Resources