It's my first post. So far I've always found my answers myself, but today, after plenty hours of research, I'm still hitting my head against a wall...
Here is my problem, I get a JSON from a Alamofire request and I cannot parse my data as I want to.
I have this answer :
{"family": {"id":1, "name": "myFamily","members": [{"userId":15, "lasName": "COLINET", "firstName":"Steve","latitude":48.290762, "longitude":4.070 },{"userId":18,"lasName":"BERTHIER","firstName":"Renaud","latitude":48.290755, "longitude":4.071 }]}}
I want to get a list of members in which I can pickup the firstname/lastname and latitude/longitude.
I've tried so much things that I can't enumerate...
Here is the (bad) code that I have right now :
func getMembers(username:String, password:String){
var members:NSDictionary = [:]
let parameters: Parameters=[
"action":"getPosition",
"username":"\(username)",
"password":"\(password)"
]
Alamofire.request(login_url, method: .post, parameters: parameters).responseJSON{
response in
switch response.result {
case .success(let data):
guard let json = data as? [String : AnyObject] else { return }
members = json["family"]!["members"] as! NSDictionary
members.forEach { member in
print(member["firstName"])
}
case .failure(let error):
print(error)
}
}
}
Thank's for your help.
members is an array not dictionary
if let family = json["family"] as? [String:Any] {
if let members = family["members"] as? [[String:Any]] {
print(members)
}
}
it would be better using
struct Root: Codable {
let family: Family
}
struct Family: Codable {
let id: Int
let name: String
let members: [Member]
}
struct Member: Codable {
let userID: Int
let lasName, firstName: String
let latitude, longitude: Double
enum CodingKeys: String, CodingKey {
case userID = "userId"
case lasName, firstName, latitude, longitude
}
}
do {
let tr = try JSONDecoder().decode(Root.self,from:jsonData)
print(tr.family.members)
}
catch {
print(error)
}
Related
I'm using the tableview to display the Two Json value but the problem is I cant add value into model struct to displaying into tableview using two Api's. i want to show percentage value in one of the cell label and
here is my json
[
{
"Percentage": 99.792098999,
}
]
my second json value
{
"Categories": [
"Developer",
"ios "
],
"Tags": [
{
"Value": "kishore",
"Key": "Name"
},
{
"Value": "2",
"Key": "office"
},
]
}
and i need show the Categories value in Categories label in tableview
value and key on tableview
here is my Struct
struct info: Decodable {
let Categories: String?
let Tags: String?
let Value: String?
let Key: String?
var Name: String?
let percentage: Double?
}
here its my code
var List = [info]()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers)
print(json as Any)
guard let jsonArray = json as? [[String: Any]] else {
return
}
print(jsonArray)
for dic in jsonArray{
guard let per = dic["percentage"] as? Double else { return }
print(per)
}
and second json
if let array = json["Tags"] as? [[String: String]] {
for dict in array {
let key = dict["Key"]
let value = dict["Value"]
switch key {
case "office":
case "Name":
default:
break;
}
}
here is my cell for row indexpath
cell.Categories.text = list[indexpath.row].percentage
cell.Name.text = list[indexpath.row].name
cell.office.text = list[indexpath.row].office
Please use Swift 4 Codable protocol to decode the value from JSON.
//1.0 Create your structures and make it conform to Codable Protocol
struct Tags: Codable{
var Key: String
var Value: String
}
struct Sample: Codable{
var Categories: [String]
var Tags: [Tags]
}
In your method, perform below steps:
//2.0 Get your json data from your API. In example below, i am reading from a JSON file named "Sample.json"
if let path = Bundle.main.path(forResource: "Sample", ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
do {
//3.0 use JSONDecoder's decode method to decode JSON to your model.
let sample = try JSONDecoder().decode(Sample.self, from: jsonData)
//4.0 User the "sample" model to do your stuff. Example, printing some values
print("Sample.Category = \(sample.Categories)")
print("Sample.Name = \(sample.Tags[0].Value)")
print("Sample.Office = \(sample.Tags[1].Value)")
} catch let error {
print("Error = \(error)")
}
} catch {
// handle error
}
}
I prefer to use Codable all the time with JSON even for simpler types so for percentage I would do
struct ItemElement: Decodable {
let percentage: Double
enum CodingKeys: String, CodingKey {
case percentage = "Percentage"
}
}
and we need to keep these values in a separate array, declared as a class property
let percentageList: [Double]()
and json encoding would then be
let decoder = JSONDecoder()
do {
let result = try decoder.decode([ItemElement].self, from: data)
percentageList = result.map { item.percentage }
} catch {
print(error)
}
Similar for the second part
struct Item: Decodable {
let categories: [String]
let tags: [Tag]
enum CodingKeys: String, CodingKey {
case categories = "Categories"
case tags = "Tags"
}
}
struct Tag: Decodable {
let value, key: String
enum CodingKeys: String, CodingKey {
case value = "Value"
case key = "Key"
}
}
use a dictionary for the result, again as a class property
var values = [String: String]()
and the decoding
let decoder = JSONDecoder()
do {
let result = try decoder.decode(Item.self, from: data)
for item in result.tags {
values[item.key] = values.item.value
}
} catch {
print(error)
}
and then in the cell for row code
cell.Categories.text = percentageList[indexpath.row].percentage
cell.Name.text = values["name"]
cell.office.text = values["office"]
Note that this last code looks very strange since you don't have an array of name/office values judging by your json. Maybe you have simplified it some way but the code above is the best I can do with the information given even if it possibly wrong
Getting data append problem in nested json by using swiftjson library swift.
I have created two struct but getting an error while appending the final list. Error in getting when appending data. Have i created struct well.
My struct
struct GistModel {
var comments : Int!
var commentsUrl : String!
var descriptionField : String!
var owner : Owner!
}
struct Owner{
var login : String!
}
JSON DATA result:
{
url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2",
forks_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/forks",
commits_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/commits",
id: "7e624eed62b3a317541791d719dcacf2",
node_id: "MDQ6R2lzdDdlNjI0ZWVkNjJiM2EzMTc1NDE3OTFkNzE5ZGNhY2Yy",
git_pull_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2.git",
git_push_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2.git",
html_url: "https://gist.github.com/7e624eed62b3a317541791d719dcacf2",
files:
{
GistTest2:
{
filename: "GistTest2",
type: "text/plain",
language: null,
raw_url: "https://gist.githubusercontent.com/MasamMahmood/7e624eed62b3a317541791d719dcacf2/raw/7302f0d923e9e08b0e502ad9df762a1b2aa072e1/GistTest2",
size: 29
}
},
public: true,
created_at: "2019-02-01T18:41:39Z",
updated_at: "2019-02-01T19:01:16Z",
description: "Gist Test 2",
comments: 0,
user: null,
comments_url: "https://api.github.com/gists/7e624eed62b3a317541791d719dcacf2/comments",
owner:
{
login: "MasamMahmood",
id: 36441313,
node_id: "MDQ6VXNlcjM2NDQxMzEz",
avatar_url: "https://avatars3.githubusercontent.com/u/36441313?v=4",
gravatar_id: "",
url: "https://api.github.com/users/MasamMahmood",
html_url: "https://github.com/MasamMahmood",
followers_url: "https://api.github.com/users/MasamMahmood/followers",
following_url: "https://api.github.com/users/MasamMahmood/following{/other_user}",
gists_url: "https://api.github.com/users/MasamMahmood/gists{/gist_id}",
starred_url: "https://api.github.com/users/MasamMahmood/starred{/owner}{/repo}",
subscriptions_url: "https://api.github.com/users/MasamMahmood/subscriptions",
organizations_url: "https://api.github.com/users/MasamMahmood/orgs",
repos_url: "https://api.github.com/users/MasamMahmood/repos",
events_url: "https://api.github.com/users/MasamMahmood/events{/privacy}",
received_events_url: "https://api.github.com/users/MasamMahmood/received_events",
type: "User",
site_admin: false
},
truncated: false
}
Swift:
switch response.result{
case .success(let value):
let json = JSON(value)
print(json)
for subJson in json.arrayValue {
let comm = subJson["comments"].intValue
let commurl = subJson["comments_url"].stringValue
let desc = subJson["description"].string
//let age = subJson["owner"]["login"].string
for item in subJson{
let login = subJson["owner"]["login"].string
// do something
}
let user = GistModel(comments: comm, commentsUrl: commurl, descriptionField: desc, login: login)//, owner: login)
self.DataList.append(user)
print(user)
}
I am newbie Getting error on append the list. "Use of unresolved identifier 'login'".
If you are willing to move to standard json handling using Codable then this will work. First let structs implement Decodable
struct GistModel: Decodable {
let comments: Int
let commentsUrl: String
let description: String //Changed the name here
let owner: Owner
}
struct Owner: Decodable {
let login: String
}
And encoding is done like this
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode([GistModel].self, from: data)
print(result[0].owner.login)
print(result[0].comments)
print(result[0].commentsUrl)
} catch {
print(error)
}
I'm having trouble getting a certain value when I make an API call. The structs are set up like so:
struct Status: Decodable {
let status: String
let results: [Results]
}
struct Results: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]
}
struct Multimedia: Decodable {
let imageUrl: String
enum CodingKeys: String, CodingKey {
case imageUrl = "url"
}
}
And I'm trying to get the imageUrl member of the Multimedia struct. When I make the data request, I populate an array var storyData = [Results]() with the object.results. I make the data request in my viewDidLoad:
fetchData(url: jsonURL) { (result: FetchResult<Status>) -> (Void) in
switch result {
case .success(let object): self.storyData = object.results
print("\n\nNumber of stories: \(self.storyData.count)\n\nStories: \n\n \(self.storyData)")
case .failure(let error):
print("Error decoding JSON: \n\n \(error)")
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Now, I'm trying to get the imageUrl so I can display the image in a table view, and I thought I'd get it in my cellForRowAt method using something like
let newsStories = storyData[indexPath.row]
cell.storyImageView.cacheImage(urlString: newsStories.multimedia.imageUrl)
However I can't do this, I can access newsStories.multimedia but no further, as if multimedia doesn't have any properties.
Is there a different way that I can get the imageUrl?
[Multimedia] is an array, you have to access it through an index. Then you can access it with imageUrl.
For Example:
cell.storyImageView.cacheImage(urlString: newsStories.multimedia[0].imageUrl)
This response from API:
{
"status": 200,
"message": "Success",
"data": {
"result": [
{
"name": "Anything",
"response": [
{
"name": "XYZ",
"prize": "1.86"
},
{
"name": "ABCD",
"prize": "9.86"
}
]
}
],
"overall": "XYZ"
}
}
How, can I sum the prize in the response as I have to show it in the header of Table. I did this.
var prizeArr = [Int]()
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let countArr = winnerArr![section]["response"] as? Array<Dictionary<String,Any>>
prizeArr = (countArr!.compactMap{ $0["prize"] })
print(prizeArr)
header.textL[0].text = winnerArr![section]["name"] as? String
header.textL[3].text = "\(String(describing: countArr!.count))"
return header
}
I am trying to store the prize in the prizeArr and then I will sum it up. But, it prints nil, but when I write it in the terminal it gives the value individually. How, can I sum the prize in this ArrayObject?
Also, when I use prizeArr = [String](), then it works and store the prize in the Array, but for Int why is it not working?
First, as #Benhamine pointed out you should start with a clean architecture and map your JSON into a type safe class.
Step 1: Codable Types
Lets define a structure that represents our JSON so we can better consume it in our App. JSON is something we never want to pass around our App. Lets rather pass around something well-defined and documented so we have no need to do any force unwraps and crash our App.
struct JSONResponse: Codable {
enum CodingKeys: String, CodingKey {
case data
}
let data: Data
}
extension JSONResponse {
struct Data: Codable {
enum CodingKeys: String, CodingKey {
case results = "result"
}
let results: [Result]
}
}
extension JSONResponse.Data {
struct Result: Codable {
let name: String
let winners: [Winner]
enum CodingKeys: String, CodingKey {
case winners = "response"
case name
}
}
}
extension JSONResponse.Data.Result {
struct Winner: Codable {
let name: String
let prize: String
}
}
Step 2: Parsing
Parsing using Codable is super simple. The code below will show how we convert it into JSON and also how one could go about getting the sum of the float values.
do {
let o: JSONResponse = try JSONDecoder().decode(JSONResponse.self, from: jsonData)
let floatValues = o.data.results.flatMap({ $0.winners }).compactMap({ Float($0.prize) })
floatValues.reduce(0, +)
print(floatValues)
} catch let e {
print(e)
}
Step 3: Integrate
We now have the building blocks we need to get this information so let's hook it up to your code by starting with what we want our code to look like.
/// We store our main data type for easy reference
var resultsBySection: [Result]
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let currentResults = resultsBySection[section]
let prizeTotals = currentResults.flatMap({ $0.winners }).compactMap({ Float($0.prize) })
let totalPriceMoney = prizeTotals.reduce(0, +)
header.textL[0].text = currentResults.name
header.textL[3].text = "\(String(describing: totalPriceMoney))"
return header
}
Notice how in the above code I do not do any JSON decoding in cell dequeueing. Ideally that should be done when we retrieve our JSON and convert it to our types.
Step 4: Refactor
An essential piece of any code experience should contain some reflection into the code we have written and consider how it could be refactored.
In our example above we could probably hardcode the totals onto the controller or create a custom data structure that will do this for us when we parse JSON. Do we always want to manually calculate the totals if we always need this total? We could have a custom function to do the calculation or just do it in our JSON decoding logic.
Anyways, the idea is that we should always look at what we are writing and question how it could be improved
Try this Codable solution to reduce the prizes together:
struct Winners: Codable {
let status: Int
let message: String
let data: DataClass
}
struct DataClass: Codable {
let result: [Result]
let overall: String
}
struct Result: Codable {
let name: String
let response: [Response]
}
class Response: Codable {
let name: String
let prize: Double
init(name: String, prize: Double) {
self.name = name
self.prize = prize
}
enum CodingKeys: String, CodingKey {
case name
case prize
}
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
let prizeString = try container.decode(String.self, forKey: .prize)
guard let prize = Double(prizeString) else {
throw SerializationError.invalid("prize", prizeString)
}
self.prize = prize
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode("\(prize)", forKey: .prize)
}
}
func testing() {
let winners = try! JSONDecoder().decode(Winners.self, from: jsonData)
let sum = winners.data.result[section].response
.map({ $0.prize })
.reduce(0, +)
print(sum)
}
let sum = winnerArr?[section]["response"].reduce(0, { x, y in
x + y["prize"]
}) ?? 0
As an aside, I would recommend parsing the response and turning it into usable objects rather than dealing with the raw response : https://developer.apple.com/swift/blog/?id=37
Thanks to all of your guys, who helped. I am a newbie and not familiar with Codable. Thanks for introducing me to the same. I will try to understand it and use it for future purposes.
So, coming back to the answer:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
self.prizeArr.removeAll()
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: cellReuseIdentifier) as! OverallHeaderCell
let resArr = self.winnerArr![section]["response"]! as? Array<Dictionary<String,Any>>
for prize in resArr!
{
let doubleStr = prize["prize"] as? NSString
self.prizeArr.append((doubleStr?.doubleValue)!)
}
let sumedArr = prizeArr.reduce(0, +)
let countArr = winnerArr![section]["response"] as? Array<Dictionary<String,Any>>
header.textL[0].text = winnerArr![section]["name"] as? String
header.textL[1].text = "$\(sumedArr)"
header.textL[3].text = "\(String(describing: countArr!.count))"
return header
}
Here I am converting each string to a double and appending it to the prizeArr and then, finally, summing the entire array to get the desired result. But, this is not the ideal way to do it. This answer is for all the newbies like me and please learn Codables.
I've got some JSON messages coming in over a websocket connection.
// sample message
{
type: "person",
data: {
name: "john"
}
}
// some other message
{
type: "location",
data: {
x: 101,
y: 56
}
}
How can I convert those messages into proper structs using Swift 4 and the Codable protocol?
In Go I can do something like: "Hey at the moment I only care about the type field and I'm not interested in the rest (the data part)." It would look like this
type Message struct {
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
As you can see Data is of type json.RawMessage which can be parsed later on. Here is a full example https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal.
Can I do something similar in Swift? Like (haven't tried it yet)
struct Message: Codable {
var type: String
var data: [String: Any]
}
Then switch on the type to convert the dictionary into proper structs. Would that work?
I wouldn't rely upon a Dictionary. I'd use custom types.
For example, let's assume that:
you know which object you're going to get back (because of the nature of the request); and
the two types of response truly return identical structures except the contents of the data.
In that case, you might use a very simple generic pattern:
struct Person: Decodable {
let name: String
}
struct Location: Decodable {
let x: Int
let y: Int
}
struct ServerResponse<T: Decodable>: Decodable {
let type: String
let data: T
}
And then, when you want to parse a response with a Person, it would be:
let data = json.data(using: .utf8)!
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Person>.self, from: data)
let person = responseObject.data
print(person)
} catch let parseError {
print(parseError)
}
Or to parse a Location:
do {
let responseObject = try JSONDecoder().decode(ServerResponse<Location>.self, from: data)
let location = responseObject.data
print(location)
} catch let parseError {
print(parseError)
}
There are more complicated patterns one could entertain (e.g. dynamic parsing of the data type based upon the type value it encountered), but I wouldn't be inclined to pursue such patterns unless necessary. This is a nice, simple approach that accomplishes typical pattern where you know the associated response type for a particular request.
If you wanted you could validate the type value with what was parsed from the data value. Consider:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse<T: Payload>: Decodable {
let type: PayloadType
let data: T
}
Then, your parse function could not only parse the right data structure, but confirm the type value, e.g.:
enum ParseError: Error {
case wrongPayloadType
}
func parse<T: Payload>(_ data: Data) throws -> T {
let responseObject = try JSONDecoder().decode(ServerResponse<T>.self, from: data)
guard responseObject.type == T.payloadType else {
throw ParseError.wrongPayloadType
}
return responseObject.data
}
And then you could call it like so:
do {
let location: Location = try parse(data)
print(location)
} catch let parseError {
print(parseError)
}
That not only returns the Location object, but also validates the value for type in the server response. I'm not sure it's worth the effort, but in case you wanted to do so, that's an approach.
If you really don't know the type when processing the JSON, then you just need to write an init(coder:) that first parses the type, and then parses the data depending upon the value that type contained:
enum PayloadType: String, Decodable {
case person = "person"
case location = "location"
}
protocol Payload: Decodable {
static var payloadType: PayloadType { get }
}
struct Person: Payload {
let name: String
static let payloadType = PayloadType.person
}
struct Location: Payload {
let x: Int
let y: Int
static let payloadType = PayloadType.location
}
struct ServerResponse: Decodable {
let type: PayloadType
let data: Payload
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(PayloadType.self, forKey: .type)
switch type {
case .person:
data = try values.decode(Person.self, forKey: .data)
case .location:
data = try values.decode(Location.self, forKey: .data)
}
}
enum CodingKeys: String, CodingKey {
case type, data
}
}
And then you can do things like:
do {
let responseObject = try JSONDecoder().decode(ServerResponse.self, from: data)
let payload = responseObject.data
if payload is Location {
print("location:", payload)
} else if payload is Person {
print("person:", payload)
}
} catch let parseError {
print(parseError)
}