Try to parsing json into class object fail - json

I need help to parsing the json using Swift . I could not parse them into an array of a class object using swift. What I tried is I bundle the json key value into an array of object and return. However, I could not deal with the types. It so confuse.
Could someone help me.
The json I have:
{
"mass_info1":
[{
"mass_id":7780,
"mass_date":"5/1/2019",
"mass_time":"7:30 PM",
"mass_location":"",
"mass_description": “Blah1”
} ] ,
"mass_info2":
[ {
"mass_id":7781,
"mass_date":"6/10/2019",
"mass_time":"7:30 PM",
"mass_location”:”1234 some place Los Angeles”,
"mass_description": “blah2”
} ] ,
"mass_info3":
[ {
"mass_id":7783,
"mass_date":"5/21/2019",
"mass_time":"7:30 PM",
"mass_location":"",
"mass_description": “blah3”
} ] ,
"mass_info4":
[ {
"mass_id":1115,
"mass_date":"4/5/2019",
"mass_time":"2:30 PM",
"mass_location":"4050 Mission Ave, Oceanside, CA 92057",
"mass_description": “blah4”
} ]
}
The class MassInfoObject import Foundation
struct MassInfoObject {
var mass_id:Int
var mass_date: String?
var mass_time:String?
var mass_location:String?
var mass_description:String?
init(mass_id:Int,mass_date:String,
mass_time:String,mass_location:String,mass_description:String)
{
self.mass_id=mass_id
self.mass_date=mass_date
self.mass_time=mass_time
self.mass_location=mass_location
self.mass_description=mass_description
}
}
and the code that I get the json from a URLSession.
func getjson()->[MassInfoObject]? {
let urlPath = "http://myclassinfo.com/dev/json.php"
let url = URL(string: urlPath)
var massObjArray = [MassInfoObject]()
var index:Int = 0
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
print("Task completed")
guard data != nil && error == nil else {
//print(error?.localizedDescription)
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
for (key, value) in jsonResult {
//print("Value: \(value) for key: \(key)")
if let results = jsonResult[key] as? [String]
{
let dict = results[index].toJSON() as? [String:AnyObject] // can be any type here
for (key,value) in dict!{
let result2 = dict![key] as! String
print (result2)
}
}
}
}
} catch let parseError as NSError {
print("JSON Error \(parseError.localizedDescription)")
}
}
task.resume()
return massObjArray
}

The json you are trying to parse does not have a very logical structure. If you have control over the json, you should work on that first. If you don't have control I suggest converting the json on your client end before parsing.
The following works in a Playground:
let str = """
{ "mass_info1": [{ "mass_id":7780, "mass_date":"5/1/2019", "mass_time":"7:30 PM", "mass_location":"", "mass_description": "Blah1"
} ] ,
"mass_info2": [ { "mass_id":7781, "mass_date":"6/10/2019", "mass_time":"7:30 PM", "mass_location":"1234 some place Los Angeles", "mass_description": "Blah2"
} ] ,
"mass_info3":
[ { "mass_id":7783, "mass_date":"5/21/2019", "mass_time":"7:30 PM", "mass_location":"", "mass_description": "blah3"
} ] ,
"mass_info4":
[ { "mass_id":1115, "mass_date":"4/5/2019", "mass_time":"2:30 PM", "mass_location":"4050 Mission Ave, Oceanside, CA 92057", "mass_description": "blah4"
} ]
}
"""
struct MassInfoObject: Codable {
var mass_id: Int
var mass_date: String?
var mass_time: String?
var mass_location: String?
var mass_description: String?
}
let data = str.data(using: .utf8)!
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: [[String: Any]]]
let jsonArray = json!.values.map { $0 }
let cleanedJson = try JSONSerialization.data(withJSONObject: jsonArray, options: [])
let decoder = JSONDecoder()
let items = try decoder.decode([[MassInfoObject]].self, from: cleanedJson)
The first steps are to convert the json to something that can be parsed easily. Because now MassInfoObject conforms to Codable, you can use a simple JSONDecoder to decode an array of objects, this is just one line of code.
Also, in your networking code you are calling an async task, but your are returning immediately, that's not going to work. When you return from the getJson function, you don't have the results yet. The easiest thing to do is use a closure that you can call with the results:
In your networking code it would look as such:
func getjson(handler: #escaping ([MassInfoObject]?)->()) {
let urlPath = "http://myclassinfo.com/dev/json.php"
let url = URL(string: urlPath)
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
print("Task completed")
guard let data = data && error == nil else {
//print(error?.localizedDescription)
return
}
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: [[String: Any]]]
let jsonArray = json!.values.map { $0 }
let cleanedJson = try JSONSerialization.data(withJSONObject: jsonArray, options: [])
let decoder = JSONDecoder()
let items = try decoder.decode([[MassInfoObject]].self, from: cleanedJson).compactMap { $0 }
handler(items)
} catch let parseError as NSError {
print("JSON Error \(parseError.localizedDescription)")
handler(nil)
}
}
task.resume()
}
If you have json that looks like this:
[
{
"mass_description": "Blah1",
"mass_date": "5/1/2019",
"mass_location": "",
"mass_time": "7:30 PM",
"mass_id": 7780
},
{
"mass_description": "blah2",
"mass_date": "6/10/2019",
"mass_location": "1234 some place Los Angeles",
"mass_time": "7:30 PM",
"mass_id": 7781
},
{
"mass_description": "blah3",
"mass_date": "5/21/2019",
"mass_location": "",
"mass_time": "7:30 PM",
"mass_id": 7783
},
{
"mass_description": "blah4",
"mass_date": "4/5/2019",
"mass_location": "4050 Mission Ave, Oceanside, CA 92057",
"mass_time": "2:30 PM",
"mass_id": 1115
}
]
You could easily decode this:
let items = try JSONDecoder().decode([MassInfoObject].self, data)

