I am very new in swift and trying to extract information from a Json response and save them in some separate arrays.
Here is my Json:
{
"result": {
"date": [
{
"TestDate": 970606
},
{
"TestDate": 980703
}
],
"headId": [
{
"AreaCode": 200,
"AreaName": "center"
},
{
"AreaCode": 300,
"AreaName": "Middle"
}
]
}
}
I want to have an array for AreaName and another array for TestDate.
Can you please tell me how to do this?
You decode the JSON with Decodable and then pick off the pieces you want from it:
import Foundation
struct Response: Decodable {
struct Result: Codable {
let headID: [HeadID]
let date: [DateElement]
enum CodingKeys: String, CodingKey {
case headID = "headId"
case date
}
}
struct DateElement: Codable {
let testDate: Int
enum CodingKeys: String, CodingKey {
case testDate = "TestDate"
}
}
struct HeadID: Codable {
let areaCode: Int
let areaName: String
enum CodingKeys: String, CodingKey {
case areaCode = "AreaCode"
case areaName = "AreaName"
}
}
let result: Result
}
let data = """
{
"result": {
"headId": [
{
"AreaCode": 200,
"AreaName": "center"
},
{
"AreaCode": 300,
"AreaName": "Middle"
}
],
"date": [
{
"TestDate": 970606
},
{
"TestDate": 980703
}
]
}
}
""".data(using: .utf8)!
let response = try JSONDecoder().decode(Response.self, from: data)
let areaNames = response.result.headID.map { $0.areaName }
let testDates = response.result.date.map { $0.testDate }
print(areaNames)
print(testDates)
What I use that saves me a lot of time is a website that will translate the JSON structure to Swift for me. https://app.quicktype.io/
It's super duper helpful, and usually works perfectly just by copying and pasting the structures and classes into my Swift file.
Related
Currently working with an API, and while I have successfully gotten it to decode the full result, I am only interested in the Entities/identifier portion. While I have gotten it working and get what I want/need I feel like this could be done better and more elegant and maybe in a single step. Any insight/suggestions appreciated.
JSON returned from API:
{
"count": 4596,
"entities": [
{
"facet_ids": [
"contact",
"siftery",
"investor",
"ipqwery",
"aberdeen",
"apptopia",
"semrush",
"company",
"rank",
"builtwith",
"bombora",
"key_event"
],
"identifier": {
"uuid": "468bef9f-2f50-590e-6e78-62e3adb05aa1",
"value": "Citi",
"image_id": "v1417152861/pdgwqt8ddecult5ktvdf.jpg",
"permalink": "citigroup",
"entity_def_id": "organization"
},
"short_description": "Citigroup is a diversified financial services holding company that provides various financial products and services."
},
{
"facet_ids": [
"contact",
"siftery",
"investor",
"apptopia",
"semrush",
"company",
"rank",
"builtwith",
"key_event"
],
"identifier": {
"uuid": "031a344b-c2b9-e60b-d950-1ae062026fde",
"value": "Citi",
"image_id": "yzlzhjqpparamrswaqa1",
"permalink": "citi-2",
"entity_def_id": "organization"
},
"short_description": "CITi is an NPO supporting the ICT sector in Western Cape."
},
{
"facet_ids": [
"contact",
"siftery",
"semrush",
"company",
"rank",
"builtwith",
"bombora"
],
"identifier": {
"uuid": "7ce45379-957c-49c5-bca2-c9ffd521f7da",
"value": "CITI",
"image_id": "qbkqndm7d0wgbogxjcrs",
"permalink": "citi-f7da",
"entity_def_id": "organization"
},
"short_description": "CITI trusted gateway to project-based change expertise that major organisations need to thrive, change and innovate."
}
]
}
Structs:
struct Entity: Decodable, Identifiable
{
var id: String
var companyName: String
var permalink: String
var imageID: String
init(from entity: Entities.Entity) throws
{
self.id = entity.identifier?.uuid ?? ""
self.companyName = entity.identifier?.value ?? ""
self.permalink = entity.identifier?.permalink ?? ""
self.imageID = entity.identifier?.image_id ?? ""
}
}
struct Entities: Decodable
{
var count:Int?
var entities: [Entity]?
struct Entity: Decodable
{
var facet_ids:[String]?
var identifier:Identifier?
var short_description:String?
}
struct Identifier:Decodable
{
var permalink:String?
var value:String?
var image_id:String?
var entity_def_id:String?
var uuid:String?
}
}
Call to decode:
if let data = data{
do {
let businessEntities = try decoder.decode(Entities.self, from: data)
let entities:[Entity] = try businessEntities.entities!.compactMap{
entity in
do
{
return try Entity(from: entity)
}
}
Thinking you are just interested in the Entities/identifier, you can simplify your model. Here's an example:
typealias Entities = [Entitie]
struct Entitie: Codable {
let facetIDS: [String]
let identifier: Identifier
let shortDescription: String
enum CodingKeys: String, CodingKey {
case facetIDS = "facet_ids"
case identifier
case shortDescription = "short_description"
}
}
struct Identifier: Codable {
let uuid, value, imageID, permalink: String
let entityDefID: String
enum CodingKeys: String, CodingKey {
case uuid, value
case imageID = "image_id"
case permalink
case entityDefID = "entity_def_id"
}
}
You can access entities object and decode it like this:
guard let data = data,
let jsonDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let entitiesObj = jsonDict["entities"] else { return }
do {
let entitiesData = try JSONSerialization.data(withJSONObject: entitiesObj)
let result = try JSONDecoder().decode(Entities.self, from: entitiesData)
} catch {
print(error.localizedDescription)
}
Obs: I created the CodingKeys because I use camelCase in my projects, you can left it in snake_case just remember to replace variables.
I'm new to the Wikipedia API and I have been trying to parse the image url from the API. The JSON I'm trying to parse is as follows:
API:
https://en.wikipedia.org/w/api.php?action=query&titles=tokyo&prop=pageimages&format=json
JSON Result:
{"batchcomplete": "",
"query": {
"normalized": [
{
"from": "tokyo",
"to": "Tokyo"
}
],
"pages": {
"30057": {
"pageid": 30057,
"ns": 0,
"title": "Tokyo",
"thumbnail": {
"source": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Skyscrapers_of_Shinjuku_2009_January.jpg/50px-Skyscrapers_of_Shinjuku_2009_January.jpg",
"width": 50,
"height": 27
},
"pageimage": "Skyscrapers_of_Shinjuku_2009_January.jpg"
}
}
}
}
Below are the struct I created to parse the data. I can see the url when I print it to the console, but since "source" is nested under "thumbnail", which lives in the value of the [String:Page] dict pair, I can't figure out a way to access it. How do I parse data in a dictionary like this? Thank you for your help in advance.
struct WikiAPIResults: Codable {
let batchcomplete: String?
let query: Query?
}
struct Query: Codable {
let normalized: [Normalized]?
let pages: [String:Pages]? // <- I can get to here
}
struct Normalized: Codable {
let from, to: String?
}
struct Pages: Codable {
let pageid, ns: Int?
let title: String?
let thumbnail: Thumbnail?
let pageimage: String?
}
struct Thumbnail: Codable {
let source: String? // <- But I want to grab this
let width, height: Int?
}
func fetchImageFromWikipedia(imageKeyword: String, completion: #escaping (WikiAPIResults) -> Void) {
var urlComponents = URLComponents(string: "https://en.wikipedia.org/w/api.php?")!
urlComponents.queryItems = [
"action": "query",
"titles": imageKeyword,
"prop": "pageimages",
"format": "json"].map { URLQueryItem(name: $0.key, value: $0.value) }
let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in
let jsonDecoder = JSONDecoder()
if let data = data,
let result = try? jsonDecoder.decode(WikiAPIResults.self, from: data) {
completion(result)
}
}
task.resume()
}
fetchImageFromWikipedia(imageKeyword: "Tokyo") { result in
print(result.query?.pages?.values)
}
Do you mean gathering all thumbnail sources?
fetchImageFromWikipedia(imageKeyword: "Tokyo") { result in
let pages = result.query?.pages?.compactMap { $0.value } ?? []
let thumbnailSources = pages.compactMap { $0.thumbnail?.source }
print(thumbnailSources)
}
This works with your example perfectly:
import Foundation
struct Response: Decodable {
struct Query: Decodable {
struct NormalizedQuery: Decodable {
let from: String
let to: String
}
struct Page: Decodable {
struct Thumbnail: Decodable {
let source: URL
let width: Int
let height: Int
}
let id: Int
let ns: Int
let title: String
let thumbnail: Thumbnail
let pageImage: String
private enum CodingKeys: String, CodingKey {
case id = "pageid"
case ns
case title
case thumbnail
case pageImage = "pageimage"
}
}
let normalized: [NormalizedQuery]
let pages: [String : Page]
}
let batchComplete: String
let query: Query
private enum CodingKeys: String, CodingKey {
case batchComplete = "batchcomplete"
case query
}
}
let responseString = """
{
"batchcomplete" : "",
"query" : {
"normalized" : [
{
"from" : "tokyo",
"to" : "Tokyo"
}
],
"pages" : {
"30057" : {
"pageid" : 30057,
"ns" : 0,
"title" : "Tokyo",
"thumbnail" : {
"source" : "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Skyscrapers_of_Shinjuku_2009_January.jpg/50px-Skyscrapers_of_Shinjuku_2009_January.jpg",
"width" : 50,
"height" : 27
},
"pageimage" : "Skyscrapers_of_Shinjuku_2009_January.jpg"
}
}
}
}
"""
let responseData = responseString.data(using: .utf8)!
let response = try! JSONDecoder().decode(Response.self, from: responseData)
print(response)
// Response(batchComplete: "", query: __lldb_expr_18.Response.Query(normalized: [__lldb_expr_18.Response.Query.NormalizedQuery(from: "tokyo", to: "Tokyo")], pages: ["30057": __lldb_expr_18.Response.Query.Page(id: 30057, ns: 0, title: "Tokyo", thumbnail: __lldb_expr_18.Response.Query.Page.Thumbnail(source: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b2/Skyscrapers_of_Shinjuku_2009_January.jpg/50px-Skyscrapers_of_Shinjuku_2009_January.jpg, width: 50, height: 27), pageImage: "Skyscrapers_of_Shinjuku_2009_January.jpg")]))
My problem is that I have JSON object got from the server like this one:
{
"data": [
{
"id": 1,
"name": "at",
"amount": 446,
"createdAt": "25/04/2020",
"updatedAt": "25/04/2020"
},
{
"id": 2,
"name": "iste",
"amount": 872,
"createdAt": "25/04/2020",
"updatedAt": "25/04/2020"
}
]
}
And I have Codable struct that decodes this object:
struct Expense: Codable, Identifiable {
var id: Int
var name: String
var amount: String
var createdAt: String
var updatedAt: String
}
Also, I have class with a static method that will do the AF request, also I'm using FuturePromise library for hendling completionof the request:
struct RequestAPI {
#discardableResult
static func callAndDecode<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder()) -> Future<T> {
return Future(operation: { completion in
AF.request(route).responseDecodable(decoder: decoder, completionHandler: { (response: DataResponse<T, AFError>) in
switch response.result {
case .success(let value):
completion(.success(value))
case .failure(let error):
print(error.localizedDescription)
completion(.failure(error))
}
})
})
}
}
Problem is that I have a root "data" parameter that sometimes is present and sometimes not.
I know that there is a solution that I can create a Result codable Model that will be the parent of the Expense Model, but that does not approach that I want, because what will happen if I will have 20 different models I'll have to create 20 deferent root Models?
Yes, I can do it with CodingKeys but that is a little bit hacky and too much of code for this simple task.
So the best approach is to add something like this:
struct ExpensesList: Codable {
var data: [Expense]
}
But for me, it is a problem that I will always have 'data' root, so then for any model I will have some 'List' model.
Is there a better approach that is not hacky or this is the only one.
Maybe to send a child model to one data model, but how to recognize it in views,...?
Thank you in advance.
If I understand you correctly, you can make your Root structure generic, and should also make the data property optional.
struct Foo: Decodable {
let foo: Int
}
struct Bar: Decodable {
let bar: String
}
struct Root<T: Decodable>: Decodable {
let data: [T]?
}
typealias FooList = Root<Foo>
typealias BarList = Root<Bar>
let fooData = """
{ "data": [ { "foo": 42 } ] }
""".data(using: .utf8)!
let barData = """
{ "data": [ { "bar": "baz" } ] }
""".data(using: .utf8)!
do {
let foos = try JSONDecoder().decode(FooList.self, from: fooData)
let bars = try JSONDecoder().decode(BarList.self, from: barData)
} catch {
print(error)
}
I have a below JSON response. And I want to fetch the allkeys of “data” which is [“rules”, “preference”, “goals”] using .keys method. But I couldn’t get the array of allkeys using .keys feature. I have attached my code snippet also. if you had faced this one, please suggest me to rid of this concern.
Although, I can get these allKeys using ObjectMapper and native Dictionary objects. I just need to know why I couldn't achieve this using Codable.
My json response
{
"statusCode": 200,
"status": "success",
"message": null,
"data": {
"rules": {
"goals": {
"min": "1",
"max": "3"
}
},
"preference": [
1,
2,
3
],
"goals": {
"total": 4,
"data": []
}
}
}
My code Snippet:
struct MeetingsDataModal: Codable {
let statusCode: Int?
let status: String?
let message: String?
let data: Results?
enum CodingKeys: String, CodingKey {
case statusCode = "statusCode"
case status = "status"
case message = "message"
case data = "data"
}
func allkeys() {
}
}
struct Results : Codable {
let rules: Rules?
let preference: [Preference]?
let goals: Goals?
enum CodingKeys: String, CodingKey {
case rules = "rules"
case preference = "preference"
case goals = "goals"
}
}
struct Rules : Codable {
}
struct Preference : Codable {
}
struct Goals : Codable {
}
My expectation
let content = try JSONDecoder().decode(MeetingsDataModal.self, from: (response as? Data)!)
print(content.data.keys)
But I am getting,
Value of type 'Results?' has no member 'keys'
Perhaps I am not understanding the question well but your "keys" are defined by your Codable protocol - so they are known. If you are using Swift 4.2+ you can take advantage of the CaseIterable protocol
struct Results: Codable {
let testOne: Int
let testTwo: String
enum CodingKeys: String, CodingKey, CaseIterable {
case testOne
case testTwo
}
}
Results.CodingKeys.allCases.map { $0.rawValue }
If you do need .keys. could add two-line code :
struct Results : Codable {
let rules: Rules?
let preference: [Preference]?
let goals: Goals?
enum CodingKeys: String, CodingKey {
case rules = "rules"
case preference = "preference"
case goals = "goals"
}
var keys : [String]{
return["rules", "preference","goals"]
}
}
Actually, if you don't like encoding/decoding way , we have another traditional JSON object can help you to handle unstructured JSON.
let obj = try JSONSerialization.jsonObject(with: response!, options: JSONSerialization.ReadingOptions.allowFragments)
print((((obj as! [String: Any?])["data"]) as! [String: Any?]).keys)
Here is one way to decode you data structure.
let d = """
{
"statusCode": 200,
"status": "success",
"message": null,
"data": {
"rules": {
"goals": {
"min": "1",
"max": "3"
}
},
"preference": [
1,
2,
3
],
"goals": {
"total": 4,
"data": []
}
}
}
""".data(using: .utf8)!
struct MeetingsDataModal: Decodable {
let statusCode: Int
let status: String
let message: String?
let data: Results
}
struct Results : Decodable {
let rules: Rules
let preference: [Int]
let goals: Goals
}
struct Rules : Decodable {
let goals : DirectData
}
struct DirectData : Decodable{
let min : String
let max : String
}
struct Goals : Decodable {
let total : Int
let data : [String]
}
let data0 = try JSONDecoder().decode(MeetingsDataModal.self, from: d)
print(data0)
The actual JSON,that I need to parse in swift4 is,
{
"class": {
"semester1": [
{
"name": "Kal"
},
{
"name": "Jack"
},
{
"name": "Igor"
}
],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept",
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
The code that I'd tried but failed to execute is,
struct Class: Decodable {
let semester: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case location = "location"
case statusTracker = "statusTracker"
}
}
struct location: Decodable {
let Dept: [typesSubIn]
let BlockNo: Int
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct internalComponents: Decodable {
let semester1: [semsIn]
let subjects: [subjectsIn]
}
struct semsIn: Decodable {
let nameIn: String
}
struct subjectsIn: Decodable {
let subjects: String
}
struct Dept: Decodable {
let Depts: String
}
I know it's completely wrong can someone give the actual format? I'm actually confused with the format for "subjects".It's not compiling as a whole too.
There are many issues.
You are making a common mistake by ignoring the root object partially.
Please take a look at the JSON: On the top level there are 3 keys class, location and statusTracker. The values for all 3 keys are dictionaries, there are no arrays.
Since class (lowercase) is a reserved word, I'm using components. By the way please conform to the naming convention that struct names start with a capital letter.
struct Root : Decodable {
let components : Class
let location: Location
let statusTracker: StatusTracker
enum CodingKeys: String, CodingKey { case components = "class", location, statusTracker }
}
There are many other problems. Here a consolidated version of the other structs
struct Class: Decodable {
let semester1: [SemsIn]
let subjects : [String]
}
struct Location: Decodable {
let dept : [String]
let blockNo : Int
enum CodingKeys: String, CodingKey { case dept = "Dept", blockNo = "BlockNo" }
}
struct SemsIn: Decodable {
let name: String
}
struct StatusTracker: Decodable {
let googleFormsURL: String // URL is no benefit
let totalCount: Int
}
Now decode Root
do {
let result = try decoder.decode(Root.self, from: data)
} catch { print(error) }
It looks like you sterilize Class object in wrong way. It should looks like:
struct Class: Decodable {
let class: [internalComponents]
let location: [location]
let statusTracker: [statusTracker]
}
There are a few things here causing your issue.
You have no top level item, I added Response struct
Location, class and statusTracker are both at the same level, not under class.
In your class struct, your items are set as arrays but they aren't arrays
To debug these types of issues, wrap your decode in a do catch block and print out the error. it will tell you the reason it failed to parse
Try this code from my playground:
let jsonData = """
{
"class": {
"semester1": [{
"name": "Kal"
}, {
"name": "Jack"
}, {
"name": "Igor"
}],
"subjects": [
"English",
"Maths"
]
},
"location": {
"Dept": [
"EnglishDept"
],
"BlockNo": 1000
},
"statusTracker": {
"googleFormsURL": "beacon.datazoom.io",
"totalCount": 3000
}
}
""".data(using: .utf8)!
struct Response: Decodable {
let cls: Class
let location: Location
let statusTracker: statusTracker
enum CodingKeys: String, CodingKey {
case cls = "class"
case location
case statusTracker
}
}
struct Class: Decodable {
let semester: [SemesterStudents]
let subjects: [String]
enum CodingKeys: String, CodingKey {
case semester = "semester1"
case subjects
}
}
struct Location: Decodable {
let dept: [String]
let blockNo: Int
enum CodingKeys: String, CodingKey {
case dept = "Dept"
case blockNo = "BlockNo"
}
}
struct statusTracker: Decodable {
let googleFormsURL: URL
let totalCount: Int
}
struct SemesterStudents: Decodable {
let name: String
}
struct Dept: Decodable {
let Depts: String
}
do {
let result = try JSONDecoder().decode(Response.self, from: jsonData)
print(result)
} catch let error {
print(error)
}
Another approach is to create an intermediate model that closely matches the JSON, let Swift generate the methods to decode it, and then pick off the pieces that you want in your final data model:
// snake_case to match the JSON
fileprivate struct RawServerResponse: Decodable {
struct User: Decodable {
var user_name: String
var real_info: UserRealInfo
}
struct UserRealInfo: Decodable {
var full_name: String
}
struct Review: Decodable {
var count: Int
}
var id: Int
var user: User
var reviews_count: [Review]
}
struct ServerResponse: Decodable {
var id: String
var username: String
var fullName: String
var reviewCount: Int
init(from decoder: Decoder) throws {
let rawResponse = try RawServerResponse(from: decoder)
// Now you can pick items that are important to your data model,
// conveniently decoded into a Swift structure
id = String(rawResponse.id)
username = rawResponse.user.user_name
fullName = rawResponse.user.real_info.full_name
reviewCount = rawResponse.reviews_count.first!.count
}
}