How can I decode two different response JSON in one request? - json

I am trying to get response from Google's Book API. In there I can get response but for example, in some books have description or many other keys in response JSON. If I desing my structs according to with descriptions key, other books which not have description gave me dataCorrupted error and not decoding JSON. If I do otherwise, I get the same error on other books which have description. I hope I can explain myself. How can I solve it correctly?
Here my root class codable:
import Foundation
struct RootClass : Codable {
let items : [Item]?
let kind : String?
let totalItems : Int?
enum CodingKeys: String, CodingKey {
case items = "items"
case kind = "kind"
case totalItems = "totalItems"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
items = try values.decodeIfPresent([Item].self, forKey: .items)
kind = try values.decodeIfPresent(String.self, forKey: .kind)
totalItems = try values.decodeIfPresent(Int.self, forKey: .totalItems)
}
}
I need to reach root -> items -> volumeInfo.
And here Item codable struct:
import Foundation
struct Item : Codable {
let accessInfo : AccessInfo?
let etag : String?
let id : String?
let kind : String?
let saleInfo : SaleInfo?
let searchInfo : SearchInfo?
let selfLink : String?
let volumeInfo : VolumeInfo?
enum CodingKeys: String, CodingKey {
case accessInfo = "accessInfo"
case etag = "etag"
case id = "id"
case kind = "kind"
case saleInfo = "saleInfo"
case searchInfo = "searchInfo"
case selfLink = "selfLink"
case volumeInfo = "volumeInfo"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
accessInfo = try AccessInfo(from: decoder)
etag = try values.decodeIfPresent(String.self, forKey: .etag)
id = try values.decodeIfPresent(String.self, forKey: .id)
kind = try values.decodeIfPresent(String.self, forKey: .kind)
saleInfo = try SaleInfo(from: decoder)
searchInfo = try SearchInfo(from: decoder)
selfLink = try values.decodeIfPresent(String.self, forKey: .selfLink)
volumeInfo = try VolumeInfo(from: decoder)
}
}
And Here I parse json :
func parseJSON(searchData: Data) -> [SearchedModel]?{
let decode = JSONDecoder()
do{
let decodedData = try decode.decode(RootClass.self, from: searchData)
// IN HERE I CAN USE FOR LOOP TO ITEMS AND GET VOLUME INFO DATAS BUT ITS ALL NIL
return mainDataArr
}catch{
print(error)
return nil
}
}
When I try to loop over to items and get volume info datas its all nil. I can't understand why is this all nil? How can I solve it?
Here part of JSON 1 :
"kind": "books#volumes",
"totalItems": 1196,
"items": [
{
"kind": "books#volume",
"id": "4qnmsgEACAAJ",
"etag": "dYXW8bT6JB0",
"selfLink": "https://www.googleapis.com/books/v1/volumes/4qnmsgEACAAJ",
"volumeInfo": {
"title": "Sineklerin Tanrisi",
"authors": [
"William Golding"
],
"publishedDate": "2014-01-01",
"industryIdentifiers": [
{
"type": "ISBN_10",
"identifier": "9754582904"
},
{
"type": "ISBN_13",
"identifier": "9789754582901"
}
],
"readingModes": {
"text": false,
"image": false
},
"pageCount": 261,
"printType": "BOOK",
"categories": [
"Boys"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "preview-1.0.0",
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=4qnmsgEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=4qnmsgEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
},
"language": "tr",
"previewLink": "http://books.google.com.tr/books?id=4qnmsgEACAAJ&dq=sineklerin+tanrisi&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.com.tr/books?id=4qnmsgEACAAJ&dq=sineklerin+tanrisi&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/Sineklerin_Tanrisi.html?hl=&id=4qnmsgEACAAJ"
},
"saleInfo": {
"country": "TR",
"saleability": "NOT_FOR_SALE",
"isEbook": false
},
"accessInfo": {
"country": "TR",
"viewability": "NO_PAGES",
"embeddable": false,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": false
},
"pdf": {
"isAvailable": false
},
"webReaderLink": "http://play.google.com/books/reader?id=4qnmsgEACAAJ&hl=&printsec=frontcover&source=gbs_api",
"accessViewStatus": "NONE",
"quoteSharingAllowed": false
}
}
And here JSON 2 (with same request's response):
"kind": "books#volumes",
"totalItems": 432,
"items": [
{
"kind": "books#volume",
"id": "yodWha1LmPsC",
"etag": "2bZz2CDqiq4",
"selfLink": "https://www.googleapis.com/books/v1/volumes/yodWha1LmPsC",
"volumeInfo": {
"title": "Momo the Monkey Arrives",
"authors": [
"Shariffa Keshavjee"
],
"publisher": "Master Publishing",
"publishedDate": "2012-09-21",
"description": "Momo the Monkey Arrives is the first in a series of illustrated children's books about the rescue of a monkey and how his presence livens up the household of two children: a boy and a girl. Along the way, as they take care of Momo, Geno and Alid learn how to give the best possible care to a monkey like him, and how to be more responsible children. The adventures of Momo are based on a true story.",
"industryIdentifiers": [
{
"type": "ISBN_13",
"identifier": "9789966158987"
},
{
"type": "ISBN_10",
"identifier": "9966158987"
}
],
"readingModes": {
"text": true,
"image": true
},
"pageCount": 28,
"printType": "BOOK",
"categories": [
"Juvenile Fiction"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "1.4.4.0.preview.3",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=yodWha1LmPsC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=yodWha1LmPsC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.com.tr/books?id=yodWha1LmPsC&pg=PT25&dq=momo&hl=&cd=1&source=gbs_api",
"infoLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC&source=gbs_api",
"canonicalVolumeLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC"
},
"saleInfo": {
"country": "TR",
"saleability": "FOR_SALE",
"isEbook": true,
"listPrice": {
"amount": 35.04,
"currencyCode": "TRY"
},
"retailPrice": {
"amount": 35.04,
"currencyCode": "TRY"
},
"buyLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC&rdid=book-yodWha1LmPsC&rdot=1&source=gbs_api",
"offers": [
{
"finskyOfferType": 1,
"listPrice": {
"amountInMicros": 35040000,
"currencyCode": "TRY"
},
"retailPrice": {
"amountInMicros": 35040000,
"currencyCode": "TRY"
}
}
]
},
"accessInfo": {
"country": "TR",
"viewability": "PARTIAL",
"embeddable": true,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": true
},
"pdf": {
"isAvailable": true
},
"webReaderLink": "http://play.google.com/books/reader?id=yodWha1LmPsC&hl=&printsec=frontcover&source=gbs_api",
"accessViewStatus": "SAMPLE",
"quoteSharingAllowed": false
},
"searchInfo": {
"textSnippet": "<b>Momo</b> the Monkey Adventure Series <b>Momo</b> the Monkey Arrives <b>Momo</b> Makes a <br>\nMess <b>Momo</b> Comes to the Rescue <b>Momo</b> Moves to the Orphanage Look out for <br>\nthese stories about <b>Momo</b> and his friends. More coming soon! Master Publishing<br>\n ..."
}
}

The "data corrupted" error seems to be due to the presence of the \n in the textSnippet. If we take care of that manually, I can't reproduce any issue. Here is a complete (unlike your code) example that compiles and runs correctly in an iOS project, using the JSON you provided (patched up to be valid JSON):
let json1 = """
{
"kind": "books#volumes",
"totalItems": 1196,
"items": [
{
"kind": "books#volume",
"id": "4qnmsgEACAAJ",
"etag": "dYXW8bT6JB0",
"selfLink": "https://www.googleapis.com/books/v1/volumes/4qnmsgEACAAJ",
"volumeInfo": {
"title": "Sineklerin Tanrisi",
"authors": [
"William Golding"
],
"publishedDate": "2014-01-01",
"industryIdentifiers": [
{
"type": "ISBN_10",
"identifier": "9754582904"
},
{
"type": "ISBN_13",
"identifier": "9789754582901"
}
],
"readingModes": {
"text": false,
"image": false
},
"pageCount": 261,
"printType": "BOOK",
"categories": [
"Boys"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "preview-1.0.0",
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=4qnmsgEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=4qnmsgEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
},
"language": "tr",
"previewLink": "http://books.google.com.tr/books?id=4qnmsgEACAAJ&dq=sineklerin+tanrisi&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.com.tr/books?id=4qnmsgEACAAJ&dq=sineklerin+tanrisi&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/Sineklerin_Tanrisi.html?hl=&id=4qnmsgEACAAJ"
},
"saleInfo": {
"country": "TR",
"saleability": "NOT_FOR_SALE",
"isEbook": false
},
"accessInfo": {
"country": "TR",
"viewability": "NO_PAGES",
"embeddable": false,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": false
},
"pdf": {
"isAvailable": false
},
"webReaderLink": "http://play.google.com/books/reader?id=4qnmsgEACAAJ&hl=&printsec=frontcover&source=gbs_api",
"accessViewStatus": "NONE",
"quoteSharingAllowed": false
}
}
]
}
"""
let json2 = """
{
"kind": "books#volumes",
"totalItems": 432,
"items": [
{
"kind": "books#volume",
"id": "yodWha1LmPsC",
"etag": "2bZz2CDqiq4",
"selfLink": "https://www.googleapis.com/books/v1/volumes/yodWha1LmPsC",
"volumeInfo": {
"title": "Momo the Monkey Arrives",
"authors": [
"Shariffa Keshavjee"
],
"publisher": "Master Publishing",
"publishedDate": "2012-09-21",
"description": "Momo the Monkey Arrives is the first in a series of illustrated children's books about the rescue of a monkey and how his presence livens up the household of two children: a boy and a girl. Along the way, as they take care of Momo, Geno and Alid learn how to give the best possible care to a monkey like him, and how to be more responsible children. The adventures of Momo are based on a true story.",
"industryIdentifiers": [
{
"type": "ISBN_13",
"identifier": "9789966158987"
},
{
"type": "ISBN_10",
"identifier": "9966158987"
}
],
"readingModes": {
"text": true,
"image": true
},
"pageCount": 28,
"printType": "BOOK",
"categories": [
"Juvenile Fiction"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "1.4.4.0.preview.3",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=yodWha1LmPsC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=yodWha1LmPsC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.com.tr/books?id=yodWha1LmPsC&pg=PT25&dq=momo&hl=&cd=1&source=gbs_api",
"infoLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC&source=gbs_api",
"canonicalVolumeLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC"
},
"saleInfo": {
"country": "TR",
"saleability": "FOR_SALE",
"isEbook": true,
"listPrice": {
"amount": 35.04,
"currencyCode": "TRY"
},
"retailPrice": {
"amount": 35.04,
"currencyCode": "TRY"
},
"buyLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC&rdid=book-yodWha1LmPsC&rdot=1&source=gbs_api",
"offers": [
{
"finskyOfferType": 1,
"listPrice": {
"amountInMicros": 35040000,
"currencyCode": "TRY"
},
"retailPrice": {
"amountInMicros": 35040000,
"currencyCode": "TRY"
}
}
]
},
"accessInfo": {
"country": "TR",
"viewability": "PARTIAL",
"embeddable": true,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": true
},
"pdf": {
"isAvailable": true
},
"webReaderLink": "http://play.google.com/books/reader?id=yodWha1LmPsC&hl=&printsec=frontcover&source=gbs_api",
"accessViewStatus": "SAMPLE",
"quoteSharingAllowed": false
},
"searchInfo": {
"textSnippet": "<b>Momo</b> the Monkey Adventure Series <b>Momo</b> the Monkey Arrives <b>Momo</b> Makes a <br>\\nMess <b>Momo</b> Comes to the Rescue <b>Momo</b> Moves to the Orphanage Look out for <br>\\nthese stories about <b>Momo</b> and his friends. More coming soon! Master Publishing<br>\\n ..."
}
}
]
}
"""
struct Base : Codable {
let kind : String
let totalItems : Int
let items : [Items]
}
struct Items : Codable {
let kind : String
let id : String
let etag : String
let selfLink : String
let volumeInfo : VolumeInfo
let saleInfo : SaleInfo?
let accessInfo : AccessInfo
let searchInfo : SearchInfo?
}
struct SaleInfo : Codable {
let country : String
let saleability : String
let isEbook : Bool
let listPrice : ListPrice?
let retailPrice : RetailPrice?
let buyLink : String?
let offers : [Offers]?
}
struct Offers : Codable {
let finskyOfferType : Int
let listPrice : ListPrice
let retailPrice : RetailPrice
}
struct ListPrice : Codable {
let amountInMicros : Int?
let currencyCode : String
}
struct ReadingModes : Codable {
let text : Bool
let image : Bool
}
struct Epub : Codable {
let isAvailable : Bool
}
struct Pdf : Codable {
let isAvailable : Bool
}
struct RetailPrice : Codable {
let amountInMicros : Int?
let currencyCode : String
}
struct SearchInfo : Codable {
let textSnippet : String
}
struct ImageLinks : Codable {
let smallThumbnail : String
let thumbnail : String
}
struct IndustryIdentifiers : Codable {
let type : String
let identifier : String
}
struct PanelizationSummary : Codable {
let containsEpubBubbles : Bool
let containsImageBubbles : Bool
}
struct VolumeInfo : Codable {
let title : String
let authors : [String]
let publisher : String?
let publishedDate : String
let description : String?
let industryIdentifiers : [IndustryIdentifiers]
let readingModes : ReadingModes
let pageCount : Int
let printType : String
let categories : [String]
let maturityRating : String
let allowAnonLogging : Bool
let contentVersion : String
let panelizationSummary : PanelizationSummary?
let imageLinks : ImageLinks
let language : String
let previewLink : String
let infoLink : String
let canonicalVolumeLink : String
}
struct AccessInfo : Codable {
let country : String
let viewability : String
let embeddable : Bool
let publicDomain : Bool
let textToSpeechPermission : String
let epub : Epub
let pdf : Pdf
let webReaderLink : String
let accessViewStatus : String
let quoteSharingAllowed : Bool
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let data1 = json1.data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Base.self, from: data1)
print("success1")
print(result)
} catch {
print(error)
}
let data2 = json2.data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Base.self, from: data2)
print("success2")
print(result)
} catch {
print(error)
}
}
}

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

