How parse json recursively using codable with swift - swift

I am trying to defining a decoding class model to decode this kind of json file:
Here a short extraction to understand the problem, in reality it is more nested.
{
"Title" : "Root",
"Subtitle" : "RootTree",
"Launch" : [
{
"DisplayName" : "Clients",
"Launch" : [
{
"DisplayName" : "Clients Details",
"Launch" : [
{
"DisplayName" : "Item1",
"URI" : "/rest/..."
},
{
"DisplayName" : "Item2",
"URI" : "/rest/..."
},
{
"DisplayName" : "Item3",
"URI" : "/rest/..."
}
]
}
]
}
]
}
Here my structure, I used a class because of the recursive usage:
final class Url: Codable {
let name : String
let uri: String?
let launch: [LaunchStructure]?
enum CodingKeys: String, CodingKey {
case name = "DisplayName"
case uri = "URI"
case launch = "Launch"
}
}
final class LaunchStructure: Codable {
let launch: [Url]
enum CodingKeys: String, CodingKey {
case launch = "Launch"
}
}
In the Title and Subtitle I am not interested, therefore I have excluded it from the class. I would like to get the Displayname and the uri from the items. As I said, the structure is more nested, but always the same structure. Is it possible to read the elements using a recursive way.
I am to decode it in this manner:
...
let result = Result { try JSONDecoder().decode(LaunchStructure.self, from: data) }
Thank you, best regards
Arnold

You don't need two types here at all, just one will do:
struct Item: Codable {
let name : String? // not all entries in your example has it, so it's optional
let uri: String?
let launch: [Item]? // same here, all leaf items doesn't have it
enum CodingKeys: String, CodingKey {
case name = "DisplayName"
case uri = "URI"
case launch = "Launch"
}
}

Related

URLSession POST request with json "_id" element with codable class in Swift

I'm trying to use URLSessions to get and post data. I don't have problems with get requests on Swift, just post. I followed this
I have a codable class that looks like this:
class Item: Codable {
var _id: String?
var name: String = ""
var color: String = ""
var rating: Int = 0
init(name: String, color: String, rating: Int){
self.name = name
self.color = color
self.rating = rating
}
}
And json data that looks like this:
[ {
"_id" : "5e50a10c4ea5d87f0001c9da",
"name" : "Pepper",
"color" : "blue",
"rating" : 4
},
{
"_id" : "5e50a10c4ea5d87f0001c9db",
"name" : "Pepper",
"color" : "blue",
"rating" : 2
},
{
"_id" : "5e50a10c4ea5d87f0001c9dc",
"name" : "Pepper"
"color" : "blue"
"rating" : 6
}
]
I'm currently using restdb.io for my database, and have tested my requests using Postman for all Get,Post,Put,...etc.
On Postman, when I create a POST request with Json body with just name, color and rating elements, it will generate a unique _id without me having to specify.
When I do this on Swift and send a post request with Item object using the init() method, where I left _id as an optional in the class, my code crashes with an "Unexpectedly found nil while implicitly unwrapping an Optional value". How do I work around that?
If you don't need _id in your swift code you can ignore it and not include it in your codable struct. In this way, the codable will ignore it and not process it

Unable to create an array of JSON's and assign it to a key which are to be send as parameters to Alamofire Post request?

I have a Post Request, in which I am trying to create an Array of json which the user types and then send to the server, I have used dictionary and it is working for a single request but not for multiple requests.
The JSON structure to be sent is
{
"id" : "u_101"
"data" : [
{ "name" : "Shubham"
"age" : "23"
},
{
"name" : "S"
"age" : "20"
}
]
}
Here is what I am using in swift for setting the parameters of alamofire request.
func setData (id: String, data: [Any]) {
request.httpMethod = post
var parameters = Parameters()
parameters["id"] = id
parameters["data"] = data
}
Then in the view controller I am doing this, (Items contain a dictionary of entered data through the view )
var allData : [Any] = []
for item in items {
var data: [String:String] = [:]
data["name"] = item.key
data["age"] = item.value
allData.append(data)
}
setData(id: "u_101", data: alldata)
This is not working and the server is throwing error.
If I send this to the Alamofire post request.
{
"id" : "u_101"
"data" : [
{ "name" : "Shubham"
"age" : "23"
}
]
}
The server responds with a success.

