Function for reading in JSON files - json

I am both new to stackoverflow as well as Swift programming (coming from statistics/R), so please bear with me in case I'm doing something super wrong. Ok, lets get to my problem: I want to read in multiple JSON files, e.g. metrics.json and accounts.json. After having read various posts on that topic, I have now written the following function and created one struct for each of the JSON files, replicating its structure:
func readJsonFile(fileName: String) -> [metric]? {
var result: [metric]?
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path))
result = try JSONDecoder().decode([metric].self, from: jsonData)
} catch {
fatalError("Unresolved error when loading JSON file \(fileName): \(error)")
}
}
return result
}
struct Metric: Codable {
let METRIC_ID: Int
let METRIC_NAME: String
}
struct Account: Codable {
let ACCOUNT_ID: Int
let ACCOUNT_NAME: String
}
Now what I was trying to achieve is somehow provide not just the filename to the function but also somehow the respective struct as a blueprint. Also, I would probably need to dynamically change the function output. It then should like somehow like that:
let metrics = readJsonFile("metrics", Metric)
let accounts = readJsonFile("accounts", Account)
I have a feeling that might be an easy thing if one knows his way around. Unfortunately, I do not. Can someone help me with any suggestions? Or should I in general take a different approach? Also, if there is anything else odd or wrong in the code, happy to receive any constructive feedback. Thanks guys.

I think what I would do is make the function generic over Codable, like this:
enum MyError:Error { case noSuchFile }
func readJsonFile<T:Codable>(fileName: String, type:T.Type) throws -> [T] {
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
let jsonData = try Data(contentsOf: URL(fileURLWithPath: path))
return try JSONDecoder().decode([T].self, from: jsonData)
}
throw MyError.noSuchFile
}
Now you can call it for either file and either struct:
let x = try? self.readJsonFile(fileName:"metric", type:Metric.self)
let y = try? self.readJsonFile(fileName:"account", type:Account.self)
I've revised your function to throw when there's an error, and I count the lack of the appropriate file as an error. The outcome of using try? in the call is that you get an Optional which is nil if things went badly and is an array of the correct struct type if things went well; if you don't like that, you can use do/catch instead of course, or even try! if you're dead sure this will always work (you should be, they are your files after all).

Related

JSON to dict with class

I decide some JSON and try to typecast it to a dictionary of String: classy and it fails. I have found that often the reason I have trouble doing something is because of a misunderstanding of how Swift works, so here is what I want to happen. Feel free to tell me that I am doing it wrong and if I do it this way all will be wonderful.
I want my data to live between runs of the app so I have to save the data to storage between runs. I have an object, data and associated code, and I have places where changes I make to a copy should reflect back to the original so it is a class. I have a bunch of these objects and most of the time I pick the one I want based on an id that is an integer. An array is not good since it would be a sparse array cause come ids are not used. I came up with a dictionary with a key of the id and data of the structure. I turned the key from an Int to a String, by changing the Int id to a String, cause converting a dictionary to JSON is MUCH easier for a key that is a string. I save the JSON string. When the app starts again I read the string in and convert the JSON string to Any. Then I typecast the result to the desired dictionary. This is where it fails. The cast does not work. In my Googling the samples I found said this should work.
Here is my code:
class Y: Codable, Hashable {
var a: String = "c"
static func ==(lhs: Y, rhs: Y) -> Bool {
return lhs.a == rhs.a
}
func hash(into hasher: inout Hasher) {
hasher.combine(a)
}
}
struct ContentView: View {
var body: some View {
VStack {
Button ("Error") {
var y = Y()
var yDict = [String: Y]()
yDict["0"] = y
do {
let encodedData = try JSONEncoder().encode(yDict)
let jsonString = String(data: encodedData, encoding: .utf8)
let decoded = try JSONSerialization.jsonObject(with: encodedData, options: [])
if let yyDictDec = decoded as? [String:Y] {
print("yDict after decide")
print (yyDictDec)
}
} catch {
print(error.localizedDescription)
}
print("x")
}
}
}
}
In this code the if yyDictDec = is failing, I think, cause the prints after it never happen. I can cast it as [String, Any] but I really need it to be my class.
My problem is in the convert JSON back to the dictionary. I feel I am missing something fairly simple.
DonĀ“t use JSONSerialization use JsonDecoder and decode it to the the type it was before encoding. e.g.:
let decoded = try JSONDecoder().decode([String: Y].self, from: encodedData)

Using decoded JSON in SwiftUI

I have just started with Swift so please excuse if this is just a stupid question. I am working on my first App which should load a JSON from the web, parses and displays its content to a LazyVGrid. I have started with the apple tutorial which uses a local JSON and everything worked fine.
Now I have changed to the original nested JSON and URL and I can see the output on the console, but I simply do not know how to use it in my LazyVGrid now. I did a lot of research but all good tutorials end with the successful parsing. I ended up with:
func fetchData()
{
let url = URL(string: "https://myjsonurl")!
URLSession.shared.dataTask(with: url) {(json, response, error) in
guard let json = json else {
return
}
let welcome = try! JSONDecoder().decode(Welcome.self, from: json)
print(welcome)
print(welcome.data.data.results[0].title)
}.resume()
}
So as mentioned print() gives me the whole JSON or even specific values like the title. But how can I loop this to my LazyVGrid now?(Xcode is complaining that can not reach it or it is not identifiable because ID is only all the way down in the nested array structure...)
Do I have to create a new array first because the welcome thing is not available outside the fetchData() function? How should it look like to keep the whole JSON structure? I guess all of my experiments have been far too complicated.
I would highly appreciate if someone could give me a hint or an example.
Just in case you need the structure:
// MARK: - Welcome
struct Welcome: Codable {
var data: WelcomeData
}
// MARK: - WelcomeData
struct WelcomeData: Codable {
var success: Bool
var data: DataData
}
// MARK: - DataData
struct DataData: Codable {
var results: [Result]
}
// MARK: - Result
struct Result: Codable {
var id, title, alias, introtext: String
var fulltext, publishUp: String
enum CodingKeys: String, CodingKey {
case id, title, alias, introtext, fulltext
case publishUp = "publish_up"
}
}

