Parsing JSON file using Swift - ios

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.

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.

Swift 5: getting several values out of JSON with array root object

I (a complete rookie) am currently trying to create my first iOS app - a currency table/converter for the currency of my country, Ukrainian Hryvna. I have created a TableView that I am going to fill with data from JSON file from the following link:
Tap here
The file itself has Array root. Like that:
[
{
"r030":36,"txt":"Австралійський долар","rate":21.334,"cc":"AUD","exchangedate":"23.12.2020"
}
,{
"r030":124,"txt":"Канадський долар","rate":21.9334,"cc":"CAD","exchangedate":"23.12.2020"
}
,{
"r030":156,"txt":"Юань Женьміньбі","rate":4.3192,"cc":"CNY","exchangedate":"23.12.2020"
}
,{
"r030":191,"txt":"Куна","rate":4.5833,"cc":"HRK","exchangedate":"23.12.2020"
}]
I want to get a create a Dictionary out of this file using just two values: [cc: rate] and then fill my TableView with this data. I don't care for other values.
Something like that:
["AUD": 21.334, "CAD": 21.9334]
Should I use some other data type to store this data? A Struct representing a currency and then make an Array of currency Structs, perhaps?
How do I get this file from that URL and make such a Dictionary/Struct array/...?
Thank you so much in advance :)
Make a struct
struct Currency : Decodable {
let name : String
let rate : Double
private enum CodingKeys : String, CodingKey { case name = "cc", rate }
}
Then load the data with URLSession and decode the JSON array to [Currency] with JSONDecoder (there are zillions of examples of both).

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

Converting from snake case to camel case using keyDecodingStrategy

I am trying to use Codable to automatically parse incoming JSON into my models. It is working fine, but then I learnt about _keyDecodingStrategy_, and wanted to use this. It is working great and able to lessen my code as I do not have to write CodingKeys enum for my models.
But now the problem is a new variable sent from server. The variable is post_url_110x110.
I thought it will convert to postUrl110x110, but it doesn't. Do help me into its camelCase conversion or suggest if I should avoid automatic conversion in this case.
It will work for you if you rename your data model property from postUrl110x110 to postUrl110X110 with capital X. I know it's not ideal solution but it's worth noting. Check the example below:
struct DataItem: Codable {
var itemId: String
var postUrl110X110: String
}
let json = """
{
"item_id": "abcd",
"post_url_110x110": "https://example.org/image.png"
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
print(try! decoder.decode(DataItem.self, from: json))
You can try this tool.
Sample input
Output
If you need further assistance then let me know.

Can't retrieve the info after downloading this data

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"

Resources