Can't retrieve the info after downloading this data - ios

I'm trying to access the data downloaded from a website which looks like this:
{"quizzes":[
{"id":1, "question": "Can't solve this :("},
{"id":2, "question": "Someone help pls"}]}
The data downloaded looks way more complex, with more values, more keys, and some keys being associated to another dictionary of Key:String, but since I can't even access the most simple fields I thought I would start with this.
I'm using JSONSerialization, so my question is:
If I want to create a variable where i can save the downloaded data... Which would be it's type? I would say [String:[String:Any]], but I'm not sure if "quizzes" represents a key on this specific key, since the data starts with '{' and not with '['.

Instead of using JSONSerialization one could use JSONDecoder.
Example
struct Question: Decodable {
let id: Int
let question: String
}
struct Quiz: Decodable {
let quizzes: [Question]
}
Assuming jsonStr is a string with the JSON in your question:
if let jsonData = jsonStr.data(using: .utf8) {
if let quiz = try? JSONDecoder().decode(Quiz.self, from: jsonData) {
for question in quiz.quizzes {
print("\(question.id) \"\(question.question)\"")
}
}
}
Output
1 "Can't solve this :("
2 "Someone help pls"

Related

Decode JSON array inside a JSON dictionary without creating Boilerplate Types

The JSON:
let jsonString = """
{
"groups": [
{
"id": "oruoiru",
"testProp": "rhorir",
"name": "* C-Level",
"description": "C-Level"
},
{
"id": "seese",
"testProp": "seses",
"name": "CDLevel",
"description": "CDLevel"
}
],
"totalCount": 41
}
"""
Type:
struct Group: Codable {
var id: String
var name: String
}
I would like to decode this JSON to only output an array of Group type without having to create boilerplate type like:
struct GroupsResponse: Codable {
var groups: [Group]
var totalCount: Int
}
and use:
let data = jsonString.data(using: .utf8)
let decoded = try! JSONDecoder().decode([Group].self, from: data!)
I tried getting the containers from inside the initialiser of the Group type, but the program already crashes outside at the decoding line with Swift.DecodingError.typeMismatch error
One solution that does work is doing something like:
let topLevel = try! JSONSerialization.jsonObject(with: data) as? [String: Any]
let groupsNode = topLevel?["Groups"] as? [[String: Any]]
let groups = try! JSONSerialization.data(withJSONObject: groupsNode!)
let decoded = try! JSONDecoder().decode([Group].self, from: groups)
but this seems very hacky. Is there an elegant way to handle this?
You cannot avoid the top level response struct using JSONDecoder. There has to be a type for it to work on. And you can't use Dictionary as the top level object (ie [String: [Group]]), since there's a totalCount field that doesn't have an array of Group. All the comments are correct. Just write the little wrapper. It's one line long:
struct GroupsResponse: Codable { var groups: [Group] }
There's no need to decode fields you don't care about.
But you said "for education," and it's all code, so of course you can replace JSONDecoder with something that can do this. You tried to do that with NSJSONSerialization, but that's extremely clunky. You can also just write your own version of JSONDecoder, and then do it like this:
let decoder = RNJSONDecoder()
let response = try decoder.decode(JSON.self, from: json)
let groups = try decoder.decode([Group].self, from: response.groups)
This avoids any re-encoding (RNJSONDecoder can decode directly from a JSON data structure; it doesn't have to convert it back to Data first). It also requires about 2600 lines of incredibly tedious boilerplate code, mostly copy and pasted out of stdlib. Implementing your own Decoder implementation is obnoxious.
If you wanted to get fancier, you could scan the data for the section that corresponds to the property you want, and decode just that part. While implementing the Decoder protocol is very hard, parsing JSON is quite straight-forward. I'm currently doing a bunch of JSON experiments, so I may try writing that and I'll update this if I do.
But the answers are: "just write the tiny, simple, fast, easy to understand response wrapper," or "replace JSONDecoder with something more powerful."
UPDATE:
I went ahead and built the scanner I mentioned, just to show how it could work. It's still a bit rough, but it allows things like:
let scanner = JSONScanner()
let groupJSON = try scanner.extractData(from: Data(jsonString.utf8),
forPath: ["groups", 1])
let group = try JSONDecoder().decode(Group.self, from: groupJSON)
XCTAssertEqual(group.id, "seese")
So you can just extract the part of the data you want to parse, and not worry about parsing the rest.

How to model data from JSON array with no property name into swift so It can be parsed? SWIFT

Im stuck trying to model a JSON array which has no property name into my swift project with the goal of parsing the data and using it in my app. I know how to do this when there is a NAME for the array but I don't know how to make swift and this lackluster JSON understand each other. The path to the first "Company" in the JSON is "0.Company". The error I get is "Value of type 'WorkData' has no member '0'"
Im including pictures of my full project so it is easier to understand the structure of the code and what im trying to do. Please look at the picture for a clearer understanding I apologize if Im not explaining it well i'm new to programming.
import Foundation
class WorkData: Codable {
let WorkData: [WorkData]
let Company: String
let worklogDate: String
let issue: String
}
func parseData(jsonDataInput: Data) {
let decoder = JSONDecoder() // an object that decodes JSON data
do {
let decodedData = try decoder.decode(WorkData.self, from: jsonDataInput)
let Company = decodedData.0.Company
let worklogDate = decodedData.0.worklogDate
let issue = decodedData.0.issue
} catch {
print (error)
}
}
}
json
Trying to model JSON in Swift
Parsing JSON
You cannot start JSON with an array because JSON itself is an object {}
See example below:
{
"WorkData" : [
{"Company" : ""},
{"Company" : ""},
{"Company" : ""}
]
}
let decodedData = try decoder.decode(LocalizationsResponse.self, from: jsonDataInput)
decodedData will be an array