Thank Marcel, and everyone who helped me. Here is the code that I used
struct MassObj:Decodable{
var mass_id:Int
var mass_date: String
var mass_time:String
var mass_location:String
var mass_description:String
}
func getJsonArray2() {
let urlPath = "http://myclassinfo.com/dev/json.php"
guard let url = URL(string: urlPath) else {return }
let task = URLSession.shared.dataTask(with: url)
{
data, response, error in
print("Task completed")
guard data != nil && error == nil else {
//print(error?.localizedDescription)
return
}
do{
var massArray = [MassObj]()
massArray = try JSONDecoder().decode([MassObj].self, from:data!)
for object in massArray {
print("\(object.mass_id) == \(object.mass_date) == \(object.mass_description)")
}
}catch let jerr {
print(jerr)
}
}
task.resume()
}
================
and the json is
[
{
"mass_description": "Blah1",
"mass_date": "5/1/2019",
"mass_location": "",
"mass_time": "7:30 PM",
"mass_id": 7780
},
{
"mass_description": "blah2",
"mass_date": "6/10/2019",
"mass_location": "1234 some place Los Angeles",
"mass_time": "7:30 PM",
"mass_id": 7781
},
{
"mass_description": "blah3",
"mass_date": "5/21/2019",
"mass_location": "",
"mass_time": "7:30 PM",
"mass_id": 7783
},
{
"mass_description": "blah4",
"mass_date": "4/5/2019",
"mass_location": "4050 Mission Ave, Oceanside, CA 92057",
"mass_time": "2:30 PM",
"mass_id": 1115
}
]

Related

Decode JSON Array with no Attribute Name

