nested dictionaries converted to json swift - json

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
}

Related

Swift - How to JSONDecode with codable for JSON of nested arrays (where arrays without keys or names as per example given)

I am new to Swift and Codable approach. I have to decode the following JSON structure, through Codable approach in Swift project.
[
[
[
{
"id": "58",
"parentCat": "7",
"catFirstTitle": "freedom to"
},
{
"id": "40",
"parentCat": "5",
"catFirstTitle": "freedom to"
}
],
[
{
"id": "58",
"parentCat": "7",
"catFirstTitle": "freedom to"
},
{
"id": "40",
"parentCat": "5",
"catFirstTitle": "freedom to"
}
]
],
[
[
{
"id": "58",
"parentCat": "7",
"catFirstTitle": "freedom to"
}
]
]
]
I could not find any examples of decoding multi level jsonArrays without key names. Any pointer or example will be a great help to me.
You can create a simple struct:
struct Item: Codable {
let id: String
let parentCat: String
let catFirstTitle: String
}
And decode as a nested array object:
let result = try JSONDecoder().decode([[[Item]]].self, from: jsonData)
Note that the result will be of type [[[Item]]].
You may want to flatten it as well:
let flattened = result.flatMap { $0 }.flatMap { $0 }

How to deserialize JSON in swift

I want to decode a JSON file into model objects. Unfortunately it doesn't work in the right way. So I don't get errors, but the "decoding-result" don't corresponds to my expectations.
I have the following JSON file and I want to decode it in the shown structs. I have trimmed the json file. Why get I just one "slider image" instead of 5 (the property image of ImagesSlider contains an array with just the first image/element).
What am I missing?
JSON:
[{"imageSlider" : [{
"image" : [{
"imageId" : "1",
"imageName" : "germany1",
"imageBigName" : "germany1_BIG",
"imageRights" : "Peter"
}],
"image" : [{
"imageId" : "2",
"imageName" : "germany2",
"imageBigName" : "germany2_BIG",
"imageRights" : "Peter"
}],
"image" : [{
"imageId" : "3",
"imageName" : "germany3",
"imageBigName" : "germany3_BIG",
"imageRights" : "Peter"
}],
"image" : [{
"imageId" : "4",
"imageName" : "germany4",
"imageBigName" : "germany4_BIG",
"imageRights" : "Peter"
}],
"image" : [{
"imageId" : "5",
"imageName" : "germany5",
"imageBigName" : "germany5_BIG",
"imageRights" : "Peter"
}]
}]
}]
Swift:
struct CountryModel : Decodable, Equatable {
var countryName : String
var inhabitants : String
var capital : String
var currency : String
var imageName : String
var imageSlider: [ImagesSlider]
}
struct ImagesSlider : Decodable, Equatable {
var image: [Image]
}
struct Image : Decodable, Equatable {
var imageId: String
var imageName: String
var imageBigName: String
var imageRights: String
}
Decoding:
func loadData() -> [CountryModel] {
var data: Data
guard let file = Bundle.main.url(forResource: "data", withExtension: "json") else {
fatalError("Error")
}
data = try! Data(contentsOf: file)
let decoder = JSONDecoder()
return try! decoder.decode([CountryModel].self, from: data)
}
Thanks for your help...
Edit:
My question isn't solved by the linked question...
The json looks wrong, remember that you should have only one unique key per json object. Right now is:
[
{
"imageSlider": [
{
"image": [
{
"imageId": "1",
"imageName": "germany1",
"imageBigName": "germany1_BIG",
"imageRights": "Peter"
}
],
"image": [
{
"imageId": "2",
"imageName": "germany2",
"imageBigName": "germany2_BIG",
"imageRights": "Peter"
}
]
}
]
}
]
And it should be like this:
[
{
"imageSlider": [
{
"image": [
{
"imageId": "1",
"imageName": "germany1",
"imageBigName": "germany1_BIG",
"imageRights": "Peter"
},
{
"imageId": "2",
"imageName": "germany2",
"imageBigName": "germany2_BIG",
"imageRights": "Peter"
}
]
}
]
}
]
Notice that "germany2" item is inside of the array of image.
let decoder = JSONDecoder()
let jsonData = Data(jsonString.utf8)
do {
let country = try decoder.decode([CountryModel].self, from: jsonData)
print(country[0].imageSlider[0].image.count)
} catch {
print(error.localizedDescription)
}
// Console message
2
Because your JSON contains multiple Images object with the same key! That is not valid and each one overwrites others (randomly probably). You should turn that json to array of objects:
[{
"imageSlider" : [{
"image" : [{
"imageId" : "1",
"imageName" : "germany1",
"imageBigName" : "germany1_BIG",
"imageRights" : "Peter"
}]},{
"image" : [{
"imageId" : "2",
"imageName" : "germany2",
"imageBigName" : "germany2_BIG",
"imageRights" : "Peter"
}]}
}]
}]
Also other keys of the CountryModel should be presented since all of them are non-optional.

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

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]