How can I populate part of a JSON object without repetitive code? - json

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

How to parse this type of data to a JSON in Swift?

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)
}

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)

Swift 3 parsing API response trouble

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]

nested dictionaries converted to json swift

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
}