Parsing JSON file using Swift

Here is the general structure of a team from our JSON file:
{"Team11":{
"results":{
"leg1":[
{"g":"m","n":"Name1"}
],"leg2":[
{"g":"m","n":"Name2"}
],"leg3":[
{"g":"m","n":"Name3"}
],"leg4":[
{"g":"m","n":"Name4"}
],"leg5":[
{"g":"m","n":"Name5"}
],"leg6":[
{"g":"m","n":"Name6"}
],"leg7":[
{"g":"m","n":"Name7"},{"g":"m","n":"Name8"}
]
},"tn":"TeamName",
"division":"co"
}
}
So far we are able to parse up into results categories leg1, leg2, etc. Accessing the info contained in the bracket arrays has not worked so far.
My current idea on why it is failing is because we are storing the JSON teams incorrectly via String:Any.
My other theory is I just haven't been able to find the correct documentation. Any pointers on where to look or tips would be huge!
Make sure to add What hasn't worked for you and what you have tried? As a New contributor you need to learn how to post questions. Give it a try with my below answer.
Use Codable to parse the JSON like below,
let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
// Welcome
struct Welcome: Codable {
let team11: Team11
enum CodingKeys: String, CodingKey {
case team11 = "Team11"
}
}
// Team11
struct Team11: Codable {
let results: [String: [Result]]
let tn, division: String
}
// Result
struct Result: Codable {
let g, n: String
}
Note: Your JSON is missing open and ending curly brackets, and I've updated that in your question.
Are you trying to parse the JSON manually? I'm not sure where you're at with your code, but the standard way to parse a JSON string into an object is this:
let jsonData = myJSONString.data(using: .utf8)
There is an issue with your JSON though. You can validate a JSON file on this link.

How to insert and delete objects in json file swift

So I have this json which has an array of some objects.
[
{
"id": 1,
"title": "First Object"
},
{
"id": 2,
"title": "Second Object"
},
{
"id": 3,
"title": "Third Object"
}
]
I'm parsing the json with the following code,
struct MyModel: Codable {
let id: Int?
let title: String?
}
var myModel = [MyModel]()
func decodeData(url: URL) {
do {
let jsonData = try Data(contentsOf: url)
let decoder = JSONDecoder()
myModel = try decoder.decode([MyModel].self, from: jsonData)
} catch let jsonError {
print("Error serializing json", jsonError)
}
}
Everything works fine when it comes to reading the json. What I can seem to figure out is how to delete and insert object into the same json file.
For example, delete object with "id"=2, or insert a new object after object with "id"=3 or between objects with "id"=1 and "id"=2.
What I was thinking was to read the entire json file into an array. Then modify the data in the array by deleting and appending elements in the array then overwriting the json file with everything in the modified array.
For some reason this approach doesn't seem practical. Might be fine for a small numbers of objects but what happens if/when the number of objects reach a number in the 100+ range.
Am I taking the right approach by reading the contents of the json file into an array then modifying and overwriting the json file with the contents of the modify array or is there a proper way of achieving this?
You can delete any object from Dictionary using this piece of code, convert json to Dictionary.
if let index = values.index(forKey: id) {
values.remove(at: index)
}
and in the same way you can get the index of particular object where you want to insert new object.

How to pass model class data from one page another in iOS?

Here I won't have any connection for both the pages and here I need to save the model class globally and to use anywhere in all pages till app was in use and after it may can clear the data having in array but I can able to access anywhere in all pages in app and I tried using to save in UserDefaults it crashed. Can anyone help me how to implement this?
var sortModel = [Sort]()
for (_, value) in sortJson as! [String: Any] {
self.sortModel.append(Sort.init(dict: value as! [String : Any]))
}
UserDefaults.standard.set(self.sortModel, forKey: "sorts")
Get your Sort struct to conform to Codable like:
struct Sort: Codable {
//...
}
then you can quickly get away with:
//Encode Sort to json data
if let jsonData = try? JSONEncoder().encode(sortModel) {
print("To Save:", jsonData)
//Save as data
UserDefaults.standard.set(jsonData,
forKey: "sorts")
}
//Read data for "sorts" key and decode to array of "Sort" structs
if let data = UserDefaults.standard.data(forKey: "sorts"),
let sorts = try? JSONDecoder().decode([Sort].self,
from: data) {
print("Retrieved:", sorts)
}
Basically, we make a json out of the array and save it as a data object.
We then can get it back as data and recreate the sort struct array.
NOTE: This may not work if the struct has nested within itself some types that prevent it from getting encoded as a json.
In this case, read:
http://swiftandpainless.com/nscoding-and-swift-structs

Resources