How to Deal With `self` Variable in JSON data swiftUI - json

I am trying to access the Schoology API from my app, designed for our school. I've been using the OAuthSwift package for OAuth 1 support.
In the response from Schoology, this shows up multiple times
"links": {
"self": "https:\/\/api.schoology.com\/v1\/messages\/inbox\/9999999"
}
Where I'm having difficulty is in decoding the JSON into a Codable Structure. I can't simply code for:
struct Links: Codable {
let self:String
}
As self is already reserved by swift, it can't be used as a variable name. Is there another way to decode JSON data in Swift that would avoid this issue?
The full structure of the JSON response is printed below: (note: there are many messages in the array, all share the same structure.)
{
"message": [
{
"id": 9999999,
"subject": "Your homework is late, prepare for termination.",
"recipient_ids": "9999999",
"last_updated": 9999999,
"mid": null,
"author_id": 9999999,
"message_status": "read",
"message": null,
"links": {
"self": "https:\/\/api.schoology.com\/v1\/messages\/inbox\/9999999"
}
},
],
"links": {
"self": "https:\/\/api.schoology.com\/v1\/messages\/inbox?start=0&limit=20"
},
"unread_count": "0"
}
If you've gotten this far, I thank you. But if I haven't provided enough information, all the code's available on our GitHub page in the API branch.

You can use a backtick to escape reserved words:
struct Links: Codable {
var `self`: String
}
If you don't want to use self, you can map a json key to a different property using manually defined CodingKeys:
struct Links: Codable {
var me: String
enum CodingKeys: String, CodingKey {
case me = "self"
}
}
Here's the documentation

Related

How do I create a decodable struct to match JSON nest from API?

I am trying to create a decodable struct that will store select data from an API called MarketStack. The API uses JSON format and nests the information I am looking for like this.
{
"pagination": {
"limit": 100,
"offset": 0,
"count": 100,
"total": 9944
},
"data": [
{
"open": 129.8,
"high": 133.04,
"low": 129.47,
"close": 132.995,
"volume": 106686703.0,
"adj_high": 133.04,
"adj_low": 129.47,
"adj_close": 132.995,
"adj_open": 129.8,
"adj_volume": 106686703.0,
"split_factor": 1.0,
"dividend": 0.0,
"symbol": "AAPL",
"exchange": "XNAS",
"date": "2021-04-09T00:00:00+0000"
},
[...]
]
}
My struct currently looks like this.
struct ScrapeStruct: Decodable {
var high: Double
var low: Double
}
But I am worried that this won't access the right response objects because they are nested in "data". I thought I could maybe do something like this.
struct ScrapeStruct: Decodable {
var data: high
var data: low
}
Like I've seen online but still just get errors about it not following Decodable format. Any help or suggestions to research would be greatly appreciated. Thanks!
There are a couple of issues. Firstly, your JSON data is an array of objects and your struct is just an object. So, first create a struct to model each object. Also, using var in structs is not very useful considering structs have to be destroyed and recreated with a changed property to be modified (energy intensive process). It should look like this:
struct SingleData: Codable {
let open: Double
let high: Double
}
Now, ScrapeStruct should have a property called data that should be an array of SingleData. It should look like this:
struct ScrapeStruct: Decodable {
let data: [SingleData]
}
Now, as for your second struct. It will not work because "high" and "low" are types that do not exist. To parse (essentially converting languages, in this case JSON to Swift) you can only use basic types or objects created with basic types (such as Date, Int, String, Double, etc)
struct ScrapeStruct: Decodable {
var data: high //type high does not exist
var data: low //type low does not exist
}
If you want to avoid manual decoding and rely on synthesized Decodable implementation, your data structures should entirely match responses.
Your example is long, but for instance, for this shortened response of the similar structure:
{
"data" : [
{
"open" : 129.8,
"high" : 133.04
},
{
"open" : 123.4,
"high" : 567.8
}
]
}
This should work:
struct Response: Decodable {
struct Scrape: Decodable {
let open: Double
let high: Double
}
let data: [Scrape]
}

Swift The data couldn’t be read because it isn’t in the correct format

