Swift Decoder, trying to decode file with layers - swift

How would I decode this json file with JSONDecoder().decode
{
"text": "pear",
"parsed": [
{
"food": {
"foodId": "food_bq6stkiaxkwhxia9q4v7wanjnew0",
"label": "Pear",
"nutrients": {
"ENERC_KCAL": 57,
"PROCNT": 0.36,
"FAT": 0.14,
"CHOCDF": 15.23,
"FIBTG": 3.1
},
"category": "Generic foods",
"categoryLabel": "food",
"image": "https://www.edamam.com/food-img/65a/65aec51d264db28bbe27117c9fdaaca7.jpg"
}
}
],
"hints": [
{
"food": {
"foodId": "food_bq6stkiaxkwhxia9q4v7wanjnew0",
"label": "Pear",
"nutrients": {
"ENERC_KCAL": 57,
"PROCNT": 0.36,
"FAT": 0.14,
"CHOCDF": 15.23,
"FIBTG": 3.1
},
"category": "Generic foods",
"categoryLabel": "food",
"image": "https://www.edamam.com/food-img/65a/65aec51d264db28bbe27117c9fdaaca7.jpg"
},
"measures": [
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_unit",
"label": "Whole",
"qualified": [
{
"qualifiers": [
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Qualifier_large",
"label": "large"
}
]
},
{
"qualifiers": [
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Qualifier_small",
"label": "small"
}
]
},
{
"qualifiers": [
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Qualifier_medium",
"label": "medium"
}
]
}
]
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_serving",
"label": "Serving"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_half",
"label": "Half"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_slice",
"label": "Slice"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_gram",
"label": "Gram"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_ounce",
"label": "Ounce"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_pound",
"label": "Pound"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_kilogram",
"label": "Kilogram"
},
{
"uri": "http://www.edamam.com/ontologies/edamam.owl#Measure_cup",
"label": "Cup"
}
The file goes on and on.
I try to do [String] but it says it expects a dictionary,
So I put in a dictionary, then it says it expects another dictionary,
This repeats until I have this:
let posts = try! JSONDecoder().decode([String: [String: [String: String]]].self, from: data!)
Then I get the error
"Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))
Though when I go back one to do a string/data I get this error
"Expected to decode String but found a dictionary instead.", underlyingError: nil))
Help?

As mentioned in the comments you can use a quick parser, however
you need to understand why is this happening, the issue here is that you're passing [String: [String: [String: String]]] which is not valid skeleton for this key you can start breaking down each layer alone to understand first level for example [String, Any] and then start exporting each key and decode that stand alone, using the above link your models should be something like that.
// MARK: - Foo
struct Foo: Codable {
let text: String
let parsed: [Parsed]
let hints: [Hint]
}
// MARK: - Hint
struct Hint: Codable {
let food: Food
let measures: [Measure]
}
// MARK: - Food
struct Food: Codable {
let foodID, label: String
let nutrients: Nutrients
let category, categoryLabel: String
let image: String
enum CodingKeys: String, CodingKey {
case foodID = "foodId"
case label, nutrients, category, categoryLabel, image
}
}
// MARK: - Nutrients
struct Nutrients: Codable {
let enercKcal: Int
let procnt, fat, chocdf, fibtg: Double
enum CodingKeys: String, CodingKey {
case enercKcal = "ENERC_KCAL"
case procnt = "PROCNT"
case fat = "FAT"
case chocdf = "CHOCDF"
case fibtg = "FIBTG"
}
}
// MARK: - Measure
struct Measure: Codable {
let uri: String
let label: String
let qualified: [Qualified]?
}
// MARK: - Qualified
struct Qualified: Codable {
let qualifiers: [Qualifier]
}
// MARK: - Qualifier
struct Qualifier: Codable {
let uri: String
let label: String
}
// MARK: - Parsed
struct Parsed: Codable {
let food: Food
}
And you can use it simply like this:
let foo = try? newJSONDecoder().decode(Foo.self, from: jsonData)

Related

Alamofire JSON parsing of dictionary of arrays