Type 'AreaData' does not conform to protocol 'Encodable'

When I make a model for my JSON file, I get these two errors.
1) I got the conform error in AreaData struct
2) "AnyObject cannot be used as a type conforming to protocol Decodable because Decodable has static requirements" error in initialiser.
I have tried several ways but I cannot find the correct solution. How can I make a proper model for this nested/complex JSON?
Here is my JSON file. This data is nested
[
{
"ID": "01",
"Name": "Area 01",
"parentId": null,
"sublevel": [
{
"ID": "01-01",
"Name": "Building 01",
"On": "",
"Off": "",
"parentId": "01",
"sublevel": [
{
"ID": "01-01-01",
"Name": "Flat 01",
"On": "",
"Off": "",
"parentId": "01-01",
"sublevel": []
}
]
},
{
"ID": "01-02",
"Name": "Building 02",
"On": "01",
"Off": "03",
"parentId": "01",
"sublevel": [
{
"ID": "01-02-01",
"Name": "Flat 01",
"On": "",
"Off": "",
"parentId": "01-02",
"sublevel": []
},
{
"ID": "01-02-02",
"Name": "Flat 02",
"On": "01",
"Off": "02",
"parentId": "01-02",
"sublevel": []
},
{
"ID": "01-02-03",
"Name": "Flat 03",
"On": "02",
"Off": "12",
"parentId": "01-02",
"sublevel": [
{
"ID": "01-02-03-01",
"Name": "Room 01",
"On": "",
"Off": "",
"parentId": "01-02-03",
"sublevel": []
},
{
"ID": "01-02-03-02",
"Name": "Room 02",
"On": "",
"Off": "",
"parentId": "01-02-03",
"sublevel": []
},
{
"ID": "01-02-03-03",
"Name": "Room 03",
"On": "02",
"Off": "03",
"parentId": "01-02-03",
"sublevel": []
},
{
"ID": "01-02-03-04",
"Name": "Room 04",
"On": "",
"Off": "",
"parentId": "01-02-03",
"sublevel": []
},
{
"ID": "01-02-03-05",
"Name": "Room 05",
"On": "01",
"Off": "",
"parentId": "01-02-03",
"sublevel": []
}
]
},
{
"ID": "01-02-04",
"Name": "Flat 04",
"On": "12",
"Off": "03",
"parentId": "01-02",
"sublevel": []
},
{
"ID": "01-02-05",
"Name": "Flat 05",
"On": "02",
"Off": "",
"parentId": "01-02",
"sublevel": []
}
]
},
{
"ID": "01-03",
"Name": "Building 03",
"On": "02",
"Off": "01",
"parentId": "01",
"sublevel": []
},
{
"ID": "01-04",
"Name": "Building 04",
"On": "",
"Off": "",
"parentId": "01",
"sublevel": []
}
]
}
]
And this is my model class
import Foundation
struct AreaData : Codable {
let iD : String?
let name : String?
let parentId : AnyObject?
let sublevel : [Sublevel]?
enum CodingKeys: String, CodingKey {
case iD = "ID"
case name = "Name"
case parentId = "parentId"
case sublevel = "sublevel"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
iD = try values.decodeIfPresent(String.self, forKey: .iD)
name = try values.decodeIfPresent(String.self, forKey: .name)
parentId = try values.decodeIfPresent(AnyObject.self, forKey: .parentId)
sublevel = try values.decodeIfPresent([Sublevel].self, forKey: .sublevel)
}
}
struct Sublevel : Codable {
let on : String?
let iD : String?
let name : String?
let off : String?
let parentId : String?
let sublevel : [Sublevel]?
enum CodingKeys: String, CodingKey {
case on = "On"
case iD = "ID"
case name = "Name"
case off = "Off"
case parentId = "parentId"
case sublevel = "sublevel"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
on = try values.decodeIfPresent(String.self, forKey: .on)
iD = try values.decodeIfPresent(String.self, forKey: .iD)
name = try values.decodeIfPresent(String.self, forKey: .name)
off = try values.decodeIfPresent(String.self, forKey: .off)
parentId = try values.decodeIfPresent(String.self, forKey: .parentId)
sublevel = try values.decodeIfPresent([Sublevel].self, forKey: .sublevel)
}
}
try this,
struct AreaDataModel: Codable {
let id, name: String
let parentID: String? // AnyObject can't conform to Encodable protocol .
let sublevel: [Sublevel]
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case parentID = "parentId"
case sublevel
}
}
// MARK: - Sublevel
struct Sublevel: Codable {
let id, name, on, off: String
let parentID: String
let sublevel: [Sublevel]
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case on = "On"
case off = "Off"
case parentID = "parentId"
case sublevel
}
}
typealias AreaData = [AreaDataModel]
I recommend this tool simple and fast .
The problem is due to AnyObject. Codabe doesn't support anything like Any or AnyObject. You need to specify the type explicitly.
In the JSON response you added, the parentId is either null or String. So, you can use String? as its type, i.e
struct AreaData : Codable {
let parentId : String? //here.....
//rest of the code....
}
Also, there is no need to specify the rawValue of the case explitly in enum CodingKeys if the property and the key has an exact match. So the CodingKeys in struct AreaData must be,
enum CodingKeys: String, CodingKey {
case iD = "ID"
case name = "Name"
case parentId, sublevel
}
Moreover, init(from decoder: Decoder) is not required. This is because you're not doing any specific parsing inside it. Direct parsing will be handled by the Codable itself.
So, struct AreaData should look like,
struct AreaData : Codable {
let iD : String?
let name : String?
let parentId : String?
let sublevel : [Sublevel]?
enum CodingKeys: String, CodingKey {
case iD = "ID"
case name = "Name"
case parentId, sublevel
}
}
Make similar changes to struct Sublevel as well.
Also, use Codable only if you want to both encode and decode the data. In case you want a single functionality i.e. either encode or decode, use Encodable or Decodable instead.
Recommendation:
Since the AreaData and Sublevel contain almost same kind of data, you can use a single struct to decode that JSON, i.e.
struct AreaData: Decodable {
let iD : String?
let name : String?
let parentId : String?
let sublevel : [AreaData]?
let on : String?
let off : String?
enum CodingKeys: String, CodingKey {
case iD = "ID"
case name = "Name"
case on = "On"
case off = "Off"
case parentId, sublevel
}
}

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

