Swift JSON response two field values append into single array - ios

I am trying to validated students == null or values avilable, If values avilable I need to get grade and store grade into table data array and subject null also I need to store in same array For example: [10, null, 11] from below JSON. how to append like this in single array from JSON response.
{
"students":[
{
"id":0,
"subject":[
{
"grade":10
}
]
},
{
"id":1,
"subject":null
},
{
"id":2,
"subject":[
{
"grade":11
}
]
}
]
}
Expected output: [10,null,11,......] //This array I am going to use Tableview cell
I am validating based on null and not null array values within cell for row. I can use var array = [String?] for accepting null values but how to append two different field result into same array?

You should take a look into the 'Codable' protocol.
By simply defining a struct like:
struct Student: Codable
you can decode it from JSON into these objects.
See for example: hackernoon or grokswift

This looks like a trivial scenario. Best solution is Decodable. Your payload loaded from network or whatever will be parsed into structure. Now you can easily make any manipulations.
Setup: Open a new project. Add "payload.json" file with json payload you provided in question.
Add the following to your project.
import UIKit
struct StudentData: Decodable {
var students: [Student]
}
struct Student: Decodable {
var id: Int
var subject: [Subject]?
}
struct Subject: Decodable {
var grade: Int
}
class ViewController: UIViewController {
var data: Data? {
guard let path = Bundle(for: type(of: self)).path(forResource: "payload", ofType: "json") else { return nil }
return try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
}
override func viewDidLoad() {
super.viewDidLoad()
if let data = data {
do {
let studentData = try JSONDecoder().decode(StudentData.self, from: data)
print(studentData)
// manipulate the structure in any way you want
let subjects: [Subject?] = studentData.students.map { $0.subject?.first }
print(subjects)
let nonNilValues = subjects.compactMap { $0 }
print(subjects)
// ... etc
} catch let error {
print(error.localizedDescription)
}
}
}
}
Sorry for not coding in playgrounds. It's way too buggy.

Try this
let students = [["id": 0,"subject": [["grade": 10]]],
["id": 0,"subject": nil],
["id": 0,"subject": [["grade": 10]]]] as! [Dictionary<String,Any>]
let array = students.map({(($0["subject"] as? [Any])?.first as? Dictionary<String,Int>)?["grade"]})
print(array)

Related

How to create a Swift model for the JSON file with dynamic key?

I am trying to write a Swift Codable model for the below JSON.
{
"batchcomplete": "",
"query": {
"pages": {
"26667" (The problem is here): {
"pageid": 26667,
"ns": 0,
"title": "Spain",
"contentmodel": "wikitext",
"pagelanguage": "en",
"pagelanguagehtmlcode": "en",
"pagelanguagedir": "ltr",
"touched": "2020-03-14T18:03:48Z",
"lastrevid": 945549863,
"length": 254911,
"fullurl": "https://en.wikipedia.org/wiki/Spain",
"editurl": "https://en.wikipedia.org/w/index.php?title=Spain&action=edit",
"canonicalurl": "https://en.wikipedia.org/wiki/Spain"
}
}
}
}
The problem is that one of the key changes every time I query.
Marked the in the above JSON as (The problem is here)
How to parse the above JSON file with JSONDecoder?
This can be easily parsed with libraries like SwiftyJSON.
The point is in making let pages: [String:Item] Use
// MARK: - Root
struct Root: Codable {
let batchcomplete: String
let query: Query
}
// MARK: - Query
struct Query: Codable {
let pages: [String:Item]
}
// MARK: - Item
struct Item: Codable {
let pageid, ns: Int
let title, contentmodel, pagelanguage, pagelanguagehtmlcode: String
let pagelanguagedir: String
let touched: Date
let lastrevid, length: Int
let fullurl: String
let editurl: String
let canonicalurl: String
}
let res = try JSONDecoder().decode(Root.self, from: data)

How to parse through JSON with [] brackets?

I'm parsing through JSON on a website like so (under a request.httpMethod = "GET" in Swift
):
let example = json.data.first?.TShirtPrice
The JSON I'm getting is structured like so
{"returned":1,"data":[{"TShirtPrice":"5"}]}
But I have a new JSON set that is structured without [] brackets like so:
{"returned":1,"base":"USD","data":{"TShirtPrice":"3.448500"}}
The same exact code doesn't let me get the price of the shirt anymore -- what is the fix? Thank you!
This is my code
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let price = json.data.first?.TShirtPrice
struct Root: Codable {
let data: [Datum]
}
struct Datum: Codable {
let TShirtPrice: String
}
Assuming your data model is something as follows You can be using Struct or Class it's not an issue.
struct Root: Decodable {
let returned: Int?
let base: String?
let data: Price?
}
struct Price: Codable {
let TShirtPrice: String?
}
Sample JSON Sting is as follows
let jsonString = """
{
"returned": 1,
"base": "USD",
"data": {
"TShirtPrice": "3.448500"
}
}
"""
You just have to change the way data is parsed by making change in data model as given above and way to access the data as given below
if let data = jsonString.data(using: .utf8) {
let myObject = try JSONDecoder().decode(Root.self, from: data)
print(myObject.data?.TShirtPrice)
}
In your case it will look like this
if let data = data {
do {
let json = try JSONDecoder().decode(Root.self,from: data)
let Price = json.data?.TShirtPrice
}
}
What is changed here?
As your price data was in format of Array the code was written accordingly and as per new data it's not an Array anymore so you have to adapt those changes app side also.

