Create a comparison in an array - ios

I working on an app to show some information about cities
In an array I have two parameters
1- languages (I don't know how many there are)
2- the number of people that speak on that language
I get this data from an server
here is this two paramter in JSon
"language": "French",
"number": "12321",
these data among other data is saved in an array
I Just want to get the most used language with the pecentage
for example French with 35%
how can I do it in swift?
Your help will be appreciated.

import Foundation
let serverOutput = Data("""
[
{
"language": "French",
"number": "12"
},
{
"language": "English",
"number": "10"
}
]
""".utf8)
struct LangueUsers: Codable {
let language: String
let number: Int
enum CodingKeys: CodingKey {
case language
case number
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
language = try container.decode(String.self, forKey: .language)
let rawNumber = try container.decode(String.self, forKey: .number)
guard let number = Int(rawNumber) else {
throw DecodingError.typeMismatch(Int.self, DecodingError.Context(codingPath: [CodingKeys.number], debugDescription: "\(rawNumber) can't be convert to Int"))
}
self.number = number
}
}
struct LangueUsersPercent: CustomStringConvertible {
let language: String
let share: Double
init(language: String, number: Int, all: Int) {
self.language = language
self.share = Double(number) / Double(all)
}
var description: String {
return String(format: "%# - %0.1f%%", language, share * 100)
}
}
let decoder = JSONDecoder()
//1
var output = try decoder.decode([LangueUsers].self, from: serverOutput)
//2
let allUser = output.reduce(0) { (reult, languageUsers) -> Int in
reult + Int(languageUsers.number)
}
//3
output.sort { (lhs, rhs) -> Bool in
lhs.number > rhs.number
}
//4
let response = output.map {
LangueUsersPercent(language: $0.language, number: $0.number, all: allUser)
}
print(response[0])
The code assumes that the output from a server is in serverOutput variable. So steps need to achieve your task:
decode your JSON to swift structure (called LangueUsers) using swift codable. Keep in mind that by default it won't convert string values to int, so I have to create a custom init(from decoder: Decoder) initializer
count the sum of all users (you can do it using for loop or reduce like in my example)
sort your list, so the language with the most users will be first
this step is optional, but I've added a separate structure that will help us generate output and in this step, we are rewriting our input structures to output ones
Hope this is clear for you. Let me know if you have any questions.

The simplest way is:
// Example JSON
const exampleJSON = {
"people": [
{
"language": "French",
"number": 12321,
},
{
"language": "English",
"number": 7000,
}
]
};
// Parse it
const parsed = JSON.parse(JSON.stringify(exampleJSON));
const speakers = [...parsed.people];
// Count number of all speakers and let it be as 100%
let sumAllSpeakers = 0;
parsed.people.reduce((previous, current) => {
sumAllSpeakers = previous.number + current.number;
return previous;
});
// Compare fucntion
const compareSpeakers = (speaker1, speaker2) => {
if (speaker1.number > speaker2.number) {
return -1;
}
if (speaker1.number < speaker2.number) {
return 1;
}
return 0;
}
// Create new array with statistic sorted from max number
const statisticsOfSpeakersInPersent = speakers
.sort((speaker1, speaker2) => compareSpeakers(speaker1, speaker2))
.map(speaker => {
return {...speaker, ...{inPersent: speaker.number * 100 / sumAllSpeakers + '%'}}
});
I hope this example helps you.
Out:
[ { language: 'French',
number: 12321,
inPersent: '63.76999120128358%' }, { language: 'English',
number: 7000,
inPersent: '36.23000879871642%' } ]

Related

Unable to extract data properly from JSON in swift

I have a this kind of json object in my response after parsing json string to object
[
"requestId": 1,
"response": {
code = SUCCESS;
},
"messageId": ACTION_COMPLETE
]
I am trying to extract requestId using
responseMsg["requestId"] as! Int
I am getting this error
Could not cast value of type 'NSTaggedPointerString' (0x21877a910) to
'NSNumber' (0x218788588).
I tried it changing to Int(responseMsg["requestId"] as! String)!
This thing is working for positive numbers but not for negative numbers probably bcz when requestId = -2 it throws me an error
Could not cast value of type '__NSCFNumber' (0x21877a000) to
'NSString' (0x218788290).
I tried with different other solution too but did not work.
For parsing the JSON data, its better use Codable instead of manually parsing everything.
For JSON format,
{
"requestId": 1,
"response": {
"code":"SUCCESS"
},
"messageId": "ACTION_COMPLETE"
}
Create Models like,
struct Root: Decodable {
let requestId: String?
let messageId: String
let response: Response
enum CodingKeys: String, CodingKey {
case requestId, messageId, response
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let id = try? values.decode(Int.self, forKey: .requestId) {
requestId = String(id)
} else if let id = try? values.decode(String.self, forKey: .requestId) {
requestId = id
} else {
requestId = nil
}
messageId = try values.decode(String.self, forKey: .messageId)
response = try values.decode(Response.self, forKey: .response)
}
}
Now, parse the JSON data using,
do {
let root = try JSONDecoder().decode(Root.self, from: data)
print(root.requestId) //access requestId like this....
} catch {
print(error)
}
Try
Int(String(describing: responseMsg["requestId"]))!
This ensures any data is converted to string first and then to int
This error message
Could not cast value of type 'NSTaggedPointerString' (0x21877a910) to 'NSNumber' (0x218788588).
tells us that the JSON request id is being parsed as a string. NSTaggedPointerString is a special internal type used by the ObjC runtime to represent strings.
Try this:
let requestId = responseMsg["requestId"] as! String
print("request id: \(requestId)") // Prints a string
Note, it may print something that looks like a number, but it isn't one.
The JSON you are parsing probably looks like
{
"requestId": "1",
"response": {
"code" = "SUCCESS"
},
"messageId": "ACTION_COMPLETE"
}
Note the 1 in quotes.
Swift 5
String interpolation is what worked for me! (casting it first to String didn't work as I had other values for which the json decoder actually did its job and cast them directly to a number)
if let responseMsg_any = responseMsg["requestId"],
let responseMsg_int = Int("\(responseMsg_any)") {
//..
}
Warning:
This solution allows any Type to become a String and be checked for a Int value. Only use this if you're not concerned about the value's Type prior to interpolation.

Swift 4 enum codable

I parse json data from an api. My struct looks like this:
struct ServiceUnit: Codable {
let description,id: String?
let group, groupDescription:String?
let name: String?
var value: MyValue?
enum CodingKeys: String, CodingKey {
case description = "Description"
case group = "Group"
case groupDescription = "GroupDescription"
case id = "Id"
case name = "Name"
case value = "Value"
}
}
enum MyValue: Codable {
case string(String)
case innerItem(InnerItem)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
return
}
if let innerItem = try? container.decode(InnerItem.self) {
self = .innerItem(innerItem)
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
case .innerItem(let x):
try container.encode(x)
}
}
}
struct InnerItem: Codable {
let type, id, name: String
enum CodingKeys: String, CodingKey {
case type = "__type"
case id = "Id"
case name = "Name"
}
}
and the json data looks like this:
[
{
"Description": null,
"Group": "Beskrivning av enheten",
"GroupDescription": null,
"Id": "Description",
"Name": "Mer om enheten",
"Value": "Det finns möjlighet till parkering på gatorna runt om, men det är kantstenar och ganska branta backar för att komma upp till lekplatsen.\r\n\r\nUtanför själva lekplatsen finns en gungställning med en plan omväg in. Alla lekredskap står i sandytor, det finns många kanter. Runt hela lekplatsen går ett staket med öppningar i olika riktningar."
},
{
"Description": null,
"Group": "Bilder och film",
"GroupDescription": null,
"Id": "Image",
"Name": "Huvudbild",
"Value": {
"__type": "FileInfo",
"Id": "8871b3b1-14f4-4054-8728-636d9da21ace",
"Name": "ullerudsbacken.jpg"
}
}
]
When the data is loaded, I filter it to get only the result where id = description, and I retried the value of value like this:
let su = serviceUnit.filter{$0.id == "ShortDescription"}
let description = su[0].value
Then, my problem is that I get this error from Xcode when I want to use the value to fill a label:
Cannot assign value of type MyValue? to type String?
If I print su, I get this:
[stockholmsParks.(unknown context at 0x105c3d098).ServiceUnit(description: nil, id: Optional("ShortDescription"), group: Optional("Beskrivning av enheten"), groupDescription: nil, name: Optional("Introduktion"), value: Optional(stockholmsParks.(unknown context at 0x105c3d0e8).MyValue.string("Regnbågen på höjden. Den här lekplatsen ligger på ett högt berg i naturmark, omgiven av höghus. Det finns en instängslad bollplan och olika lekredskap står placerade efter varandra. Utanför själva lekplatsen finns en gungställning. Det finns också bänkbord i sol och grillplats.")))]
What am I missing???
You need to get the associated value from your enum.
let value = su[0].value
switch value {
case .string(let description)?:
yourLabel.text = description
default:
break
}