I am new to swift and trying to figure out how to parse JSON to a struct. I am trying to get an image from NASA Mar's Rover Photos.I am trying to follow tutorials online, but can't seem to fix this issue. What am I doing wrong here?
error:
Fatal error: The data couldn’t be read because it isn’t in the correct format.
import Foundation
class API {
class func getImage(_ onSucessus: #escaping ([LatestPhoto]) -> ()){
Constrant.session.dataTask(with: Constrant.request){(data, res, err) in
guard let data = data, err == nil else{
fatalError()
}
do{
let apod = try Constrant.decoder.decode([LatestPhoto].self, from: data)
DispatchQueue.main.async {
onSucessus(apod)
}
}
catch{
fatalError(error.localizedDescription)
}
}.resume()
}
}
Struct
struct LatestPhoto: Identifiable, Codable{
let id = UUID()
let imgSrc: String
let earthDate: String
enum CodingKeys: String, CodingKey {
case imgSrc = "img_src"
case earthDate = "earth_date"
}
}
JSON
{
"latest_photos": [
{
"id": 839114,
"sol": 3127,
"camera": {
"id": 20,
"name": "FHAZ",
"rover_id": 5,
"full_name": "Front Hazard Avoidance Camera"
},
"img_src": "https://mars.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/03127/opgs/edr/fcam/FRB_675093431EDR_F0880366FHAZ00302M_.JPG",
"earth_date": "2021-05-23",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
},
{
"id": 839115,
"sol": 3127,
"camera": {
"id": 20,
"name": "FHAZ",
"rover_id": 5,
"full_name": "Front Hazard Avoidance Camera"
},
"img_src": "https://mars.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/03127/opgs/edr/fcam/FLB_675093431EDR_F0880366FHAZ00302M_.JPG",
"earth_date": "2021-05-23",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
}
]
}
Your JSON format doesn't quite match what you're trying to decode. You need a wrapper for the latest_photos array at the root of your JSON object.
For example:
struct LatestPhotosWrapper: Codable {
let latestPhotos: [LatestPhoto]
enum CodingKeys: String, CodingKey {
case latestPhotos = "latest_photos"
}
}
let apod = try JSONDecoder().decode(LatestPhotosWrapper.self, from: data)
(Rather than providing a CodingKey, you can also look into the built-in systems for converting from snake case: https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/convertfromsnakecase)
Also, you may want to print the error and not just the error.localizedDescription -- you can get a better picture of what's going on. For example, with your original code, you get:
Expected to decode Array but found a dictionary instead.
Finally, you might check out app.quicktype.io -- you can paste in your JSON and get correct Swift structs pre-built for you.

Issue Parsing / Decoding JSON from API Endpoint Into Struct Object

I am writing a Swift 5.x app using Alamofire 5 to get a list of files from an API I wrote. The API returns the file list as a JSON Data object. I want to then get this data into a struct I created. I am not able to get this working. Here is an example JSON string that my server sends over when you hit the API endpoint:
[{
"ID": "0d37ee7a-39bf-4eca-b3ec-b3fe840500b5",
"Name": "File01.iso",
"Size": 6148
}, {
"ID": "656e0396-257d-4604-a85c-bdd0593290cd",
"Name": "File02.iso",
"Size": 224917843
}, {
"ID": "275fdf66-3584-4899-8fac-ee387afc2044",
"Name": "File04.iso",
"Size": 5549504
}, {
"ID": "1c73f857-56b5-475b-afe4-955c9d2d87fe",
"Name": "File05.iso",
"Size": 15476866871
}, {
"ID": "bfebbca2-49de-43d7-b5d0-3461b4793b62",
"Name": "File06.iso",
"Size": 37254264
}]
I created the following Data Model in swift to hold this:
struct Files: Decodable {
let root: [File]
}
struct File: Decodable, Identifiable {
var id: UUID
var name: String
var size: Int
}
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case size = "Size"
}
Then I used Alamofire 5.x to call the API endpoint and attempt to decode the JSON and place it into the object in question:
func getPackageFilesJSON() {
AF.request("http://localhost:8080/api/v1/list/pkgs").responseDecodable(of: Files.self) { response in
guard let serverFiles = response.value else {
print("Error Decoding JSON")
return
}
let self.serverFilesList = serverFiles
}
}
This fails. If I debugPrint the response I get this for the result:
[Result]: failure(Alamofire.AFError.responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)))))
I have never been great at creating these data models and decoding JSON into them. I am sure I am missing something silly. I am hopeful that someone more knowledgable than me, or a second set of eyes can help me get this working.
Thanks,
Ed
There is no key root in the JSON. The root object is an array
Delete
struct Files: Decodable {
let root: [File]
}
and decode
AF.request("http://localhost:8080/api/v1/list/pkgs").responseDecodable(of: [File].self) { response in ...
and move the CodingKeys enum into the File struct
struct File: Decodable, Identifiable {
var id: UUID
var name: String
var size: Int
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case size = "Size"
}
}