I have looked through other threads regarding trying to parse JSON data where a JSON array has no name. From what I have found you need to use a unkeyedContainer but I'm not entirely sure from the examples I have seen how this works with the data model.
Below is a snippet of data from open charge api:
[
{
"IsRecentlyVerified": false,
"ID": 136888,
"UUID": "254B0B07-E7FC-4B4B-A37C-899BCB9D7261",
"DataProviderID": 18,
"DataProvidersReference": "0a9fdbb17feb6ccb7ec405cfb85222c4",
"OperatorID": 3,
"UsageTypeID": 1,
"AddressInfo": {
"ID": 137234,
"Title": "Ballee Road Park & Share",
"AddressLine1": "Ballee Road",
"Town": "Ballymena",
"Postcode": "BT42 2HD",
"CountryID": 1,
"Latitude": 54.844648,
"Longitude": -6.273606,
"AccessComments": "Ballee Road Park and Share, Ballymena",
"RelatedURL": "http://pod-point.com",
"Distance": 3.81818421833416,
"DistanceUnit": 2
},
"Connections": [
{
"ID": 191571,
"ConnectionTypeID": 25,
"Reference": "1",
"StatusTypeID": 50,
"LevelID": 2,
"Amps": 32,
"Voltage": 400,
"PowerKW": 22,
"CurrentTypeID": 20
},
It looks to me that the first [ and { have no attribute names which I belive is creating the error in xcode: "Error!: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))"
Here is my data model:
import Foundation
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
Here is my code:
//Find public chargers from local coordinates
func findPublicChargers(lat: Double, long: Double) {
//Use apiurl to pull all charge points that are currently in that area by adding lat and long into the api call &latitude=***&longitude=*****
let apiurl = "https://api.openchargemap.io/v3/poi/?output=json&countrycode=UK&maxresults=100&compact=true&verbose=false"
let urlString = "\(apiurl)&latitude=\(lat)&longitude=\(long)"
//print(urlString)
performRequest(urlString: urlString)
}
//Perform API Request - (London App Brewry code)
//Create the custom url
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
//print("Called")
//Create a URL Session
let session = URLSession(configuration: .default)
//Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
//print(dataString)
self.parseJSON(data: safeData)
print("Data: \(safeData)")
}
}
//Start the task
task.resume()
}
}
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(PublicCharger.self, from: data)
print("Data: \(decodedData.AddressInfo[0].Title)")
} catch {
print("Error!: \(error)")
}
}
struct AddressInfo: Decodable {
let Title: String
}
I have seen that in the data model you would need to include an unkeyed container element. I'm just not sure how this should be carried out in the data model. Any light on this would be much appreciated.
Try to change your PublicCharger data model to
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
And your parseJSON function to
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([PublicCharger].self, from: data)
if !decodedData.isEmpty {
print("Data: \(decodedData[0].AddressInfo[0].Title)")
} else {
print("Empty result!")
}
} catch {
print("Error!: \(error)")
}
}

Trying to parse json for public git repos in swift but receiving "Expected to decode Dictionary<String, Any> but found an array instead."