Trying to parse this dictionary of Arrays.
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere#april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna#melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
}
]
Is it possible to parse this Dictionary of arrays into an array of Model ([Artist]) for convenience.
AF.request(Constants.kGetArtistsAPIEndPoint).responseDecodable(of: [ArtistsResponse].self) { (response) in
guard let artistsResponse = response.value else { return }
print("ArtistsResponse is \(artistsResponse)")
if let artists = artistsResponse.first {
completion(artists)
}
}
My Artist class looks like below
struct ArtistsResponse: Codable {
let artists: [Artist]
}
struct Artist: Codable {
let artistId: String
let name: String
let username: String?
let emailId: String?
let address: Address?
let phone: String?
let website: String?
let company: Company?
enum CodingKeys: String, CodingKey {
case artistId = "id"
case name
case username
case emailId
case address
case phone
case website
case company
}
}
struct Address: Codable {
let street: String
let suite: String?
let city: String
let zipcode: String
let geo: GeoLocation
enum CodingKeys: String, CodingKey {
case street
case suite
case city
case zipcode
case geo
}
}
struct GeoLocation: Codable {
let latitude: Double
let longitude: Double
enum CodingKeys: String, CodingKey {
case latitude = "lat"
case longitude = "lng"
}
}
Tried with above code but it fails with below error code
Alamofire.AFError.responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))))

Get special values from [AnyHashable] to other [AnyHashable]

I have data which type [AnyHashable] given in below;
"options": [
{
"index": 0,
"label": "Choice 1"
},
{
"index": 1,
"label": "Choice 2"
},
{
"index": 2,
"label": "Choice 3"
}
],
I want to get all "label" values an the other [AnyHashable] . I cant find the best way. How can I do it?
You can define related models and decode JSON to your model like this:
let jsonString = """
{
"options": [
{
"index": 0,
"label": "Choice 1"
},
{
"index": 1,
"label": "Choice 2"
},
{
"index": 2,
"label": "Choice 3"
}
]
}
"""
struct Option: Codable {
let index: Int
let label: String
}
struct Model: Codable {
let options: [Option]
}
let data = jsonString.data(using: .utf8)!
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: data)
let labels = model.options.map(\.label)
print(labels)
//["Choice 1", "Choice 2", "Choice 3"]
Thanks for your useful recommendation. I think it is some weirds but works.
func options(for question: [String : Any?]) -> [AnyHashable] {
var qoptions: [String] = []
for val in question["options"] as! [[String:Any]] {
let value = val["label"] as! String
qoptions.append(value)
}
return qoptions as! [AnyHashable]
}

SwiftyJSON YouTube API

