SWIFT 4 nested JSON Struct - Codable - json

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

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]
}

Issues with Codable struct and Measuments<Unit>

Can't figure out how to save to JSON standard units of the Dimension class, I have a struct:
struct Item: Hashable, Identifiable, Codable {
var id: Int
var name: String
var price: Int
var unit: Measurement<Unit>
}
Xcode doesn't throw any errors so I'm assuming Measurement can be encoded? I can't really make it working and save a json, and how should my json data look like if I want to load the struct with a test json?
[
{
"id": 1,
"name": "Test",
"price": 195,
"unit": ???
}
]
The idea is that I operate with the standard Dimension class that has all units I need (kg/g/L/ml) instead of creating my own class and describe all units from scratch.
Is it possible to have a JSON with "unit": "kg" that then will match a standard UnitMass.kilogram automatically?
Thanks.
For me this actually works with this (simplified) example:
struct Item: Codable {
var name: String
var unit: Measurement<Unit>
}
let json =
"""
{
"name": "Test",
"unit": {
"value": 12,
"unit": {
"symbol": "ml"
}
}
}
""".data(using: .utf8)!
let item = try JSONDecoder().decode(Item.self, from: json)
yields the correct result. You can replace the unit symbol with anything you like (kg, g, ml, e.t.c)

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"
}
}

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

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

Swift how to express JSON in structs

I want to express the following JSON and convert to swift structs
1) I get error in the third line full_plan "comma is missing". I don't know why a comma is required? I need help fixing it
2) If that is fixed will the structs shown below is accurate to convert to JSON?
Please note: add_ons may be missing in the JSON for some plans, so second plan shown does not have add_ons.
Basically I am asking help to fix the JSON and the struct for swift.
{
"id": "100",
"plans":
[
"full_plan":
{
"plan":
[
{ "plan_type": "Legacy" },
{ "contract_duration_months": "12" }
],
"add_ons" :
[
{ "parking": "yes"},
{ "washerDryer": "no" }
]
},
"full_plan":
{
"plan":
[
{ "plan_type": "New" },
{ "contract_duration_months": "0" }
]
}
]
}
struct TopPlan : Decodable {
var uniqueId: String?
var Plans: [FullPlan]?
enum CodingKeys : String, CodingKey {
case uniqueId = "id"
case Plans = "plans"
}
}
struct FullPlan: Decodable {
var Plan: PlanJSON?
var freePlan: AddOnsJSON?
enum CodingKeys : String, CodingKey {
case pricedPlan = "plan"
case freePlan = "add_ons"
}
}
struct PlanJSON: Decodable {
var planType: String?
var duration: String?
enum CodingKeys : String, CodingKey {
case planType = "plan_type"
case duration = "contract_duration_months"
}
}
struct AddOnsJSON: Decodable {
var parking: String?
var washerDryer: String?
enum CodingKeys : String, CodingKey {
case parking = "parking"
case washerDryer = "washerDryer"
}
}
Short answer: your current JSON is invalid syntax.
You are using "full_plan" as a key (which would be fine if "plans" was an object) inside an array. Arrays in JavaScript (and thus in JSON) are unkeyed. You should either remove "full_plan" and just use the object that it refers to like "plans": [{}, {}, etc], or if you need to keep the object key wrap the entire item in curly braces such as "plans": [{ "full_plan": {}}, { "full_plan": {}}, etc]
You should build this up from the bottom (and if you post your question in a form that is executable in a Playground you will get answers faster). As it stands your plan JSON is unfortunate. Your struct states that you want a hash and you provide it with an array of hashes (which should be merged to get what you want). Try it like this:
import Cocoa
let jsonData = """
{ "plan_type": "Legacy",
"contract_duration_months": "12"
}
""".data(using: .utf8)!
struct PlanJSON: Decodable {
var planType: String?
var duration: String?
enum CodingKeys : String, CodingKey {
case planType = "plan_type"
case duration = "contract_duration_months"
}
}
do {
let plan = try JSONDecoder().decode(PlanJSON.self, from: jsonData)
print(plan)
} catch {
print(error)
}
That way you will be given enough information to further fix your JSON, but the rest looks ok.