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"
]
}
}
Related
I have called an API to get all holidays in a year, it came out a Json type. But I only can extract it like below (it is one of many elements of "items")
"items": [
{
"kind": "calendar#event",
"etag": "\"3235567993214000\"",
"id": "20200101_1814eggq09ims8ge9pine82pclgn49rj41262u9a00oe83po05002i01",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=MjAyMDAxMDFfMTgxNGVnZ3EwOWltczhnZTlwaW5lODJwY2xnbjQ5cmo0MTI2MnU5YTAwb2U4M3BvMDUwMDJpMDEgZW4udWsjaG9saWRheUB2",
"created": "2021-04-07T08:26:36.000Z",
"updated": "2021-04-07T08:26:36.607Z",
"summary": "New Year's Day",
"creator": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"organizer": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"start": {
"date": "2020-01-01"
},
"end": {
"date": "2020-01-02"
},
"transparency": "transparent",
"visibility": "public",
"iCalUID": "20200101_1814eggq09ims8ge9pine82pclgn49rj41262u9a00oe83po05002i01#google.com",
"sequence": 0,
"eventType": "default"
},
{
"kind": "calendar#event",
"etag": "\"3235567993214000\"",
"id": "20200412_1814eggq09ims8gd8lgn6t35e8g56tbechgniag063i0ue048064g0g",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=MjAyMDA0MTJfMTgxNGVnZ3EwOWltczhnZDhsZ242dDM1ZThnNTZ0YmVjaGduaWFnMDYzaTB1ZTA0ODA2NGcwZyBlbi51ayNob2xpZGF5QHY",
"created": "2021-04-07T08:26:36.000Z",
"updated": "2021-04-07T08:26:36.607Z",
"summary": "Easter Sunday",
"creator": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"organizer": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"start": {
"date": "2020-04-12"
},
"end": {
"date": "2020-04-13"
},
"transparency": "transparent",
"visibility": "public",
"iCalUID": "20200412_1814eggq09ims8gd8lgn6t35e8g56tbechgniag063i0ue048064g0g#google.com",
"sequence": 0,
"eventType": "default"
}
I try to get the value in key "start" and key "summary" but I can't.
Xcode told me that "items" is a __NSArrayI type.
What I've tried so far is create a class simple like this (just use to try first, so I didn't make all variable)
class API_Info {
var kind: String?
var etag: String?
var id: String?
var status: String?
var htmlLink: String?
var created: String?
var updated: String?
var summary: String?
init(items: [String:Any]){
self.kind = items["kind"] as? String
self.etag = items["etag"] as? String
self.id = items["id"] as? String
self.status = items["status"] as? String
self.htmlLink = items["htmlLink"] as? String
self.created = items["created"] as? String
self.updated = items["updated"] as? String
self.summary = items["summary"] as? String
}
}
And I parse like this:
guard let items = json!["items"]! as? [API_Info] else{
print("null")
return
}
In this way, else statement was run.
What am I doing wrong, and how can I get the data I want?
Thanks in advance.
Codable is the solution here. Below is the struct I used rather than your class
struct ApiInfo: Codable {
let kind: String
let etag: String
let id: String
let status: String
let htmlLink: String
let created: Date
let updated: Date
let summary: String
}
Then I created a root type to hold the array
struct Result: Codable {
let items: [ApiInfo]
}
And then the decoding
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let result = try decoder.decode(Result.self, from: data)
print(result.items)
} catch {
print(error)
}
Notice that the data values are decoded to Date objects.
Optionally you can skip the root type and decode as a dictionary
do {
let items = try decoder.decode([String: [ApiInfo]].self, from: data)
if let values = items["items"] {
print(values)
}
} catch {
print(error)
}
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))
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)
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]
I Have a Dictionary of Dictionaries that I need to convert to Json.
[
Dict1:1,
test: A Value,
NestedDict1:[
city: A City Name,
address: An Address,
NestedDict2: [
1: 1,
39: 2
],
favorite1: 2,
favorite3: Chocolate
]
]
When I use the
NSJSONSerialization.dataWithJSONObject(myJsonDict, options: NSJSONWritingOptions.PrettyPrinted, error: nil)
it only encodes the outer most dictionary. So my output looks something like this:
{
"Dict1":"1",
"test": "A Value",
"NestedDict1":"[
city: A City Name,
address: An Address,
NestedDict2: [
1: 1,
39: 2
],
favorite1: 2,
favorite3: Chocolate
]"
}
How do I JSON the inner dictionaries as well?
Swift 3
let myJsonDict : [String: Any] = [
"Dict1": "1",
"test": "A Value",
"NestedDict1":[
"city": "A City Name",
"address": "An Address",
"NestedDict2": [
"1": "1",
"39": "2"
],
"favorite1": "2",
"favorite3": "Chocolate"
]
]
let jsonObject = try? JSONSerialization.data(withJSONObject: myJsonDict, options: [])
if let jsonString = String(data: jsonObject!, encoding: .utf8) {
print(jsonString)
}
Output
{"test":"A Value","Dict1":"1","NestedDict1":{"favorite1":2,"city":"A
City
Name","NestedDict2":{"1":"1","39":"2"},"favorite3":"Chocolate","address":"An
Address"}}
I think the problem is more with your representation of the data.
If you can get away with changing all the keys to Strings, then it can be a dictionary, since Strings conform to Hashable. Otherwise, it would be defined as Any, and can't really be a Dictionary key.
Doing this allows the following:
let myJsonDict : [String:AnyObject] = [
"value": 1123,
"test": "some string",
"NestedDict1": [
"city": "A City Name",
"address": "An Address",
"NestedDict2": [
"1": 1,
"39": 2
],
"favorite1": 2,
"favorite3": "Chocolate"
]
]
var jsonObject = NSJSONSerialization.dataWithJSONObject(myJsonDict, options: NSJSONWritingOptions.PrettyPrinted, error: nil)
println(NSString(data: jsonObject!, encoding: NSUTF8StringEncoding)!)
which gives the following output:
{
"test" : "some string",
"NestedDict1" : {
"city" : "A City Name",
"address" : "An Address",
"favorite3" : "Chocolate",
"NestedDict2" : {
"1" : 1,
"39" : 2
},
"favorite1" : 2
},
"value" : 1123
}