how to add Json value into model Array to display into tableview in swift

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

Swift: Decodable

Lets say i have this json from an API request:
friends: {
"john":31,
"mark":27,
"lisa":17,
"tom":41
}
I usually expect it in an array format:
friends: [
{ "john":31 },
{ "mark":27 },
{ "lisa":17 },
{ "tom":41 }
]
But the API doesn't provide me this way the results. So i want finally to map it to an array of [Friend], where Friend is:
class Friend: Decodable {
let name: String
let age: Int
}
How should i serialize this json to get [Friend] ?
First of all, example isn't valid json at all. To be valid it either shouldn't include "friends" label, or it should be embedded in another object like this
{
"friends": {
"john":31,
"mark":27,
"lisa":17,
"tom":41
}
}
If I understand question correctly, you want to decode json object to swift array. I don't think there is a way to do so without writing custom decoding. Instead, you can decode json into Dictionary and when manually map it like so
struct Friend {
let name: String
let age: Int
}
struct Friends: Decodable {
let friends: [String: Int]
}
let friends = try! JSONDecoder().decode(Friends.self, from: json.data(using: .utf8)!)
.friends
.map { (name, age) in Friend(name: name, age: age) }
Disclaimer: I recommend to change you API format to one like in #scriptable's answer (which was deleted while I was answering, hm), where name and age fields are properly defined. And where you're not limited to basically a pair of key-value to parse.
But if you can't or won't change it you may use something like this to decode your Friend type:
struct Friend: Decodable {
let name: String
let age: UInt
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dictionary = try container.decode([String: UInt].self)
guard dictionary.count == 1, let (name, age) = dictionary.first else {
throw DecodingError.invalidFriendDictionary
}
self.name = name
self.age = age
}
enum DecodingError: Error {
case invalidFriendDictionary
}
}
struct Friends: Decodable {
let friends: [Friend]
}
let friends = try JSONDecoder().decode(Friends.self, from: data)
It assumes key is name and value is age. And checks that there's only a one pair to parse.

How to parse such type array?

I got the next when I'm trying to do:
for item in serverResponse {
print(item)
}
it gives me:
("0", {
"id" : 1,
"name" : "Programming"
})
("1", {
"id" : 2,
"name" : "Music"
})
How can I parse this array to take name there? I've confused with it =/
UPDATE
The full response is:
{
"name": "ABC",
"books": [
{
"id": 1,
"name": "Programming"
}
{
"id": 2,
"name": "Music"
}
]
}
You are looking to convert Json to an object to read name. Crude solution would be to parse for name. Better way of doing it is use SwiftyJson or ObjectMapper.
If you get Json from your server you can use it:
func getJSON(urlToRequest: String) -> NSData{
return NSData(contentsOfURL: NSURL(string: urlToRequest)!)!
}
func parseJSON(inputData: NSData) -> NSArray{
let boardsDictionary = try! NSJSONSerialization.JSONObjectWithData(inputData, options: .AllowFragments) as! NSArray
return boardsDictionary
}
Then use myData = parseJSON(json) and you can access name with
let data = myData[0] as! NSDictionary
let name = data["name"]! as? String
for item in serverResponse {
let itemArray = (item as? NSDictionary)["books"] as? NSArray
for (var i = 0; i < itemArray.count ; i++ ) {
if let dic1 = itemArray[i] as? NSDictionary {
let item: String!
item = dic1["name"] as? String
print(item)
}
}
}
I don't like dealing with dictionaries in Swift. I find that objects behave better and are easier to manage - they're also safer.
So my suggestion is to parse your response into "Book" structs, then you can access the properties of these structs (id, name) easily.
It could be even better to make a class that does all the work.
For testing purposes I've reproduced your response like this:
let response: NSDictionary = ["name": "ABC", "books": [["id": 1, "name": "Programming"], ["id": 2, "name": "Music"]]]
Now let's first make our Book struct.
It will have the two properties, and also an initializer which will decode the content of each dictionary that's located in the array in the "books" key of your response. The initializer is failable because the content might not be decodable.
struct Book {
let id: Int
let name: String
init?(_ object: AnyObject) {
if let dictionary = object as? NSDictionary,
dictId = dictionary["id"] as? Int,
dictName = dictionary["name"] as? String {
self.id = dictId
self.name = dictName
} else {
return nil
}
}
}
Now we can make a class that will hold all the books. We need another initializer for that, which will iterate over the contents and create the objects with the structs:
class BooksManager {
var books = [Book]()
init(response: NSDictionary) {
if let array = response["books"] as? NSArray {
self.books = array.flatMap { Book($0) }
}
}
}
Ok, now we have finished writing the boilerplate code, we can use our toys.
Create an instance of the class:
let manager = BooksManager(response: response)
That's it! Now the class holds the objects and you can easily access their properties.
For example with a loop:
for book in manager.books {
print("Book id: \(book.id), book name: \(book.name)")
}
Result:
Book id: 1, book name: Programming
Book id: 2, book name: Music
And let's say you want the name of the first book:
if let nameOfFirstBook = manager.books.first?.name {
print(nameOfFirstBook)
}
Result:
Programming
Last example, let's say you want to collect all books names in an array:
let booksNames = manager.books.map { $0.name }
Result:
["Programming", "Music"]

Resources