My json looks like this:
[
{
"name": "sensei",
"owner": {
"login": "linkedin",
},
"description": "distributed realtime searchable database",
"fork": false,
},
{
"name": "linkedin-utils",
"owner": {
"login": "linkedin",
},
"description": "Base utilities shared by all linkedin open source projects",
"fork": false,
}
]
The structs I built are the following:
struct LinkedinData: Codable {
var name: String
var description: String
var owner: OwnerLogin
var fork: Bool
}
struct OwnerLogin: Codable {
var login: String
}
My code for parsing is this one:
import UIKit
class ViewController: UIViewController {
var linkedinData = [LinkedinData]()
override func viewDidLoad() {
super.viewDidLoad()
let urString : String = "https://api.github.com/orgs/linkedin/repos"
if let url = URL(string: urString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return //exit out of function
}
parseJSON(json: data!)
}
task.resume()
}
func parseJSON(json: Data) {
let decoder = JSONDecoder()
if let decodedData = try? decoder.decode(LinkedinData.self, from: json) {
linkedinData = [decodedData]
}
}
}
I tried for hours bút it seems impossible to parse the json and retreive the data I am looking for (name, description, owner.login and fork) in a collection type. Could you please help?
You should decode an array of LinkedinData, instead of just one, because your JSON has an array as its root:
[ <------- this "[" indicates an array
{
"name": "sensei",
"owner": {
"login": "linkedin",
},
Therefore, you should write:
if let decodedData = try? decoder.decode([LinkedinData].self, from: json) {
linkedinData = decodedData
}
if let decodedData = try? decoder.decode(LinkedinData.self, from: json) {
linkedinData = [decodedData]
}
replace this with
if let decodedData = try? decoder.decode([LinkedinData].self, from: json) {
linkedinData = decodedData
}
as your topmost object in JSON is an Array.

How to get data from JSON with different keys?

The JSON looks like this:
{
"00AK": {
"icao": "00AK",
"iata": "",
"name": "Lowell Field",
"city": "Anchor Point",
"country": "US",
"elevation": 450,
"lat": 59.94919968,
"lon": -151.695999146,
"tz": "America\/Anchorage"
},
"00AL": {
"icao": "00AL",
"iata": "",
"name": "Epps Airpark",
"city": "Harvest",
"country": "US",
"elevation": 820,
"lat": 34.8647994995,
"lon": -86.7703018188,
"tz": "America\/Chicago"
},
"00AZ": {
"icao": "00AZ",
"iata": "",
"name": "Cordes Airport",
"city": "Cordes",
"country": "US",
"elevation": 3810,
"lat": 34.3055992126,
"lon": -112.1650009155,
"tz": "America\/Phoenix"
}
}
As you can see the keys varies "00AK", "00AL", "00AZ", and so on. How do I parse this format of JSON?
let jsonData = //JSON DATA HERE
do {
let dict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: []) as! NSDictionary
for (key, value) in dict {
let subDict = value as! NSDictionary
//Then you can access the values from subDict
} catch {
//ERROR HANDLING
}
So here it is I declare one structure as following
struct Model {
var iaco: String?
var iata: String?
var name: String?
var city: String?
var country: String?
var elevation: Int?
var lat: Double?
var lon: Double?
var tz: String? }
Then declare on array to hold the response result
var listOfModels = Array<Model>()
Then take a list of keys from response Dictionary and iterate over it to get result and store it in array
handleResponse { (response) in
for key in response.keys {
let dict = response[key] as? [String:Any]
var model = Model()
model.iaco = dict?["icao"] as? String
model.iata = dict?["iata"] as? String
model.name = dict?["name"] as? String
model.city = dict?["city"] as? String
model.country = dict?["country"] as? String
model.elevation = dict?["elevation"] as? Int
model.lat = dict?["lat"] as? Double
model.lon = dict?["lon"] as? Double
model.tz = dict?["tz"] as? String
listOfModels.append(model)
}
}
response.keys is used to get list of keys from dictionary.
You could try the below snippet:
func parseData() {
let jsonData = Data() /// your actual response data goes here...
do {
let dict = try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
guard let swiftDict = dict as? [String : Any] else {
print("Not a valid response")
return
}
for (key, value) in swiftDict {
guard let valueDict = value as? [String: Any] else {
/// handle improper response here
return
}
/// Got the actual dictionary in 'valueDict'...
}
}
catch {
/// handle parsing error here
}
}

Swift is not assigning variables from JSON results

I have an issue with loading JSON results within swift (php connection).
I can retrieve JSON data but it will not let me assign it to a variable.
it always assigns the results as Optional.
The JSON Data:
{
"country": [{
"id": 1,
"name": "Australia",
"code": 61
}, {
"id": 2,
"name": "New Zealand",
"code": 64
}]
}
The xCode Output:
["country": <__NSArrayI 0x60000002da20>(
{
code = 61;
id = 1;
name = Australia;
},
{
code = 64;
id = 2;
name = "New Zealand";
}
)
]
Country Name: Optional(Australia)
Country Name: Optional(New Zealand)
The .swift file:
//function did_load
override func viewDidLoad() {
super.viewDidLoad()
//created RequestURL
let requestURL = URL(string: get_codes)
//creating NSMutable
let request = NSMutableURLRequest(url: requestURL!)
//setting the method to GET
request.httpMethod = "GET"
//create a task to get results
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if error != nil{
print("error is \(String(describing: error))")
return;
}
//lets parse the response
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: Any]
print(json)
if let countries = json["country"] as? [[String: AnyObject]] {
for country in countries {
print("Country Name: \(String(describing: country["name"]))")
print("Country Code: \(String(describing: country["code"]))")
if let couname = country["name"] as? [AnyObject] {
print(couname)
}
if let coucode = country["code"] as? [AnyObject] {
print(coucode)
}
}
}
} catch {
print("Error Serializing JSON: \(error)")
}
}
//executing the task
task.resume()
}
You need to unwrap the optional before you try to use it via string interpolation. The safest way to do that is via optional binding:
Please use below code, which will work for you.
if let countries = json["country"] as? [[String: AnyObject]] {
for country in countries {
print("Country Name: \(country["name"] as! String)")
print("Country Code: \(country["code"] as! String)")
if let couname = country["name"] as? String {
print(couname)
}
if let coucode = country["code"] as? Int {
print(coucode)
}
}
}