iOS Swift Filter Array using NSPredicate crashing on run time

I have a cell Model
struct BeerCellModel: Hashable {
var beer: Beer
static func == (lhs: BeerCellModel, rhs: BeerCellModel) -> Bool {
return lhs.beer.id == rhs.beer.id
}
var hashValue: Int {
return self.beer.id
}
}
AND
public struct Beer {
public var abv: String
public var ibu: String
public var id: Int
public var name: String
public var style: String
public var ounces: Int
}
Now i have a array name 'items = [BeerCellModel]' of above cell model and i'm filtering array with param style
let value = ["tuborg", "budwiser", "bira"]
let query = value.map { "SELF.beer.style CONTAINS[cd] \($0)" }.joined(separator: " || ")
let predicate = NSPredicate(format: query)
let results = self.items.filter { predicate.evaluate(with: $0) }
But i'm getting run time crash
2018-08-08 00:38:01.787170+0530 BeerCrafts[3388:401950] ***
Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<_SwiftValue 0x60400064dce0> valueForUndefinedKey:]: this
class is not key value coding-compliant for the key beer.'
What is wrong in array or predicate?
Update: Adding JSON response of Beer
{
"abv": "0.08",
"ibu": "35",
"id": 11,
"name": "Monks Blood",
"style": "Belgian Dark Ale",
"ounces": 12
},
{
"abv": "0.07",
"ibu": "65",
"id": 10,
"name": "Brew Free! or Die IPA",
"style": "American IPA",
"ounces": 12
},
{
"abv": "0.04",
"ibu": "17",
"id": 9,
"name": "Hell or High Watermelon Wheat",
"style": "Fruit / Vegetable Beer",
"ounces": 12
}
At this point, there is no need to deal with NSPredicate, the filter method is sufficient enough for your case, as:
let filtered = items.filter { value.contains($0.beer.style) }
The filtered is array of BeerCellModel which should contains the objects that their beer.style is one of the value array elements.
You can try
let value = ["tuborg", "budwiser", "bira"]
let query = value.map { "self CONTAINS[cd] '\($0)'" }.joined(separator: " || ")
let predicate = NSPredicate(format: query)
let results = self.items.filter { predicate.evaluate(with: $0.beer.style) }
Looks like you're trying to map an array of Strings, not an array of BeerCellModel structs.