Parsing a nested JSON using Decodable

My JSON structure looks like this:
{
"code": 200,
"status": "Ok",
"etag": "7232324423",
"data": {
"offset": 0,
"limit": 25,
"results": [{
"id": 1011244,
"name": "Miss Nesbit",
"description": "",
"modified": "2018-04-04T20:15:35-0400",
"thumbnail": {
"path": "http://i.annihil.us/u/prod/i/mg/8/70/4c002efc322e3",
"extension": "jpg"
}
},
{
"id": 1011244,
"name": "Miss Solis",
"description": "",
"modified": "2018-09-04T20:15:35-0400",
"thumbnail": {
"path": "http://i.annihil.us/u/prod/i/mg/8/70/4c002efc382e3",
"extension": "jpg"
}
}
]
}
}
I want to parse the results in a struct as follows:
struct Character: Codable {
let id: Int
let name: String
let thumbnail: Thumbnail
let description: String
}
However I'm a bit confused about where I specify I only want the results part ? Would I do it when implementing Decodable as follows?
let container = try decoder.container(keyedBy: CodingKeys.self)
let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results)
Or do we have to map out each nested section? ANy help or guidance would be so appreciated! :)
Mapping out the relevant keys is necessary to drill down for you, yes.
You can use app.quicktype.io to get started fast and remove the non-relevant keys if you really don‘t want to parse the rest or leave it there if you may want to use it later.
You can use my extension helper NestedDecodable, which allows you to extract Decodable from a keyPath. It adds this method to JSONDecoder:
decoder.decode(Class, from: Data, keyPath: String)
A key path specifies the nested path to the model you want, separated by .
So using your example, this works (tested):
let characters = try decoder.decode([Character].self, from: data, keyPath: "data.results")
Internally, it creates a wrapper struct to hold your target Decodable class, split keyPath by ., then for-loop each key down until it reaches the final keyed decoding container, decode that model and returns. Ref

SWIFT 4 nested JSON Struct - Codable

I'm having issues creating a struct to parse JSON in Swift 4. I'm able to parse small JSONs and JSONDecoder seems to work fine. Just need help to create a struct to parse JSON like that:
{
"main": {
"solutions": [
{
"exersises": [
{
"book_title": "test",
"release_date": "2015-01-12T11:00",
"price": 100,
"additional": [
{
"item1": "test",
"item2": "test",
"number": 1
},
{
"item1": "test2",
"item2": "test2",
"number": 2
}
],
"availability": "Yes",
"item_id": 43534
}
]
}
]
}
}
What kind of struct do I need to get to value of book_title for example?
Its really easy. Your main probem is most likely root element. Let me get first layer or two for you.
let decoded = try JSONDecoder().decode(MainJSON.self, from: data)
class MainJSON: Codable {
var main:SolutionJSON?
}
class SolutionJSON: Codable {
var exercises:[ExercisesJSON]?
}
class ExercisesJSON: Codable {
var bookTitle: String?
var releaseDate: String?
var price: Double?
... etc
enum CodingKeys: String, CodingKey {
case bookTitle = "book_title"
case releaseDate = "release_date"
case price = "price"
}
}
ExerciseJSON also uses Codable interface which lets remap json properties into swift properties if they don't match. Hope this helps.
i prefer to give a general solution not only for this condition
it is very simple just download and run this MACOS APP from GITHUB
run it in your mac by XCODE and but your JSON in it,
it will make Models for any complex JSON
notes
1 if JSON keys have a capital character in the first it will be small
, so after copying model you need to change it like the JSON
2 if two JSON objects have the same structure and the same key names it will be only one model