Swift 3 parsing API response trouble - json

I'm trying to parse an API response in Swift an Im having trouble getting to nested objects and arrays in the response
here is my sample json
{
"Id": "10",
"Name": "PV Prediction By Site",
"Description": "",
"Permalink": "",
"Source_format": "JSON",
"Internal_function_name": "get-meteo-by-site",
"Additional_parameters": "Prediction",
"Sites": null,
"Data": [
{
"UTCDateString": "2017-05-01T20:10:33Z",
"Value": [
{
"metadata": {
"name": "Beck_Hill",
"latitude": 46.26,
"longitude": -112.44,
"height": 1926,
"timezone_abbrevation": "MDT",
"utc_timeoffset": -6,
"modelrun_utc": "2017-05-01 12:00",
"modelrun_updatetime_utc": "2017-05-01 16:41",
"kwp": 40.26,
"slope": 30,
"facing": 180,
"tracking": 0
},
"units": {
"time": "YYYY-MM-DD hh:mm",
"pvpower": "kW",
"snowcover": "mm",
"iam": "percent",
"temperature": "C"
},
"data_xmin": {
"time": [
"2017-05-01 07:00",
"2017-05-01 07:15",
"2017-05-01 07:30",
"2017-05-01 07:45",
"2017-05-01 08:00",
"2017-05-01 08:15",
"2017-05-01 08:30"
],
"pvpower_instant": [
40.26,
40.26,
40.26,
40.26,
40.26
]
}
}
]
}
]
}
And here is some of my parsing code, I can get to the first object in the "Data" array fine, but when i try to get the first object in the Value string it fails to convert the AnyObject to anything else
//get a Dictionary of sites
sitesDictionary = try JSONSerialization.jsonObject(with: decodedData, options: .allowFragments) as? [[String:AnyObject]]
CoreDataStack.sharedInstance.persistentContainer.performBackgroundTask({ (context) in
//loop thorugh all site and create SiteMO objects from them
for site in (sitesDictionary?.enumerated())! {
//SiteMO
let siteMO = SiteMO.siteInfo(siteInfo: site.element, inManagedObjectContext: context)!
let siteFeedsDictionary = site.element["Feeds"] as! [[String:AnyObject]]
//loop through every feed object and create FeedMO objects from them
for feed in siteFeedsDictionary.enumerated() {
//FeedMO
let feedMO = FeedMO.feedInfo(feedInfo: feed.element, site: siteMO, inManagedObjectContext: context)!
//what type of data is in the feed?
switch feedMO.additionalParameters! {
case "Weather":
//its a feed with a Weather object
print("There should be a WeatherMO created Here")
case "Prediction":
//its a feed with a Prediction object
let dataArray = feed.element["Data"] as? [[String:AnyObject]]
I need some data out of the "metadata" "units" and "data_xmin" objects

For metadata
result[“Data”][0][“Value”][0]

Related

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 can I populate part of a JSON object without repetitive code?

I am making use of the Microsoft Graph API, specifically the FindMeetingTimes API. Which can be seen here: https://learn.microsoft.com/en-us/graph/api/user-findmeetingtimes?view=graph-rest-1.0
I am using Swift to develop this. My JSON object works and the post is successful, but I am just wondering what would be the best / most efficient way to alter this code so that it cuts out the repetitive code - specifically the adding of locations in the JSON. The locations list could be very large, so I would like to iterate through an locations array and add these to the JSON object..
My method looks like this:
static func setupJsonObjectForFindMeetingTimeAllRooms(nameOfRoom: String, roomEmailAddress: String, dateStartString: String, dateEndString: String, durationOfMeeting: String) -> [String: Any] {
let jsonObj : [String: Any] =
[
"attendees": [
[
"type": "required",
"emailAddress": [
"name": nameOfRoom,
"address": roomEmailAddress
]
]
],
"locationConstraint": [
"isRequired": "true",
"suggestLocation": "false",
"locations": [
[
"displayName": "First Floor Test Meeting Room 1",
"locationEmailAddress": "FirstFloorTestMeetingRoom1#microsoft.com"
],
[
"displayName": "Ground Floor Test Meeting Room 1",
"locationEmailAddress": "GroundFloorTestMeetingRoom1#microsoft.com"
]
//and the rest of the rooms below this.. how do i do this outside in a loop? to prevent repetitive code?
]
],
"timeConstraint": [
"activityDomain":"unrestricted",
"timeslots": [
[
"start": [
"dateTime": dateStartString,
"timeZone": Resources.utcString
],
"end": [
"dateTime": dateEndString,
"timeZone": Resources.utcString
]
]
]
],
"meetingDuration": durationOfMeeting,
"returnSuggestionReasons": "true",
"minimumAttendeePercentage": "100",
"isOrganizerOptional": "true"
]
return jsonObj
}
What is the best way to go about doing this? Would I just remove the locations part of the JSON, and before returning, populate it with the array of locations?
I tried to implement a method to add locations to the JSON - using this method:
static func addLocationsToExistingJson(locations: [String], jsonObj: [String: Any]) -> [String: Any] {
var data: [String: Any] = jsonObj
for i in stride(from: 0, to: locations.count, by: 1){
let item: [String: Any] = [
"displayName": locations[i],
"locationEmailAddress": locations[i]
]
// get existing items, or create new array if doesn't exist
var existingItems = data["locations"] as? [[String: Any]] ?? [[String: Any]]()
// append the item
existingItems.append(item)
// replace back into `data`
data["locations"] = existingItems
}
return data
}
and calling this method before returning the JSON in the original method.. But it seems that the final JSON is not the format that I want.
The incorrect version looks like this:
["timeConstraint": ["activityDomain": "unrestricted", "timeslots": [["start": ["dateTime": "2019-02-07 14:00:00", "timeZone": "UTC"], "end": ["dateTime": "2019-02-07 15:00:00", "timeZone": "UTC"]]]], "minimumAttendeePercentage": "100", "isOrganizerOptional": "true", "returnSuggestionReasons": "true", "meetingDuration": "PT60M", "attendees": [["type": "required", "emailAddress": ["name": "N", "address": "TestUser6#qubbook.onmicrosoft.com"]]], "locationConstraint": ["isRequired": "true", "suggestLocation": "false"]]
Whereas the working JSON looks like this:
["timeConstraint": ["activityDomain": "unrestricted", "timeslots": [["start": ["dateTime": "2019-02-07 14:30:00", "timeZone": "UTC"], "end": ["dateTime": "2019-02-07 15:30:00", "timeZone": "UTC"]]]], "attendees": [["type": "required", "emailAddress": ["name": "N", "address": "TestUser6#qubbook.onmicrosoft.com"]]], "minimumAttendeePercentage": "100", "locations": [["displayName": "FirstFloorTestMeetingRoom1#qubbook.onmicrosoft.com", "locationEmailAddress": "FirstFloorTestMeetingRoom1#qubbook.onmicrosoft.com"], ["displayName": "GroundFloorTestMeetingRoom1#qubbook.onmicrosoft.com", "locationEmailAddress": "GroundFloorTestMeetingRoom1#qubbook.onmicrosoft.com"]], "locationConstraint": ["isRequired": "true", "suggestLocation": "false"], "meetingDuration": "PT60M", "isOrganizerOptional": "true", "returnSuggestionReasons": "true"]
How would I change my code so that the locations get added under the locationConstraint object within the JSON, rather than just to the JSON, not under the ["locationConstraint"] part?
As Joakim suggested, you could model the json structure via structs and Codable. For example:
struct AdditionalData: Codable {
let emailAdress: [String]
}
struct Person: Codable {
let age: Int
let name: String
let additionalData: AdditionalData
}
let additionalData = AdditionalData(emailAdress: ["test#me.com", "foo#bar.com"])
let p1 = Person(age: 30,
name: "Frank",
additionalData: additionalData)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
if let data = try? encoder.encode(p1),
let jsonString = String(data: data, encoding: .utf8) {
print(jsonString)
}
Which results in:
{
"age" : 30,
"name" : "Frank",
"additionalData" : {
"emailAdress" : [
"test#me.com",
"foo#bar.com"
]
}
}

Accessing nested JSON [String: Any] object and appending to it

Using swift, I am attempting to access the "locations" object within the "locationConstraint" part of a JSON that looks like this:
let jsonObj : [String: Any] =
[
"attendees": [
[
"type": "required",
"emailAddress": [
"name": nameOfRoom,
"address": roomEmailAddress
]
]
],
"locationConstraint": [
"isRequired": "true",
"suggestLocation": "false",
"locations": [
[
"displayName": "First Floor Test Meeting Room 1",
"locationEmailAddress": "FirstFloorTestMeetingRoom1#onmicrosoft.com"
],
[
"displayName": "Ground Floor Test Meeting Room 1",
"locationEmailAddress": "GroundFloorTestMeetingRoom1#onmicrosoft.com"
]
//and the rest of the rooms below this..
]
],
"meetingDuration": durationOfMeeting,
]
I am attempting to add items to the locations from outside this method (to prevent repetitive code, as location list could be large) - but I am having problems replacing back into this part of the json..
My method to do this:
static func setupJsonObjectForFindMeetingTimeAllRoomsTest(nameOfRoom: String, roomEmailAddress: String, dateStartString: String, dateEndString: String, durationOfMeeting: String, locations: [String]) -> [String: Any] {
let jsonObj : [String: Any] =
[
"attendees": [
[
"type": "required",
"emailAddress": [
"name": nameOfRoom,
"address": roomEmailAddress
]
]
],
"meetingDuration": durationOfMeeting
]
let jsonObject = addLocationsToExistingJson(locations:locations, jsonObj: jsonObj)
return jsonObject
}
and my method to add locations to the existing json object:
static func addLocationsToExistingJson(locations: [String], jsonObj: [String: Any]) -> [String: Any] {
var data: [String: Any] = jsonObj
let locConstraintObj = [
"isRequired": "true",
"suggestLocation": "false",
"locations" : []
] as [String : Any]
//try access locationConstraint part of json
data["locationConstraint"] = locConstraintObj
for i in stride(from: 0, to: locations.count, by: 1) {
let item: [String: Any] = [
"displayName": locations[i],
"locationEmailAddress": locations[i]
]
// get existing items, or create new array if doesn't exist
//this line below wrong? I need to access data["locationConstraint]["locations"]
//but an error occurs when i change to the above.. .how do i access it?
var existingItems = data["locations"] as? [[String: Any]] ?? [[String: Any]]()
// append the item
existingItems.append(item)
// replace back into `data`
data["locations"] = existingItems
}
return data
}
So ultimately, my final json object that works should look like this:
["meetingDuration": "PT60M", "returnSuggestionReasons": "true",
"attendees": [["emailAddress": ["address":
"TestUser6#qubbook.onmicrosoft.com", "name": "N"], "type":
"required"]], "minimumAttendeePercentage": "100",
"locationConstraint": ["locations": [["displayName": "First Floor Test
Meeting Room 1", "locationEmailAddress":
"FirstFloorTestMeetingRoom1#qubbook.onmicrosoft.com"], ["displayName":
"Ground Floor Test Meeting Room 1", "locationEmailAddress":
"GroundFloorTestMeetingRoom1#qubbook.onmicrosoft.com"]],
"suggestLocation": "false", "isRequired": "true"], "timeConstraint":
["activityDomain": "unrestricted", "timeslots": [["start":
["dateTime": "2019-02-07 14:30:00", "timeZone": "UTC"], "end":
["dateTime": "2019-02-07 15:30:00", "timeZone": "UTC"]]]],
"isOrganizerOptional": "true"]
Where as it looks like this:
["timeConstraint": ["activityDomain": "unrestricted", "timeslots":
[["start": ["dateTime": "2019-02-08 08:30:00", "timeZone": "UTC"],
"end": ["dateTime": "2019-02-08 09:30:00", "timeZone": "UTC"]]]],
"locationConstraint": ["suggestLocation": "false", "locations": [],
"isRequired": "true"], "attendees": [["emailAddress": ["address":
"TestUser6#qubbook.onmicrosoft.com", "name": "N"], "type":
"required"]], "returnSuggestionReasons": "true",
"isOrganizerOptional": "true", "minimumAttendeePercentage": "100",
"locations": [["locationEmailAddress":
"FirstFloorTestMeetingRoom1#qubbook.onmicrosoft.com", "displayName":
"FirstFloorTestMeetingRoom1#qubbook.onmicrosoft.com"],
["locationEmailAddress":
"GroundFloorTestMeetingRoom1#qubbook.onmicrosoft.com", "displayName":
"GroundFloorTestMeetingRoom1#qubbook.onmicrosoft.com"]],
"meetingDuration": "PT60M"]
Where the locations object is being added outside the locationConstraint part of the JSON.. I know I need to be accessing the locationConstraint part of my json like this: var existingItems = data["locationConstraint"]!["locations"] as? [[String: Any]] ?? [[String: Any]]() but this returns an error:
Type 'Any' has no subscript members
This is my first time working with JSONs and trying to manipulate them in swift.. How would I go about fixing this?
Solution by using model objects and Codable
as trojanfoe proposed, you should use model objects and manipulate them directly.
import Foundation
struct Meeting: Codable {
var attendees: [Attendee]
var locationConstraint: LocationConstraint
var meetingDuration: Int
}
struct Attendee: Codable {
var type: Type
var emailAddress: EmailAdress
enum `Type`: String, Codable {
case required
}
}
struct LocationConstraint: Codable {
var isRequired: Bool
var suggestLocation: Bool
var locations: [Location]
}
struct EmailAdress: Codable {
var name: String
var address: String
}
struct Location: Codable {
var displayName: String
var locationEmailAddress: String
}
At first we take your dictionary...
let jsonDict: [String: Any] =
[
"attendees": [
[
"type": "required",
"emailAddress": [
"name": "specificName",
"address": "specificAdress"
]
]
],
"locationConstraint": [
"isRequired": true,
"suggestLocation": false,
"locations": [
[
"displayName": "First Floor Test Meeting Room 1",
"locationEmailAddress": "FirstFloorTestMeetingRoom1#onmicrosoft.com"
],
[
"displayName": "Ground Floor Test Meeting Room 1",
"locationEmailAddress": "GroundFloorTestMeetingRoom1#onmicrosoft.com"
]
]
],
"meetingDuration": 1800,
]
... and serialize it.
let jsonData = try JSONSerialization.data(withJSONObject: jsonDict, options: .prettyPrinted)
print(String(data: jsonData, encoding: String.Encoding.utf8))
We then decode it into our meeting model.
var meeting = try JSONDecoder().decode(Meeting.self, from: jsonData)
We initialize a new location and append it to our meeting.locationConstraints.locations array.
let newLocation = Location(displayName: "newDisplayName", locationEmailAddress: "newLocationEmailAdress")
meeting.locationConstraint.locations.append(newLocation)
And finally reencode our model object again.
let updatedJsonData = try JSONEncoder().encode(meeting)
print(String(data: updatedJsonData, encoding: String.Encoding.utf8))

Read an url HTTPS in a JSON file swift

I try to read an URL request (in dictionary "vehicles")in a json but the array will empty
This is the json and I need to read the dictionary and the url that are in vehicles
{
"count": 87,
"next": "https://swapi.co/api/people/?page=2",
"previous": null,
"results": [
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "https://swapi.co/api/planets/1/",
"films": [
"https://swapi.co/api/films/2/",
"https://swapi.co/api/films/6/",
"https://swapi.co/api/films/3/",
"https://swapi.co/api/films/1/",
"https://swapi.co/api/films/7/"
],
"species": [
"https://swapi.co/api/species/1/"
],
"vehicles": [
"https://swapi.co/api/vehicles/14/",
"https://swapi.co/api/vehicles/30/"
],
This is the code that I try
if let results = json["results"] as? [[String : AnyObject]] {
var finalArray2 : [String] = []
for result in results {
if let dict = result as? [String: Any], let vehicles = dict["vehicles"] as? String{
self.URLveicoli = vehicles
//print(self.nomepersonaggio)
print(self.URLveicoli)
//finalArray.append(name)
finalArray2.append(vehicles)
}
}
print(finalArray2)
}
In json vehicles is an array of strings (urls) not a string.
Change this
let vehicles = dict["vehicles"] as? String
To
let vehicles = dict["vehicles"] as? [String]
And to append array of vechicles
finalArray2.append(contentsOf: vehicles)

Accessing JSON data in swift

I am trying to access JSON data in my swift code and I'm having trouble getting it to return correctly. Here is my JSON code:
[
{
"id": "1",
"isImage": "0",
"name": "test name",
"post": "test post",
"time": "10:27",
"ip": "192.168.1.1",
"city ": "Columbus",
"latlong": "39.896418,-82.9751105",
"clientID": "clientID",
"popularity": "300"
},
{
"id": "2",
"isImage": "0",
"name": "test name two",
"post": "test post two",
"time": "13:37",
"ip": "192.168.1.1",
"city ": "Columbus",
"latlong": "39.896418,-82.9751105",
"clientID": "clientID",
"popularity": "69"
}
]
I'd just like to know how to access the data by their keys json[0].['id'] or?
I am currently using this json.swift module and trying to access the data with
func jsonHandle(data: NSString) {
var parsedJSON = JSON(data)
var id = parsedJSON[0].["id"]
NSLog("\(id)")
}
but it returns nothing. Any Ideas?
You can call the JSON(string:...) rendition and eliminate the period between the [0] and the ["id"]:
func jsonHandle(data: NSString) {
let parsedJSON = JSON(string: data)
var id = parsedJSON[0]["id"]
NSLog("\(id)")
}
Or, if you had a NSData you could use the JSON(data: ...) rendition:
func jsonHandle(data: NSData) {
let parsedJSON = JSON(data: data)
let id = parsedJSON[0]["id"]
NSLog("\(id)")
}
Or, if you wanted to use the native NSJSONSerialization, rather than that third-party library, you could:
func jsonHandle(data: NSData) {
var error: NSError?
let parsedJSON = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &error) as NSArray
let id = parsedJSON[0]["id"]
NSLog("\(id)")
}
Personally, I'd lean towards the standard NSJSONSerialization approach as it's a tried and true approach, but that's your call.