Swift - Decoding a Deeply Nested Dictionary

I am so close - but I am struggling with a very simple function to allow me to access a data point deeply nested in my JSON. The example I am using is on the Google directions API.
Sample JSON (from GMapsAPI):
{
"geocoded_waypoints" : [
{
"geocoder_status" : "OK",
"partial_match" : true,
"place_id" : "ChIJ960bMolw44kRQcGOlOZQ-r8",
"types" : [ "premise" ]
},
{
"geocoder_status" : "OK",
"partial_match" : true,
"place_id" : "EiMxMTggU2FsZW0gU3QsIEJvc3RvbiwgTUEgMDIxMTMsIFVTQSIaEhgKFAoSCSvDfDSJcOOJEbQanF0WxROfEHY",
"types" : [ "street_address" ]
}
],
"routes" : [
{
"bounds" : {
"northeast" : {
"lat" : 42.3647252,
"lng" : -71.0555085
},
"southwest" : {
"lat" : 42.3644965,
"lng" : -71.05552419999999
}
},
"copyrights" : "Map data ©2018 Google",
"legs" : [
{
"distance" : {
"text" : "82 ft",
"value" : 25
},
"duration" : {
"text" : "1 min",
"value" : 11
},
"end_address" : "118 Salem St, Boston, MA 02113, USA",
"end_location" : {
"lat" : 42.3647252,
"lng" : -71.0555085
},
"start_address" : "115 Salem St, Boston, MA 02113, USA",
"start_location" : {
"lat" : 42.3644965,
"lng" : -71.05552419999999
},
"steps" : [
{
"distance" : {
"text" : "82 ft",
"value" : 25
},
"duration" : {
"text" : "1 min",
"value" : 11
},
"end_location" : {
"lat" : 42.3647252,
"lng" : -71.0555085
},
"html_instructions" : "Head \u003cb\u003enorth\u003c/b\u003e on \u003cb\u003eSalem St\u003c/b\u003e toward \u003cb\u003eJerusalem Pl\u003c/b\u003e",
"polyline" : {
"points" : "ciqaG~_upLO?]A"
},
"start_location" : {
"lat" : 42.3644965,
"lng" : -71.05552419999999
},
"travel_mode" : "DRIVING"
}
],
"traffic_speed_entry" : [],
"via_waypoint" : []
}
],
"overview_polyline" : {
"points" : "ciqaG~_upLm#A"
},
"summary" : "Salem St",
"warnings" : [],
"waypoint_order" : []
}
],
"status" : "OK"
}
Decodable Structure: To work with this, I am using Decodable. I have been able to access first level nested data (routes.summary), but I am struggling to get further down (for example: routes.legs.duration). My code structure is as follows:
struct Directions: Decodable {
let status: String
let routes: [Routes]
enum CodingKeys :String, CodingKey {
case status, routes
}
struct Routes: Decodable {
let summary: String
let legs: [Legs]
enum CodingKeys : String, CodingKey {
case summary, legs
}
}
struct Legs: Decodable {
let duration: Duration
enum CodingKeys : String, CodingKey {
case duration
}
}
struct Duration: Decodable {
let text: String
enum CodingKeys : String, CodingKey {
case text
}
}
Implementation after URL set-up:
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let directions = try
JSONDecoder().decode(Directions.self, from: data)
for item in directions.routes {
self.stringoutput = item.summary
}
After all this, all I want to do is be able to access "text" in the JSON and return that value. The last line in the code is able to successfully return "summary" in the JSON; and I can print(directions) and the whole array/dictionary will return in the debug area, including "text". But I still can't figure out how to do:
x = directions.routes.legs.duration.text
to make x equal to "1 min"
Would be appreciative of anyone's help.
Edit: What ended up working is Vadian's struct keys below and the following for in loop:
for item in directions.routes {
print(item.summary)
self.direct = item.summary
for items in item.legs {
self.stringoutput = items.duration.text
print(items.duration.text)
}
Cheers!
These structs don't decode all keys, but it's a starting point.
If keys and struct members have the same name you don't need to specify CodingKeys
struct Directions: Decodable {
let status: String
let routes: [Route]
}
struct Route: Decodable {
let summary: String
let legs: [Leg]
}
struct Leg: Decodable {
let duration : TextValue
let distance : TextValue
let endAddress : String
let endLocation : Location
let startAddress : String
let startLocation : Location
let steps : [Step]
}
struct TextValue: Decodable {
let text: String
let value : Int
}
struct Location: Decodable {
let lat, lng : Double
}
struct Step: Decodable {
let duration : TextValue
let distance : TextValue
let endLocation : Location
let startLocation : Location
let htmlInstructions : String
let travelMode : String
}
To decode the snake_cased keys properly you have to add the appropriate key decoding strategy
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
To access the arrays ([]) you have to get an item by index
step[0]
or iterate the array with a loop
for step in steps {}

Are there different solution how retrieve waypoints?

"-KHbCuQflKHrJiUmfpG0" : {
"waypoints" : {
"-KHbCuQflKHrJiUmfpG1" : {
"latitude" : 13.17078652595298,
"longitude" : -59.5775944578738
},
"-KHbCuQflKHrJiUmfpG2" : {
"latitude" : 13.15541190861343,
"longitude" : -59.57619643155932
},
"-KHbCuQg9W_tebl1pU66" : {
"latitude" : 13.148444967591,
"longitude" : -59.5589266947333
}
},
"subtitle" : "jamesrick",
"title" : "Highway",
"type" : "polyline"
},
I have this structure for lines in Firebase. How retrieve all data with also nested node waypoints?
ref.observeEventType(.Value, withBlock: { polylines in
if let objects = polylines.children.allObjects as? [FDataSnapshot] {
for object in objects {
polylineDictionary = object.values as? Dictionary<String, AnyObjects> {
// ? ? ?
}
}
}
})
Now I have access to title, subtitle, type but how obtain access to waypoints? When I use
`polylineDictionary["waypoints"] as? [String: [String:Double]]`
so this dictionaries are not ordered. Thanks for some advice.
This is how I would get the nested waypoints... it's basically by iterating through the keys... I would also add some error checking when grabbing the longitude and latitudes to make sure they're there, but this is the gist:
if let polylineDictionary["waypoints"] as? [String: [String:Double]] {
let waypoints = Array(polylineDictionary.keys)
for i in 0..<waypoints.count {
let waypointId = waypoints[i]
let latitude = polylineDictionary[waypointId]["latitude"]
let longitutde = polylineDictionary[waypointId]["longitude"]
}
}
It's not clear from the question about ordering; if you just want to get to the waypoints, it's pretty straight forward:
Assume your complete Firebase structure is:
root_node
"-KHbCuQflKHrJiUmfpG0" : {
"waypoints" : {
"-KHbCuQflKHrJiUmfpG1" : {
"latitude" : 13.17078652595298,
"longitude" : -59.5775944578738
},
"-KHbCuQflKHrJiUmfpG2" : {
"latitude" : 13.15541190861343,
"longitude" : -59.57619643155932
},
"-KHbCuQg9W_tebl1pU66" : {
"latitude" : 13.148444967591,
"longitude" : -59.5589266947333
}
},
"subtitle" : "jamesrick",
"title" : "Highway",
"type" : "polyline"
}
Suppose I want the data for this specific node, -KHbCuQflKHrJiUmfpG0
let nodeRef = rootRef.childByAppendingPath("-KHbCuQflKHrJiUmfpG0")
nodeRef.observeSingleEventOfType(.Value , withBlock: { snapshot in
print(snapshot.value) //prints everything in the node
let title = snapshot.value["title"] as! String //to get any element
print(title) //prints Highway
var waypoints = [String: [String:String]]() //dict to hold key:value, unordered
waypoints = snapshot.value["waypoints"] as! Dictionary
print(waypoints) //prints the 3 waypoints and their children (as a dict)
//get fancy and convert the dictionary to an array (which can be sorted)
let arr = waypoints.map {"\($0) \($1)"}
for point in arr {
print(point)
}
}

Swift: dictionaries inside array

Data:
[
{ firstName: "Foo", lastName: "Bar" },
{ firstName: "John", lastName: "Doe" }
]
How can I have this kind of structure using swift array and dictionary? This data shows dictionaries inside an array, right? So I suggest:
var persons:Array = [Dictionary<String, String>()]
but this gives me the error:
Cannot convert the expressions type () to type Array<T>
Any ideas?
The correct way is:
var persons = [Dictionary<String, String>]()
which is equivalent to:
var persons = [[String : String]]()
What your code does instead is to create an array filled in with an instance of Dictionary<String, String>, whereas I presume you want an empty instance of the array containing elements of Dictionary<String, String> type.
Which version of Xcode have you got?
Your code should work fine but the line:
var persons:Array = [Dictionary<String, String>()]
create the array with first empty dictionary, try this instead:
var persons:Array = [Dictionary<String, String>]()
var dic1 = ["Name" : "Jon"]
var dic2 = ["Surname" : "Smith"]
persons.append(dic1)
persons.append(dic2)
println(persons)
Are you sure you really want a dictionary within an array? The code you've given indicates more an array with named columns, which can be achieved using something like the following:
struct Name {
var firstName : String
var lastName : String
}
var persons1 : Array<Name> = [
Name(firstName: "Foo", lastName: "Bar"),
Name(firstName: "John", lastName: "Doe")
]
persons1[0].firstName // "Foo"
var persons2 : Array<(firstName: String, lastName:String)> = [
(firstName: "Mary", lastName: "Mean"),
(firstName: "Foo", lastName: "Bar"),
(firstName: "John", lastName: "Doe")
]
persons2[1].firstName // "Bar"
These are proper arrays and adressed as such using subscripts. The dictionary type is usually a combination of key and value, i.e. nickname as key, and name as value.
var nickNames : [String:String] = [
"mame" : "Mary Mean",
"foba" : "Foo Bar",
"jodo" : "John Doe"]
nickNames["mame"]! // "Mary Mean"
And here we lookup on the key value, and get an optional value in return, which I forcefully unwrapped...
All of these can be appended rather easily, but do note that the named tuple variant, persons2, is not following recommended practice. Also note that the Array of Dictionaries allow for inclusion on different keys as suggested in my last injection.
persons1.append( Name(firstName: "Some", lastName: "Guy") )
persons2.append( firstName: "Another", lastName: "Girl" )
nickNames["anna"] = "Ann Nabel"
// Array of Dictionaries
var persons : [[String:String]] = [
[ "firstName" : "Firstly", "lastName" : "Lastly"],
[ "firstName" : "Donald", "lastName" : "Duck"]
]
persons.append( ["firstName" : "Georg", "middleName" : "Friedrich", "lastName" : "Händel"] )
something like this can work for you:
var persons: Array<Dictionary<String, String>> = Array()
and now you can add the names:
persons.append(["firstName": "Foo", "lastName": "Bar"]);
persons.append(["firstName": "John", "lastName": "Doo"]);
NOTE: if you are insecure how to use literals, just don't use them.