I am using SwiftyJSON to retrieve data from the youtube api.
I am writing swift 4 code.
I'm trying to print out the description
Here is what I have:
func getFeedVideos() {
Alamofire.request(API_URL, method: .get, parameters: ["part":"snippet", "playlistId":PLAYLIST_ID,"key":API_KEY], encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
if let value = response.result.value {
let json = JSON(value)
print(json["items"]["snippet"]["description"].stringValue)
}
}
}
But no description is being printed out.
Below is the youtube API:
"items" : [
{
"kind": "youtube#playlistItem",
"etag": etag,
"id": string,
"snippet": {
"publishedAt": datetime,
"channelId": string,
"title": string,
"description": string,
"thumbnails": {
(key): {
"url": string,
"width": unsigned integer,
"height": unsigned integer
}
},
"channelTitle": string,
"playlistId": string,
"position": unsigned integer,
"resourceId": {
"kind": string,
"videoId": string,
}
},
}
If you look closely at the first line of the response, you'll notice the following:
"items" : [
The [ indicates items is an array, which means you should try
items[0]["snippet"]["description"].stringValue

restkit, how to access object in response without object mapping

How can I access the original response json data without using object mapping. I have the followingresponse data. it contains a nextSyncToken which used to execute the query and a collection of items (within the items session).
I created a object mapping of the Item object which represent the contents in items. However, I also need the nextSyncToken field. How can I access it without object mapping. Since the syncToken has no relationship with object mapping. How can i deal with this.
{
"kind": "calendar#events",
"nextSyncToken": "COib8eSw78gCEOib8eSw78gCGAU=",
"items": [
{
"id": "_74rk4cpg84o42b9k8or3gb9k74s34b9p8ks34b9m851kac9m64rk4ci36g",
"created": "2010-04-16T11:09:31.000Z",
"updated": "2010-04-16T11:10:27.487Z",
"summary": "iCal test 1",
"start": {
"dateTime": "2010-03-16T21:00:00+08:00"
},
"end": {
"dateTime": "2010-03-16T22:00:00+08:00"
}
},
{
"id": "_752j2h1j6cq4cba68csjeb9k8p33eba1692k4ba284qj8ea688rj2chh6c",
"status": "confirmed",
"created": "2011-10-18T09:36:02.000Z",
"updated": "2011-10-18T09:36:02.000Z",
"summary": "New Event",
"start": {
"dateTime": "2011-10-18T03:45:00+08:00"
},
"end": {
"dateTime": "2011-10-18T08:15:00+08:00"
}
}
]
}
My code of mapping:
let eventMapping = RKEntityMapping(forEntityForName: "SGEvent", inManagedObjectStore: managedObjectStore)
eventMapping.addAttributeMappingsFromDictionary([
"id": "identifier",
"summary": "summary",
"created": "createdAt",
"updated": "updatedAt",
"location": "location",
"description": "notes",
"start.date": "allDayStart",
"end.date": "allDayEnd"
])
let startTimeMapping = RKAttributeMapping(fromKeyPath: "start.dateTime", toKeyPath: "startTime")
startTimeMapping.valueTransformer = self.googleDateTransformer
eventMapping.addPropertyMapping(startTimeMapping)
let endTimeMapping = RKAttributeMapping(fromKeyPath: "end.dateTime", toKeyPath: "endTime")
endTimeMapping.valueTransformer = self.googleDateTransformer
eventMapping.addPropertyMapping(endTimeMapping)
eventMapping.identificationAttributes = ["identifier"]
let responseDescriptor = RKResponseDescriptor(mapping: eventMapping, method: .GET,
pathPattern: "calendars/:calendarId/events", keyPath: "items",
statusCodes: RKStatusCodeIndexSetForClass(RKStatusCodeClass.Successful))
objectManager.addResponseDescriptor(responseDescriptor)
My request operation:
objectManager.getObjectsAtPath("calendars/\(identifier)/events",
parameters: [self.ACCESS_TOKEN: accessToken], success: { (operation, results) -> Void in
callback?(results: nil, error: nil)
}) { (_, error) -> Void in
print(error)
}
Generally you would add other response descriptors with appropriate mappings to deal with this issue.
When using objectManager.getObjectsAtPath you can get the raw data, assuming that you have some other response descriptor which will result in the success block being called, by navigating to the response data in the HTTP operation (which you can then unpack however you see fit):
operation.HTTPRequestOperation.responseData
(or use responseString instead of responseData).
if let dict = try? NSJSONSerialization.JSONObjectWithData(operation.HTTPRequestOperation.responseData, options: .AllowFragments) as? [String: AnyObject],
let nextSyncToken = dict?["nextSyncToken"] as? String{
print(nextSyncToken)//get the nextSyncToken
}

Swift, ObjectMapper: path for nested array

I have json from http://openweathermap.org/, and it looks like this:
{
"coord": {
"lon": 4.85,
"lat": 45.75
},
"weather": [
{
"id": 803,
"main": "Clouds",
"description": "broken clouds",
"icon": "04d"
}
],
"base": "cmc stations",
"main": {
"temp": 278.988,
"pressure": 985.88,
"humidity": 92,
"temp_min": 278.988,
"temp_max": 278.988,
"sea_level": 1032.68,
"grnd_level": 985.88
},
"wind": {
"speed": 1.8,
"deg": 355
},
"clouds": {
"all": 80
},
"dt": 1445249394,
"sys": {
"message": 0.0037,
"country": "FR",
"sunrise": 1445234548,
"sunset": 1445273273
},
"id": 2996944,
"name": "Lyon",
"cod": 200
}
I'm using Alamofire 3.0 for networking, ObjectMapper for mapping json to model, and AlamofireObjectMapper extension to get model objects from request instead of json.
Now I need to get weather description, but don't know how to write path for it. Tried ["weather.0.description"], ["weather.$0.description"], but these are not working, and my weather description is nil.
Here is my model:
class WCurrentWeather: Mappable {
var weatherDescription: String?
var tempriture: Double?
var clouds: Double?
var rain: Double?
var humidity: Double?
var pressure: Double?
var sunrise: NSDate?
var sunset: NSDate?
required init?(_ map: Map){
}
func mapping(map: Map) {
weatherDescription <- map["weather.0.description"]
tempriture <- map["main.temp"]
clouds <- map["clouds.all"]
rain <- map["rain.1h"]
humidity <- map["main.humidity"]
pressure <- map["main.pressure"]
sunrise <- (map["sys.sunrise"], DateTransform())
sunset <- (map["sys.sunset"], DateTransform())
}
}
and my request:
Alamofire.request(.GET, URL, parameters: params)
.responseObject { (response: WCurrentWeather?, error: ErrorType?) in
completionHandler(response, error)
}
Is there any way to get this working.
Thanks in advance.
I have forked ObjectMapper, and added this functionality, and thanks to Tristan Himmelman it is already merged, so now you can access to nested array elements like this map["weather.0.description"]
What you are looking for is:
let jsonDict = // You dict
jsonDict["weather"][0]["description"]
I am just giving you a direction. You would need to align it with Swift type casting rules. Good luck!