swift 4 decode json with unknown root name to table view [duplicate]

This question already has answers here:
Using Codable on a dynamic type/object
(2 answers)
Closed 4 years ago.
I have a json data like this, the root element will be generate by php which data is stored in mysql, and in the future it will be increased or change
{
"Category name 1": [
{
"name": "name 1",
"URL": "http://google.com"
}
],
"Php generated Category name 2": [
{
"name": "name 2",
"URL": "http://google.com"
}
]
}
what I want is i need the category name to be table view section header title so the section row will be listed nicely
however all information that I googled was provided that category name is a fixed name
Thanks in advance
From the data you posted, it looks like you will have this kind of a model:
struct Category: Decodable {
let name: String
let content: [Content]
struct Content: Decodable {
let name: String
let URL: String
}
}
In order to decode the JSON structure to match this model, we will need to do some custom parsing. The first issue we need to address is that we don't know the names of each category in order to parse it, since the key for the category is also the name of it. We will need to introduce a CodingKey that can take any String value, so that it can dynamically load any JSON string key.
/// When encoding/decoding, this struct allows you to dynamically read/create any coding key without knowing the values ahead of time.
struct DynamicCodingKey: CodingKey {
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
We will also need a new type that we can use to handle the custom parsing for the entire JSON list. With that new type, we must implement Decodable.init(from:) to do our custom parsing.
struct CategoryList: Decodable {
let categories: [Category]
// This is the model we created in the first step.
struct Category: Decodable {...}
init(from decoder: Decoder) throws {
// Key the JSON container with our dynamic keys.
let categoriesContainer = try decoder.container(keyedBy: DynamicCodingKey.self)
// The container's keys will be the names of each of the categories.
// We can loop over each key and decode the Content from the JSON for that
// key, then use the key as the name to create our Category.
categories = try categoriesContainer.allKeys.map { key in
let content = try categoriesContainer.decode([Category.Content].self, forKey: key)
return Category(name: key.stringValue, content: content)
}
}
}
With this CategoryList JSON decoding wrapper, we can decode the JSON in a way that fits our model, and use the model to populate the table with sections. Each Category in the CategoryList would be a section, and each Content within the Category would be a row.
let categories = try JSONDecoder().decode(CategoryList.self, from: data).categories
You can try this structure , and set the generated category title inside the key named title
{
"AllCategories": [{
"title":"any1",
"content" : [
{
"name": "name 1",
"URL": "http://google.com"
},
{
"name": "name 1",
"URL": "http://google.com"
}
]
},
{
"title":"any2",
"content" : [
{
"name": "name 1",
"URL": "http://google.com"
},
{
"name": "name 1",
"URL": "http://google.com"
}
]
}
]
}

Why SwiftyJSON cannot parse Array String in swift 3

{
"item": [
{
"pid": 89334,
"productsname": "Long Way",
"address": "B-4/7, Malikha Housing, Yadanar St., Bawa Myint Ward,",
"telephone": "[\"01570269\",\"01572271\"]"
},
{
"pid": 2,
"productsname": "Myanmar Reliance Energy Co., Ltd. (MRE)",
"address": "Bldg, 2, Rm# 5, 1st Flr., Hninsi St., ",
"telephone": "[\"202916\",\"09-73153580\"]"
}
],
"success": true
}
I cannot parse telephone value from above JSON object with following code.
for item in swiftyJsonVar["item"].array! {
if let jsonDict = item.dictionary {
let pid = jsonDict["pid"]!.stringValue
let productsname = jsonDict["productsname"]!.stringValue
var telephones = [String]()
for telephone in (jsonDict["telephone"]?.array)! {
telephones.append(telephone.stringValue)
}
}
}
I want to get and display one by one phone number of above JSON. I'm not sure why above code is not working. Please help me how to solve it, thanks.
Because telephone is a string that looks like an array, not an array itself. The server encoded this array terribly. You need to JSON-ify it again to loop through the list of telephone numbers:
for item in swiftyJsonVar["item"].array! {
if let jsonDict = item.dictionary {
let pid = jsonDict["pid"]!.stringValue
let productsname = jsonDict["productsname"]!.stringValue
var telephones = [String]()
let telephoneData = jsonDict["telephone"]!.stringValue.data(using: .utf8)!
let telephoneJSON = JSON(data: telephoneData)
for telephone in telephoneJSON.arrayValue {
telephones.append(telephone.stringValue)
}
}
}

Resources