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

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"
}
]
}
]
}

Related

Decode KeyValuePairs<String: Person> from JSON

I have a JSON response that looks something like this:
{
"persons": {
"John": {
"name": "John",
"age": 24
},
"Michael": {
"name": "Michael",
"age": 44
},
"Jack": {
"name": "Jack",
"age": 25
}
}
}
As you can see this could be parse with a struct that looks like this:
struct PersonsResponse: Decodable {
let persons: [String: Person]
}
struct Person: Decodable {
let name: String
let age: Ing
}
However, what this does, is returns a dictionary and parses fine. What I would need is to preserve the order of the persons as they arrive inside this JSON response. I have come across KeyValuePairs in swift which basically are ordered dictionaries but for the love of God I can't figure out how to decode it into being an KeyValuePairs<String, Person>.
Apple documentation says that instantiating a KeyValuePairs object is as easy as doing:
let recordTimes: KeyValuePairs = ["Florence Griffith-Joyner": 10.49,
"Evelyn Ashford": 10.76,
"Evelyn Ashford": 10.79,
"Marlies Gohr": 10.81]
Literally. But when I am decoding my response with:
struct PersonsResponse: Decodable {
let persons: KeyValuePairs<String, Person>
...
...
init(from decoder: Decoder) throws {
...
let personsDictionary = try container.decode([String: Person].self, forKey: .persons)
and then try to do:
persons = personsDictionary
of course it doesn't work at all. I tried to do all kinds of magic already with no luck. Does anyone have any solution to parsing dictionaries into ordered sequences or even Arrays? Thanks for helping!
There is NO WAY the JSON response changes into being an array and YES it always is the same order.
Both Swift, JSON Dictionaries are unordered by there nature. The JSON format does notmaintain key ordering, and as such, does not required parser to preserve the order.
If you need an ordered collection, you its better to returning an array of key-value pairs in the JSON
{
"persons": [
{ "John": {
"name": "John",
"age": 24
}
},
{"Michael": {
"name": "Michael",
"age": 44
}
},
{ "Jack": {
"name": "Jack",
"age": 25
}
}
]
}

Get All Values of Document Fields Without The Need For The Field Name

In swift I have the following array:
var postalCode = [String]()
in Firestore I will have many documents in the future with many fields in each that will include postalCodes. Something like this:
Now, how can I get the values of all documents in my collection (collection is called deliveryPostalCodes) regardless of the name of the field? Because in the future I will have to add many fields in here and I can estimate how many documents and fields I will create and what their names would be. So I want to get only the values which in this example would be something like M8V and store it in my variable var postalCode = [String](). I have the following code; but in this I have to call out what field name should be (code1):
db.collection(DatabaseRef.deliveryPostalCodes).getDocuments { (snap, error) in
if let error = error{
debugPrint(error.localizedDescription)
return
}
snap?.documents.forEach({ (doc) in
let code1 = doc[DatabaseRef.code1] as? String ?? ""
})
}
how can I do this without calling the field name?
You can get all fields in the document by calling its DocumentSnapshot.data() method, which returns a [String: Any]. After that, you can get just get the values from the map (dropping the keys), and then filter the resulting array to remove the empty values.
It would look something like this:
snap?.documents.forEach({ (doc) in
let data = doc.data()
let nonEmptyValues = (Array(data.values) as! [String]).filter { !$0.isEmpty }
...
})
I always find it easiest to test this type of thing in an online playground, such as http://online.swiftplayground.run/. My test bed for this answer:
import Foundation
let data:[String:Any] = ["key1": "value1", "key2": "42", "key3": "", "key4": "value4"]
print(data)
print(data.values)
print((Array(data.values) as! [String]).filter { !$0.isEmpty })
Which prints:
["key3": "", "key2": "42", "key1": "value1", "key4": "value4"]
["", "42", "value1", "value4"]
["42", "value1", "value4"]

How can I decode this json with Alamofire? [duplicate]

This question already has answers here:
How to parse a JSON file in swift?
(18 answers)
Closed 3 years ago.
I want to print just value for key "User_number"
[
{
"User_fullname": null,
"User_sheba": null,
"User_modifiedAT": "2019-01-31T18:37:02.716Z",
"_id": "5c53404e91fc822c80e75d23",
"User_number": "9385969339",
"User_code": "45VPMND"
}
]
I suppose this is some JSON in Data format
let data = Data("""
[ { "User_fullname": null, "User_sheba": null, "User_modifiedAT": "2019-01-31T18:37:02.716Z", "_id": "5c53404e91fc822c80e75d23", "User_number": "9385969339", "User_code": "45VPMND" } ]
""".utf8)
One way is using SwiftyJSON library, but, this is something what I don't suggest since you can use Codable.
So, first you need custom struct conforming to Decodable (note that these CodingKeys are here to change key of object inside json to name of property of your struct)
struct User: Decodable {
let fullname, sheba: String? // these properties can be `nil`
let modifiedAt, id, number, code: String // note that all your properties are actually `String` or `String?`
enum CodingKeys: String, CodingKey {
case fullname = "User_fullname"
case sheba = "User_sheba"
case modifiedAt = "User_modifiedAT"
case id = "_id"
case number = "User_number"
case code = "User_code"
}
}
then decode your json using JSONDecoder
do {
let users = try JSONDecoder().decode([User].self, from: data)
} catch { print(error) }
So, now you have Data decoded as array of your custom model. So if you want to, you can just get certain User and its number property
let user = users[0]
let number = user.number
The following code takes takes in Data and saves "User_number" as an Int
if let json = try? JSONSerialization.jsonObject(with: Data!, options: []) as! NSDictionary {
let User_number= json["User_number"] as! Int
}

Sort an array of object using string parameter in Swift3 iOS

I have to sort an array of object using one parameter. I have created an entity, below is my JSON :
{
"Name": "Amit",
"Progress": "38",
"Color": "red",
},
{
"Name": "Sonu",
"Progress": "70",
"Color": "green",
}
Below is my entity class:
class Scoreboard {
// MARK: - Properties
var scoreboardName: String? = ""
var scoreboardProgress: String? = "0"
var scoreboardColor: String? = ""
}
This JSON data I have pass into table, The problem is How can I do sorting based on 'Progress'?
I tried below code:
scoreboardArray = scoreboardArray.sorted(by: {($0.scoreboardProgress() < $1.scoreboardProgress())})
But it's not working. Please anyone suggest me.
Can you try
scoreboardArray = scoreboardArray.sorted {
Int($0.scoreboardProgress!)! < Int($1.scoreboardProgress!)!
}
also if you assign a default value , it's better to remove ?
Irrelevant but why not use Decodable
class Scoreboard : Decodable {

parsing array in array into some arrays in swift 4?

I used parsing son in my codes for news api
the son is some thing like this
> {
"status": "ok",
"source": "associated-press",
"sortBy": "top",
-"articles": [
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
"title": "Pro-Trump states most affected by his health care decision",
"description": "President Donald Trump's decision to end a provision of the Affordable Care Act that was benefiting roughly 6 million Americans helps fulfill a campaign promise",
"url": "https:urlexample",
"urlToImage": "url example",
},
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
"title": "Pro-Trump states most affected by his health care decision",
"description": "President Donald Trump's decision to end a provision of the Affordable Care Act that was benefiting roughly 6 million Americans helps fulfill a campaign promise",
"url": "https:urlexample",
"urlToImage": "url example",
},
]
}
as you see in each array we have title - description and more
I want to parse this jason into separated array for example append all of the titles in one array and append all of the descriptions in another ones and more
here is my code
struct Response : Decodable {
let articles: articles
}
struct articles: Decodable {
let title: String
let description : String
let url : String
let urlToImage : String
}
and here is the codes for json
let jsonUrl = "https://newsapi.org/[your codes]"
guard let url = URL(string : jsonUrl) else {
return
}
URLSession.shared.dataTask(with: url) { (data , response , error) in
guard let data = data else {return}
do {
let article = try JSONDecoder().decode(Response.self , from : data)
print(article.articles.title)
print(article.articles.description)
print(article.articles.url)
print(article.articles.urlToImage)
}
catch {
print(error)
}
}.resume()
and when I run this I will receive this error
"Expected to decode Dictionary but found an array instead.", underlyingError: nil))
First, to distinguish between property/method names and type names, try to follow Swift naming conventions: (the following is from Swift API Design guidelines)
Names of types and protocols are UpperCamelCase. Everything else is lowerCamelCase.
Also, your articles struct represents data for just one article, not multiple. So it should start with a capital A, and be singular:
struct Article: Decodable {
Secondly, if you take another look at the JSON you're getting back, articles is an array of dictionaries:
-"articles": [
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
...
},
-{
"author": "CHRISTINA A. CASSIDY and MEGHAN HOYER",
...
},
So the articles property in your Response struct should be an array of Article.
struct Response : Decodable {
let articles: [Article]
}

Resources