Getting invalid json when calling JSONSerialization in Swift - json

I am trying to access a URL and parse the JSON output. Printing JSON in utf8 encoding
Code:
let urlString:String = "https://developer.nrel.gov/api/.../"
let pvURL = URL(string: urlString)
let dataTask = URLSession.shared.dataTask(with:pvURL!) { (data, response, error) in
do {
let json = try JSONSerialization.jsonObject(with: data!)
print(String(data: data!, encoding: .utf8)!)
}catch let err {
print("error: \(err.localizedDescription)")
}
}
dataTask.resume()
Prints the following output. I try this JSON in an online JSON parser it fails. Gives error in the first line itself.
{"inputs":{"lat":"29.93","lon":"-95.61","system_capacity":"30.30","azimuth":"180","tilt":"40","array_type":"1","module_type":"1","losses":"10"},"errors":[],"warnings":[],"version":"1.0.1","ssc_info":{"version":45,"build":"Linux 64 bit GNU/C++ Jul 7 2015 14:24:09"},"station_info":{"lat":29.93000030517578,"lon":-95.62000274658203,"elev":41.0,"tz":-6.0,"location":"None","city":"","state":"Texas","solar_resource_file":"W9562N2993.csv","distance":964},"outputs":{"ac_monthly":[3480.57373046875,3440.078369140625,3992.6513671875,3977.071533203125,4074.91357421875,3701.75,3897.655517578125,4248.00390625,4023.283447265625,4157.29931640625,3605.156005859375,3342.12890625],"poa_monthly":[139.791015625,140.18896484375,164.8218536376953,164.47149658203125,173.2971649169922,159.90576171875,169.84793090820312,186.20114135742188,173.14492797851562,176.2291717529297,148.28318786621094,136.62326049804688],"solrad_monthly":[4.509387493133545,5.006748676300049,5.316833972930908,5.4823832511901855,5.590230941772461,5.3301920890808105,5.4789652824401855,6.00648832321167,5.77149772644043,5.684812068939209,4.94277286529541,4.407201766967773],"dc_monthly":[3644.867919921875,3606.52001953125,4179.85107421875,4158.3193359375,4252.9140625,3865.03369140625,4069.092041015625,4432.62744140625,4198.369140625,4336.99609375,3767.055419921875,3490.091064453125],"ac_annual":45940.55859375,"solrad_annual":5.293959140777588,"capacity_factor":17.308107376098633}}`
whereas if I access the urlString in a browser gives valid json:
{
"inputs": {
"lat": "29.93",
"lon": "-95.61",
"system_capacity": "30.30",
"azimuth": "180",
"tilt": "40",
"array_type": "1",
"module_type": "1",
"losses": "10"
},
"errors": [],
"warnings": [],
"version": "1.0.1",
"ssc_info": {
"version": 45,
"build": "Linux 64 bit GNU/C++ Jul 7 2015 14:24:09"
},
"station_info": {
"lat": 29.93000030517578,
"lon": -95.62000274658203,
"elev": 41,
"tz": -6,
"location": "None",
"city": "",
"state": "Texas",
"solar_resource_file": "W9562N2993.csv",
"distance": 964
},
"outputs": {
"ac_monthly": [
3480.57373046875,
3440.078369140625,
3992.6513671875,
3977.071533203125,
4074.91357421875,
3701.75,
3897.655517578125,
4248.00390625,
4023.283447265625,
4157.29931640625,
3605.156005859375,
3342.12890625
],
...
}

In your code you are converting the JSON data into an JSON object(Array, Dictionary).
But In browser the JSON data is printing as String and not as JSON object(Array, Dictionary).
So if you want to print the JSON string in your code as well, you can print as like below.
let string = String(data: self, encoding: .utf8)
print("JSON String:\(String(describing: string))")
Hope it helps.

Related

Json decode result is printing into console in swift playground

I am new to swift . I created simple playground and added the file with extension json into playground . I am trying to decode the result and print the ID into console but any reason ,it not printing the result into console , I do not see error into console window ..
Here is the playground project structure ..
Here is my json file ..
let json = """
{
"id": "1",
"options": [
{
"id": "11",
"options": [
{
"id": "111",
"options": []
}
]
},
{
"id": "2",
"options": [
{
"id": "21",
"options": []
},
{
"id": "22",
"options": [
{
"id": "221",
"options": []
}
]
}
]
}
]
}
Here is the code .. I tried ..
struct People: Codable {
let id: String
let options: [People]
}
func loadJson(filename fileName: String) -> People? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(People.self, from: data)
print(jsonData.id)
return jsonData
} catch {
print("error:\(error)")
}
}
return nil
}
It not printing the ID of the decode json ..
So I did get it to print the ID, I changed the people file name to people.json and changed the contents to:
{
"id": "1",
"options": [{
"id": "11",
"options": [{
"id": "111",
"options": []
}]
},
{
"id": "2",
"options": [{
"id": "21",
"options": []
},
{
"id": "22",
"options": [{
"id": "221",
"options": []
}]
}
]
}
]
}
(Notice I removed the let json = statement.)
After that in the playground where define the People struct and the loadJson method you can call it like so:
loadJson(filename: "people")
So now you end up with:
struct People: Codable {
let id: String
let options: [People]
}
func loadJson(filename fileName: String) -> People? {
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(People.self, from: data)
print(jsonData.id)
return jsonData
} catch {
print("error:\(error)")
}
}
return nil
}
loadJson(filename: "people")

How to serialize a Json partially?

I have a huge json (20 mb, 6000+ records) that If I use decode it takes ages, then I started using serialize to try to parse it in pieces. But I'm having trouble to parse it (getting null when I serialize it, funny enough I don't specify as? [String:Any] I actually get the json)
is there a better way to parse a Json partially?
Json structure
[
{
"page": 1,
"total": 100,
"data": [
{
"address": "677 Quincy Street #1B",
"neighborhood": "Stuyvesant Heights",
"zipcode": "11221",
"latitude": 40.68935935,
"longitude": -73.93179845,
"bedroom": "2 beds",
"bath": "1.5 baths",
"area": null,
"status": "current",
"photos": [
"https://photos.zillowstatic.com/fp/b444ec7e07f1bb83f27c6dfa2167e92a-se_extra_large_1500_800.jpg"
],
"video": null,
"description": "NO FEE!!Luxury 2 BR / 1.5 Bath DUPLEX Apartment",
"new_listing": 1,
"date_available": "Available Now",
"open_house": null,
"price": 2999,
"dishwasher": false,
"washer_and_dryer": false,
"pets_allowed": true,
"live_in_super": false,
"elevator": false,
"url": "https://streeteasy.com/building/677-quincy-street-brooklyn/1b"
},
]
}
]
I made it work, but sadly serialization doesn't help with the load speed, I was thinking to do "pagination with the first 100 items" but swift needs to parse the entire json first.
//serialization baby!
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [[String:Any]]
//make it an array
let first100Listings = json!.first!["data"] as? NSArray
let jsonData = try JSONSerialization.data(withJSONObject: first100Listings!, options: [])
let responseModel = try JSONDecoder().decode([Listings].self, from: jsonData)
self.listingsZ = responseModel

How to parse local JSON data in Swift?

How to parse local JSON data where nested (optional) property is same as main.
Items data may be available or may not be available.
struct Category: Identifiable, Codable {
let id: Int
let name: String
let image: String
var items: [Category]?
}
I am using common Bundle extension to parse JSON data.
extension Bundle {
func decode<T: Codable>(_ file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "y-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode \(file) from bundle.")
}
return loaded
}
}
For eg data :
[
{
"id": 1,
"name": "Apple",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPhone 11 Pro",
"image": "img_url"
},
{
"id": 2,
"name": "iPhone 11 Pro Max",
"image": "img_url"
}
]
},
{
"id": 2,
"name": "iPad",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "iPad mini",
"image": "img_url"
},
{
"id": 2,
"name": "iPad Air",
"image": "img_url"
},
{
"id": 3,
"name": "iPad Pro",
"image": "img_url"
}
]
}
]
},
{
"id": 2,
"name": "Samsung",
"image": "img_url",
"items" : [
{
"id": 1,
"name": "Phone",
"image": "img_url"
},
{
"id": 2,
"name": "Tablet",
"image": "img_url"
}
]
}
]
Nesting is not the issue here, You are facing an Array of Contents. so you should pass [Content] to the decoder like:
let jsonDecoder = JSONDecoder()
try! jsonDecoder.decode([Category].self, from: json)
🎁 Property Wrapper
You can implement a simple property wrapper for loading and decoding all of your properties:
#propertyWrapper struct BundleFile<DataType: Decodable> {
let name: String
let type: String = "json"
let fileManager: FileManager = .default
let bundle: Bundle = .main
let decoder = JSONDecoder()
var wrappedValue: DataType {
guard let path = bundle.path(forResource: name, ofType: type) else { fatalError("Resource not found") }
guard let data = fileManager.contents(atPath: path) else { fatalError("File not loaded") }
return try! decoder.decode(DataType.self, from: data)
}
}
Now you can have any property that should be loaded from a file in a Bundle like:
#BundleFile(name: "MyFile")
var contents: [Content]
Note that since the property should be loaded from the bundle, I raised a FatalError. Because the only person should be responsible for these errors is the developer at the code time (not the run time).

"Incorrect JSON format"

I am making a POST request with required body parameters, testing this POST request in POSTMAN gives me correct response, but when I am passing same body parameters from my code I am getting below error-:
Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.
Code -:
func submitStudentAttendance(url:String,id:String,SessionID: Int,SchoolID:Int,BatchID:Int,ClassID:Int,SectionID:Int,Medium:Int,Date:String,accessToken:String) {
let params : [String:Any] = [
"$id":"1",
"SchoolID":1,
"SessionID":42,
"BatchID":106,
"ClassID":634,
"SectionID":1246,
"Medium":"English",
"Date":"0001-01-01T00:00:00",
"CreatedBy":118,
"CreatedOn":"2019-04-14T17:02:34",
"studentList":"",
"AttendenceData":"[{\"ID\":0,\"SchoolID\":null,\"SessionID\":null,\"ClassID\":634,\"SectionID\":1246,\"StudID\":\"DSM00399\",\"SubjectID\":null,\"Date\":\"0001-01-01T00:00:00\",\"Attendence\":\"P\",\"CreatedOn\":null,\"CreatedBy\":0,\"IsActive\":null,\"Remarks\":\"rem16\",\"timeID\":null,\"AtndType\":null,\"BatchID\":null,\"Medium\":null}]"
]
let urlRequest = NSURL(string: url)
var request = URLRequest(url: urlRequest! as URL)
let tokenString = "Bearer " + accessToken
request.httpMethod = "POST"
request.setValue(tokenString, forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let data = try! JSONSerialization.data(withJSONObject: params, options: [])
let json = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
if let json = json {
print(json)
}
request.httpBody = json!.data(using: String.Encoding.utf8.rawValue);
let alamoRequest = Alamofire.request(request as URLRequestConvertible)
alamoRequest.responseString {
response in
switch response.result {
case .success:
if response.data != nil {
do{
let json = try JSON(data: response.data!)
print(json)
}catch{
print(error)
}
}
break
case .failure(let error):
print(error)
}
}
}
The body part is exactly same as in POSTMAN, but still not working. Can anyone let me know what's going wrong here? Is this server side issue or I am sending body request in wrong format?
POSTMAN RESPONSE -:
When the params as declared in your question is converted into JSON, it looks like this. Here, AttendanceData value is not converted as proper JSON structure. It looks like a string.
JSON from your param:
{
"Date": "0001-01-01T00:00:00",
"CreatedBy": 118,
"$id": "1",
"BatchID": 106,
"ClassID": 634,
"Medium": "English",
"studentList": "",
"SchoolID": 1,
"SectionID": 1246,
"AttendenceData": "[{\"ID\":0,\"SchoolID\":null,\"SessionID\":null,\"ClassID\":634,\"SectionID\":1246,\"StudID\":\"DSM00399\",\"SubjectID\":null,\"Date\":\"0001-01-01T00:00:00\",\"Attendence\":\"P\",\"CreatedOn\":null,\"CreatedBy\":0,\"IsActive\":null,\"Remarks\":\"rem16\",\"timeID\":null,\"AtndType\":null,\"BatchID\":null,\"Medium\":null}]",
"CreatedOn": "2019-04-14T17:02:34",
"SessionID": 42
}
But if you declare param as shown below, and converted to JSON, AttendanceData value is properly converted to JSON.
let params : [String:Any] = [
"$id":"1",
"SchoolID":1,
"SessionID":42,
"BatchID":106,
"ClassID":634,
"SectionID":1246,
"Medium":"English",
"Date":"0001-01-01T00:00:00",
"CreatedBy":118,
"CreatedOn":"2019-04-14T17:02:34",
"studentList":"",
"AttendenceData":[
"ID":0,"SchoolID":"null","SessionID":"null","ClassID":634,"SectionID":1246,"StudID":"DSM00399","SubjectID":"null","Date":"0001-01-01T00:00:00","Attendence":"P","CreatedOn":"null","CreatedBy":0,"IsActive":"null","Remarks":"rem16","timeID":"null","AtndType":"null","BatchID":"null","Medium":"null"
]
]
JSON
{
"$id": "1",
"BatchID": 106,
"CreatedBy": 118,
"ClassID": 634,
"studentList": "",
"SectionID": 1246,
"SessionID": 42,
"AttendenceData": {
"IsActive": "null",
"AtndType": "null",
"SectionID": 1246,
"timeID": "null",
"SessionID": "null",
"Attendence": "P",
"Date": "0001-01-01T00:00:00",
"Remarks": "rem16",
"Medium": "null",
"SchoolID": "null",
"BatchID": "null",
"ClassID": 634,
"StudID": "DSM00399",
"SubjectID": "null",
"CreatedBy": 0,
"ID": 0,
"CreatedOn": "null"
},
"Date": "0001-01-01T00:00:00",
"CreatedOn": "2019-04-14T17:02:34",
"Medium": "English",
"SchoolID": 1
}
Hope it could also solve your problem.

How to parse data which starts with an array [?

So in my swift 3 xcode project, I want to parse some data using JSOmN by consuming a web service called "http://tour-pedia.org/api/"
And the data within it for example is set like this:
[
{
"address": "Science Park 904",
"category": "attraction",
"id": 30884,
"lat": 52.355320008998,
"lng": 4.9574317242814,
"location": "Amsterdam",
"name": "Dakterras Science Park",
"originalId": "4d8b3370bc848cfa1043ea2b",
"polarity": 0,
"subCategory": "Scenic Lookout",
},
{
"address": "Science Park 201",
"category": "attraction",
"id": 30661,
"lat": 52.356701093273,
"lng": 4.9529844809109,
"location": "Amsterdam",
"name": "In 'de Natuur' rondom Science Park",
"originalId": "4da2ede7c6e96ea85e1ede5d",
"polarity": 0,
"subCategory": "Field",
}
]
I want to use the address, location and name field.
so far i have done this:
func fetchInfos(){
let urlRequest = URLRequest(url: URL(string: "http://tour-pedia.org/api/")!)
let task = URLSession.shared.dataTask(with: urlRequest){ (data,response,error) in
if error != nil {
print(error)
return
}
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
} catch let error {
print(error)
}
}
}
but i don't know how to continue on from here.
Any help will be appreciated, thank you!
Let us assume your string s is the json with sqaure brackets
String s = "[{\"a\":\"b\"}]";
s = s.substring(1, s.length() - 1);
//do all your processing on s now, as the sqaure brackets will be removed !!