How to create a JSON from Dictionary with an array

I have an object "itensList", it has the fields "name", "createdAt" and an array of "itens".
I want to be able to build JSON that looks like this:
{
"name": "List name"
"CreatedAt": "12:12 12/12/2016"
"itens": [
{
"title": "Item title"
"CreatedAt": "12:13 12/12/2016"
"isDone": false
},
{
"title": "Another item title"
"CreatedAt": "12:14 12/12/2016"
"isDone": true
}
]
}
I have tried a few different approaches with no success.
Item Object
class Item: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
dynamic var isDone = false
}
Item List Object
class ItemList: Object {
dynamic var name = ""
dynamic var createdAt = NSDate()
let itens = List<Item>()
}
For the example, let's make an object similar to what you must have:
class Iten {
let title:String
let createdAt:String
let isDone:Bool
init(title: String, createdAt: String, isDone: Bool) {
self.title = title
self.createdAt = createdAt
self.isDone = isDone
}
}
The trick I suggest is to add a computed value that will return a dictionary:
class Iten {
let title:String
let createdAt:String
let isDone:Bool
init(title: String, createdAt: String, isDone: Bool) {
self.title = title
self.createdAt = createdAt
self.isDone = isDone
}
var toDictionary: [String:AnyObject] {
return ["title": title, "createdAt": createdAt, "isDone": isDone]
}
}
Let's use it:
let iten1Dict = Iten(title: "title1", createdAt: "date1", isDone: false).toDictionary
let iten2Dict = Iten(title: "title2", createdAt: "date2", isDone: true).toDictionary
We now make the encapsulating dictionary:
let dict: [String:AnyObject] = ["name": "List name", "createdAt": "dateX", "itens": [iten1Dict, iten2Dict]]
To finish, we encode this dictionary to JSON data then we decode it as a String:
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(dict, options: .PrettyPrinted)
if let jsonString = String(data: jsonData, encoding: NSUTF8StringEncoding) {
print(jsonString)
}
} catch let error as NSError {
print(error)
}
And voilà:
{
"createdAt" : "dateX",
"itens" : [
{
"title" : "title1",
"createdAt" : "date1",
"isDone" : false
},
{
"title" : "title2",
"createdAt" : "date2",
"isDone" : true
}
],
"name" : "List name"
}
Raphael,
This piece of code builds a JSON query. It should get you started, just keep hacking and you'll find a way! That's the fun of programming!
func JSONquery()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.dropboxapi.com/2/files/get_metadata")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.addValue("application/json",forHTTPHeaderField: "Content-Type")
request.addValue("path", forHTTPHeaderField: lePath)
let cursor:NSDictionary? = ["path":lePath]
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(cursor!, options: [])
request.HTTPBody = jsonData
print("json ",jsonData)
} catch {
print("snafoo alert")
}
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if let error = error {
completion(string: nil, error: error)
return
}
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
//print("Body: \(strData)\n\n")
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers);
self.jsonPParser(jsonResult,field2file: "ignore")
/*for (key, value) in self.parsedJson {
print("key2 \(key) value2 \(value)")
}*/
completion(string: "", error: nil)
} catch {
completion(string: nil, error: error)
}
})
task.resume()
}
Like this:
var item = [
"title": "Item title",
"CreatedAt": "12:13 12/12/2016",
"isDone": false
]
var mainDictionary = [
"name": "List name",
"CreatedAt": "12:12 12/12/2016",
"items": [item]
]
And the just convert to json with NSJSONSerialization like this:
do {
let json = try NSJSONSerialization.dataWithJSONObject(mainDictionary, options: [])
} catch {
print(error)
}
UPDATE:
If you need to add values to array in dictionary you can do that like this:
if var items = mainDictionary["items"] as? NSMutableArray {
items.addObject(newItem)
mainDictionary["items"] = items
}