How to create Struct for more then one json table in swift xcode

I am writing an IOS Application that need to read a JSON FIle.
I understood the best way to do that is to write a struct for that json file and parse the json into that struct to be able to use freely.
I have a Json file that is saved locally in one of the folders
{
"colors": [
{
"color": "black",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,255,1],
"hex": "#000"
}
},
{
"color": "white",
"category": "value",
"code": {
"rgba": [0,0,0,1],
"hex": "#FFF"
}
},
{
"color": "red",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,0,0,1],
"hex": "#FF0"
}
},
{
"color": "blue",
"category": "hue",
"type": "primary",
"code": {
"rgba": [0,0,255,1],
"hex": "#00F"
}
},
{
"color": "yellow",
"category": "hue",
"type": "primary",
"code": {
"rgba": [255,255,0,1],
"hex": "#FF0"
}
},
{
"color": "green",
"category": "hue",
"type": "secondary",
"code": {
"rgba": [0,255,0,1],
"hex": "#0F0"
}
},
],
"people": [
{
"first_name": "john",
"is_valid": true,
"friends_list": {
"friend_names": ["black", "hub", "good"],
"age": 13
}
},
{
"first_name": "michal",
"is_valid": true,
"friends_list": {
"friend_names": ["jessy", "lyn", "good"],
"age": 19
}
},
{
"first_name": "sandy",
"is_valid": false,
"friends_list": {
"friend_names": ["brown", "hub", "good"],
"age": 15
}
},
]
}
i created a struct for each one of the two tables:
import Foundation
struct Color {
var color: String
var category: String
var type: String
var code: [JsonCodeStruct]
}
struct Poeople {
var firsName: String
var is_valid: Bool
var friendsNames: [JsonFriendNames]
}
struct JsonFriendNames {
var friendNames: [String]
var age: String
}
struct JsonCodeStruct {
var rgba: [Double]
var hex: String
}
and I want to open the local json file
and assign it the structs that I gave and then read them easily in the code.
can you suggest me a way on how to do that?
First of all you need an umbrella struct to decode the colors and people keys
struct Root: Decodable {
let colors: [Color]
let people : [Person]
}
The types in your structs are partially wrong. The Color related structs are
struct Color: Decodable {
let color: String
let category: String
let type: String?
let code : ColorCode
}
struct ColorCode: Decodable {
let rgba : [UInt8]
let hex : String
}
and the Person related structs are
struct Person: Decodable {
let firstName : String
let isValid : Bool
let friendsList : Friends
}
struct Friends: Decodable {
let friendNames : [String]
let age : Int
}
Assuming you read the file with
let data = try Data(contentsOf: URL(fileURLWithPath:"/...."))
you can decode the JSON into the given structs with
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let result = try decoder.decode(Root.self, from: data)
print(result)
} catch { print(error) }