Value of type ' ' has no subscripts - JSON data

I have looked at the other answers for similar questions to this but can not find one that explains why this is coming up when requesting data from API. I am mostly confused because the error occurs when I copy the path from the JSON. After reading the error code and the documentation, I think it is something to do with an expected dictionary but found an array instead. I don't understand how to fix this when the data is coming from an API request.
Here is the JSON data that I am aiming to pull from:
Here is a picture of the error:
Here the code I have so far:
import Foundation
struct VaccineManager {
let vaccineURL = "https://coronavirus.data.gov.uk/api/v1/data?filters=areaType=overview&structure=%7B%22areaType%22:%22areaType%22,%22areaName%22:%22areaName%22,%22areaCode%22:%22areaCode%22,%22date%22:%22date%22,%22newPeopleVaccinatedFirstDoseByPublishDate%22:%22newPeopleVaccinatedFirstDoseByPublishDate%22,%22newPeopleVaccinatedSecondDoseByPublishDate%22:%22newPeopleVaccinatedSecondDoseByPublishDate%22,%22cumPeopleVaccinatedFirstDoseByPublishDate%22:%22cumPeopleVaccinatedFirstDoseByPublishDate%22,%22cumPeopleVaccinatedSecondDoseByPublishDate%22:%22cumPeopleVaccinatedSecondDoseByPublishDate%22%7D&format=json"
func performRequest(vaccineURL: String){
if let url = URL(string: vaccineURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJASON(vaccineData: safeData)
}
}
task.resume()
}
}
func parseJASON(vaccineData: Data) {
let decoder = JSONDecoder()
do{
let decodedData = try decoder.decode(VaccineData.self, from: vaccineData)
print(decodedData.data[0].cumPeopleVaccinatedFirstDoseByPublishDate)
} catch {
print(error)
}
}
And this is the code from the other file I have to for the data:
import Foundation
struct VaccineData: Decodable {
let data: VaccineDataObject
}
struct VaccineDataObject: Decodable {
let cumPeopleVaccinatedFirstDoseByPublishDate: Int
let date: String
}
I hope that all makes sense. Also, please tell me if I am being too detailed.
Your struct definition is incorrect. It says there's a single value in data, but you have an array:
struct VaccineData: Decodable {
let data: VaccineDataObject
}
Should be:
struct VaccineData: Decodable {
let data: [VaccineDataObject]
}
You may find QuickType helpful here. It will take JSON and write decoding structs for you automatically. (Though it looks like you are pulling out such a small part, that it may be easier to hand-write as you have.)

Parse JSON File Starting with Curly Brace

I have a set of data in my database and I am unable to parse it through xcode. As the JSON file starts with a curly brace, I am constantly getting nil when i try to print the value upon parsing.
I am currently using XAMPP and I have done up the required PHP files and I have also tested that my database is working using INSOMNIA. However, I am unfamiliar on the parsing prior to data retrieval.
struct Coordinate: Codable {
let id: Int
let name: String
let latitude: Float
let longitude: Float
let address: String
class ViewController: UIViewController {
let url = URL(string: "http://127.0.0.1/callCenter/v2/getCoordinates.php")!
URLSession.shared.dataTask(with: url)
{ (data, _, _) in
if let data = data {
let coordinates = try? JSONDecoder().decode([Coordinate].self, from: data)
print(coordinates)
}
} .resume()
}
}
So upon running of program, I am printing NILs repeatedly. I highly its due to my first few lines of struct code. I have managed to print the size of the json file through this program as well.
Please do help me. Have been stuck for quite a few days.

parse json, populate collection view Swift 4.2

This is too simple but I am lost. I am still new to swift really.
I need to parse the downloaded json ( localized file in the Xcode project ) and populate the data to a CollectionView.
enum Response{
case success(Data)
case error(Error)
}
// struct follows the json
struct InformationFromJson: Decodable {
let id: Int
let name: String
}
class MYJSON {
public func downloadMYJSON(_ completion: #escaping (Response) -> ()) {
guard let bundle = Bundle(identifier: MYJSON.bundleId), let path = bundle.path(forResource: "data", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
completion(Response.error(NSError(domain: MYJSON.bundleId, code: MYJSON.bundleErrorCode, userInfo: [NSLocalizedDescriptionKey : MYJSON.bundleError])))
return
}
completion(Response.success(data))
}
}
So, without totally changing the function call, how do I parse the json? It's downloaded so far from the function, but I don't see how to even add a print statement to test, without getting errors because of the guard statement , the way it is.
I need to simple populate a cellForRowAt:
I never saw nested guard like this, so it got me. I am used to seeing the let statements separated so you can put print statements to at least see if things are getting downloaded or parsed.
You can decode your json by passing data, whatever you get from
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
guard let decoded = try? JSONDecoder().decode(InformationFromJson.self, from: data) else {
return
}