How to parse complex JSON in Swift 4 using Codable

I need help with parsing JSON from server. Here's the JSON:
{
"response": {
"items": [
{
"type": "post",
"source_id": -17507435,
"date": 1514538602,
"post_id": 4105,
"post_type": "post",
"text": "Some text here",
"marked_as_ads": 0,
"attachments": [
{
"type": "photo",
"photo": {
"id": 456239655,
"album_id": -7,
"owner_id": -17507435,
"user_id": 100,
"photo_75": "https://sun1-3.userapi.com/c840632/v840632924/3b7e7/4YUS7DlaLK8.jpg",
"photo_130": "https://sun1-3.userapi.com/c840632/v840632924/3b7e8/Ffpb4ZUlulI.jpg",
"photo_604": "https://sun1-3.userapi.com/c840632/v840632924/3b7e9/-pkl6Qdb9hk.jpg",
"width": 439,
"height": 312,
"text": "",
"date": 1514538602,
"post_id": 4105,
"access_key": "6a61a49570efd9c39c"
}
}
],
"post_source": {
"type": "api"
},
"comments": {
"count": 0,
"groups_can_post": true,
"can_post": 1
},
"likes": {
"count": 0,
"user_likes": 0,
"can_like": 1,
"can_publish": 1
},
"reposts": {
"count": 0,
"user_reposted": 0
},
"views": {
"count": 2
}
}
],
"profiles": [],
"groups": [
{
"id": 17507435,
"name": "Literature Museum",
"screen_name": "samlitmus",
"is_closed": 0,
"type": "group",
"is_admin": 0,
"is_member": 1,
"photo_50": "https://pp.userapi.com/c615722/v615722068/e58c/d5Y8E_5689s.jpg",
"photo_100": "https://pp.userapi.com/c615722/v615722068/e58b/Hm05ga3x2J8.jpg",
"photo_200": "https://pp.userapi.com/c615722/v615722068/e589/yoG_DDalFII.jpg"
},
{
"id": 27711883,
"name": "E:\\music\\melodic hardcore",
"screen_name": "e_melodic_hc",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 1,
"photo_50": "https://pp.userapi.com/c628220/v628220426/47092/xepNnC7pSBw.jpg",
"photo_100": "https://pp.userapi.com/c628220/v628220426/47091/uAokr-c3NQ8.jpg",
"photo_200": "https://pp.userapi.com/c628220/v628220426/4708f/eNY4vzooz4E.jpg"
},
{
"id": 81574241,
"name": "DOS4GW.EXE",
"screen_name": "dos4gw",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 1,
"photo_50": "https://pp.userapi.com/c622118/v622118651/e045/vlhV6QxtoLI.jpg",
"photo_100": "https://pp.userapi.com/c622118/v622118651/e044/P9mVUhXBV58.jpg",
"photo_200": "https://pp.userapi.com/c622118/v622118651/e043/Soq8oxCMB0I.jpg"
},
{
"id": 76709587,
"name": "Prosvet",
"screen_name": "prosvet_pub",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 0,
"photo_50": "https://pp.userapi.com/c630431/v630431500/b24a/GHox8AmDTXU.jpg",
"photo_100": "https://pp.userapi.com/c630431/v630431500/b249/H3mcC-K7htM.jpg",
"photo_200": "https://pp.userapi.com/c630431/v630431500/b248/9fyvB8gkcwc.jpg"
}
],
"next_from": "1/4105_1514494800_5"
}
}
What I need to get from this JSON are lines: "text", "comments", "likes", "reposts", "attachments".
Inside "attachments" field I want to get "photo_604" line.
Here's my code:
class NewsItems: Decodable {
var text: String?
var comments: Comments
var likes: Likes
var reposts: Reposts
var attachments: [Attachments]
}
class Comments: Decodable {
var count: Int?
}
class Likes: Decodable {
var count: Int?
}
class Reposts: Decodable {
var count: Int?
}
class Attachments: Decodable {
var attachments: AttachmentPhoto
}
class AttachmentPhoto: Decodable {
var photo: WhatIsInsideAttachmentsPhoto
}
class WhatIsInsideAttachmentsPhoto: Decodable {
var photo: String?
enum CodingKeys: String, CodingKey {
case photo = "photo_604"
}
}
class WhatIsIsideResponseNewsFeed: Decodable {
var items: [NewsItems]
}
public class ResponseNewsFeed: Decodable {
var response: WhatIsIsideResponseNewsFeed
}
But after making the request:
Alamofire.request(baseURL+methodName, parameters: parameters).responseData(completionHandler: { response in
if let result = response.result.value {
let decoder = JSONDecoder()
let myResponse = try! decoder.decode(ResponseNewsFeed.self, from: result)
completion(myResponse.response.items)
I get an error:
Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(_UL_PetrovLeonid.NewsItems.(CodingKeys
in _FA9A2FC8130449AA328C19ACD9506C2D).attachments,
Swift.DecodingError.Context(codingPath:
[_UL_Leonid.ResponseNewsFeed.(CodingKeys in
_FA9A2FC8130449AA328C19ACD9506C2D).response, _UL_PetrovLeonid.WhatIsIsideResponseNewsFeed.(CodingKeys in _FA9A2FC8130449AA328C19ACD9506C2D).items, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))], debugDescription: "No value associated with key
attachments (\"attachments\").", underlyingError: nil))
Why is that happening and what do I need to do to solve it? I've been into coding only three months from now, so please forgive me beforehand if my problem seems silly.
Thank you.
The error message is very clear
... Swift.DecodingError.keyNotFound ... NewsItems.(CodingKeys in _FA9A2FC8130449AA328C19ACD9506C2D).attachments ... No value associated with key attachments (\"attachments\").
keyNotFound is key is missing
NewsItems.(CodingKeys ... ).attachments is the object for key attachments in NewsItems which is Attachments
No value associated with key attachments (\"attachments\") is what is says.
Shortly: There is no key attachments in Attachments which is true.
Look at your JSON
"attachments": [
{
"type": "photo",
"photo": {
The class equivalent is
class Attachments: Decodable {
let type : String
let photo : AttachmentPhoto
}
And AttachmentPhoto is supposed to be
class AttachmentPhoto: Decodable {
private enum CodingKeys : String, CodingKey {
case photo604 = "photo_604"
case id
}
let id : Int
let photo604 : String // or even URL
// etc.
}
Actually there is no need to use classes, in most cases a struct is sufficient.