How do you prepare a model to encode properly - json

API Objects
Types of JSON objects served by Gutendex are given below.
Book
{
"id": <number of Project Gutenberg ID>,
"title": <string>,
"subjects": <array of strings>,
"authors": <array of Persons>,
"translators": <array of Persons>,
"bookshelves": <array of strings>,
"languages": <array of strings>,
"copyright": <boolean or null>,
"media_type": <string>,
"formats": <Format>,
"download_count": <number>
}
Format
{
<string of MIME-type>: <string of URL>,
...
}
Person
{
"birth_year": <number or null>,
"death_year": <number or null>,
"name": <string>
}
There is an API service that returns the types above. I managed to create objects for Book and person on swift but I don't know how to implement a codable struct for "Format"

Codable is a protocol you can conform your struct or class to that handles this for you. The JSON key defaults to the field name, however, you can override it with a coding key. This is a pretty close representation of what you're looking for, however, the formats field was a little ambiguous.
struct Book: Codable {
let id: Int
let title: String
let subjects: [String]
let authors: [Person]
let translators: [Person]
let bookshelves: [String]
let languages: [String]
let copyright: Bool?
let mediaType: String
let numberOfDownloads: Int
let formats: [String : String] // This wasn't clear in your example
enum CodingKeys: String, CodingKey {
case id, title, subjects, authors, translators, bookshelves,
languages, copyright, formats // No need to override the key
case mediaType = "media_type"
case numberOfDownloads = "download_count"
}
struct Person: Codable {
let birthYear: Int?
let deathYear: Int?
let name: String
enum CodingKeys: String, CodingKey {
case name // No need to override the key
case birthYear = "birth_year"
case deathYear = "death_year"
}
}

Related

How to make a JSON model

I know you have to create a model of the expected JSON. I’m having trouble creating one from the Airtable API.
Here is my data:
{
"id": "recBOdydIpM2P3xkZ",
"createdTime": "2022-04-26T17:47:12.000Z",
"fields": {
"% GP": "32.31%",
"Total Rx": 103,
"Gross Profit": 1534.77,
"Total Received": 4749.55,
"Date": "2022-04-25",
"Copay": 2469.43,
"Acquisition Cost": 3214.78,
"Average GP": 14.9,
"TP Remitted": 2280.12
}
}
Here’s my model:
struct Record: Codable, Identifiable {
var id = UUID()
let createdTime: Date
let fields: //where im stuck :(
}
Make a new struct called Fields:
struct Record: Codable, Identifiable {
var id = UUID()
let createdTime: Date
let fields: Fields
struct Fields: Codable {
let gpPercent: String
let totalRx: Int
[...]
enum CodingKeys: String, CodingKey {
case gpPercent = "% GP"
case totalRx = "Total Rx"
[...]
}
}
enum CodingKeys: String, CodingKey {
case id
case createdTime
case fields
}
}
This is a broad question with multiple possible solutions. A model is basically what you want the objects you're building to look like. I would recommend looking into object oriented programming, like here: https://www.raywenderlich.com/599-object-oriented-programming-in-swift
Here is one possible solution:
struct Record: Codable, Identifiable {
var id = UUID
let createdTime: Date
let percentGP: Int
let totalRx: Int
let grossProfit: Double
let totalReceived: Double
let date: Date
let copay: Double
let acquisitionCost: Double
let averageGP: Double
let TPRemitted: Double
}
If you need to decode "fields" as a category into SwiftUI, you could create it as a separate object, like:
struct RecordData: Codable {
let percentGP: Int
// etc
}
and then, in Record:
struct Record: Codable, Identifiable {
var id = UUID
let createdTime: Date
let fields: RecordData
}
I am not using "Fields" as the name on purpose, to avoid confusion with plurals. You could use it, just be wary of using a single entity of Fields, not something like [Fields].

How to decode dictionary JSON response in Swift?

struct Chat: Codable, Identifiable {
let id = UUID()
var Messages: [Messages]
}
class ChatApi : ObservableObject{
#Published var chats = Chat()
func loadData(completion:#escaping (Chat) -> ()) {
let urlString = prefixUrl+"/room"
let url = NSURL(string: urlString as String)!
var request = URLRequest(url: url as URL)
request.setValue(accessKey, forHTTPHeaderField: "X-Access-Key-Id")
request.setValue(secretkey, forHTTPHeaderField: "X-Access-Key-Secret")
URLSession.shared.dataTask(with: request) { data, response, error in
let chats = try! JSONDecoder().decode(Chat.self, from: data!)
print(chats)
DispatchQueue.main.async {
completion(chats)
}
}.resume()
}
}
I'm not able to decode the following JSON response using Swift.
{
"Messages": [
{...}
]
}
I have tried the above ways and Xcode keeps throwing error. Although I'm able to decode JSON response with another function that are like this
[
{...},
{...},
{...}
]
I'm able to decode JSON response that are returned as arrays but not as dictionaries.
Example response to decode
{
"Messages": [
{
"_id": "MS4mMbTXok8g",
"_created_at": "2022-04-05T10:58:54Z",
"_created_by": {
"_id": "Us123",
"Name": "John Doe",
},
"_modified_at": "2022-04-05T10:58:54Z",
"Type": "Message",
"_raw_content": "ss",
"RoomId": "Ro1234",
},
{
"_id": "MS4m3oYXadUV",
"_created_at": "2022-04-04T15:22:21Z",
"_created_by": {
"_id": "Us678",
"Name": "Jim Lane",
},
"_modified_at": "2022-04-04T15:22:21Z",
"Type": "Message",
"_raw_content": "ss",
"RoomId": "Ro1234",
}
]
}
The data model that I've used is
struct CreatedBy: Codable {
var _id: String
var Name: String
}
struct Messages: Codable {
var _id: String
var _created_by: CreatedBy?
var `Type`: String?
var _raw_content: String
}
struct Chat: Codable, Identifiable {
let id = UUID()
var Messages: [Messages]
}
The error message before compilation is Editor placeholder in source file
I am going to introduce you to a couple of sites that will help when handling JSON decoding: JSON Formatter & Validator and Quicktype. The first makes sure that the JSON that you are working off of is actually valid, and will format it into a human readable form. The second will write that actual decodable structs. These are not perfect, and you may want to edit them, but they will get the job done while you are learning.
As soon as you posted your data model, I could see the problem. One of your variables is:
var `Type`: String?
The compiler is seeing the ` and thinking it is supposed to be a placeholder. You can't use them in the code.
Also, though there is not any code posted, I am not sure you need to make Chat Identifiable, as opposed to Messages which could be, but are not. I would switch, or at least add, Identifiable to Messages. I also made CreatedBy Identifiable since it also has a unique id.
The other thing you are missing which will make your code more readable is a CodingKeys enum. This translates the keys from what you are getting in JSON to what you want your variables to actually be. I ran your JSON through the above sites, and this is what came up with:
// MARK: - Chat
struct Chat: Codable {
let messages: [Message]
enum CodingKeys: String, CodingKey {
case messages = "Messages"
}
}
// MARK: - Message
struct Message: Codable, Identifiable {
let id: String
let createdAt: Date
let createdBy: CreatedBy
let modifiedAt: Date
let type, rawContent, roomID: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case createdAt = "_created_at"
case createdBy = "_created_by"
case modifiedAt = "_modified_at"
case type = "Type"
case rawContent = "_raw_content"
case roomID = "RoomId"
}
}
// MARK: - CreatedBy
struct CreatedBy: Codable, Identifiable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case name = "Name"
}
}
This gives you conventional Swift variables, as opposed to the snake case the JSON is giving you. Try this in your code and let us know if you have any more problems.

Decoding JSON that has a dynamic key value with the key value name found elsewhere in JSON model using Swift Decodable

When I make an API request, I get returned a JSON structure that contains several objects which all have unique key value names. Therefore I cannot use the regular Decodable protocol to break down the JSON into different instances. However, these key value names are found elsewhere in my JSON structure under known constant values that I can access normally. Here is some example code I created to demonstrate my issue:
let jsonString = """
{
"uniqueObjects": {
"uniqueObject:1132435": {
"firstName": "John"
"lastName": "Smith"
}
"uniqueObject2:119672": {
"firstName": "Jane"
"lastName": "Doe"
}
"uniqueObject3:008997": {
"firstName": "Sam"
"lastName": "Greenfield"
}
}
"keys": {
"object1": {
"key": "uniqueObject1:132435"
}
"object2": {
"key": "uniqueObject2:119672"
}
"object3": {
"key": "uniqueObject3:008997"
}
}
"""
let jsonData = Data(jsonString.utf8)
let decodedData = try? JSONDecoder().decode(JSONData.self, from: jsonData)
print(decodedData?.uniqueObjects.firstObject.firstName ?? "No data decoded")
struct JSONData: Decodable {
let uniqueObjects: Object
let keys: KeyObjects
}
struct Object: Decodable {
let firstObject: Names
let secondObject: Names
let thirdObject: Names
private enum DynamicCodingKeys: String, CodingKey {
case firstObject = "???" // this needs to be mapped to the unique value for object 1
case secondObject = "??" // this needs to be mapped to the unique value for object 2
case thirdObject = "????" // etc.
// I don't think this works because Xcode says that the strings must be raw literals
}
}
struct KeyObjects: Decodable {
let object1: Key
let object2: Key
let object3: Key
}
struct Key: Decodable {
let key: String
}
struct Names: Decodable {
let firstName: String
let lastName: String
}
The approach I took here, which is definitely wrong, was to create a coding key for each of the unique objects and map its name to a String that would be somehow be decoded from its relative key value pair in the key object. CodingKeys, at least what I have tried, do not allow you to do this so I need a new method in order to access this code. I also need to know how I can reference the data once I decode it (just printing it out for now). Help and a short explanation would be much appreciated as I am a beginner developer. Thanks!
Unless I have misunderstood it looks to me like you are over complicating things. This is how I would define the types for decoding the json
struct Response: Codable {
let uniqueObjects: [String: User]
let keys: [String: ObjectKey]
}
struct ObjectKey: Codable {
let key: String
}
struct User: Codable {
let firstName: String
let lastName: String
}

parsing nested JSON in Swift 5

Can't figure out how to build the struct for this nested JSON. I'm so close but missing something..
I'm trying to verify I'm loading correctly... the first two work great the nested data fails
print(json.pagination.items) // this works
print(json.releases[3].date_added) // this works
print(json.releases[3].basicInformation?.year) //NOT WORKING, returns nil
here is the struct in built
struct Response: Codable {
let pagination: MyResult
let releases: [MyReleases]
}
struct MyResult: Codable {
var page: Int
var per_page: Int
var items: Int
}
struct MyReleases: Codable {
var date_added: String
let basicInformation: BasicInformation?
}
struct BasicInformation: Codable {
let title: String
let year: Int
enum CodingKeys: String, CodingKey {
case title, year
}
}
My JSON is
{
"pagination":{
"page":1,
"pages":2,
"per_page":50,
"items":81,
"urls":{
"last":"https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?page=2&per_page=50",
"next":"https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?page=2&per_page=50"
}
},
"releases":[
{
"id":9393649,
"instance_id":656332897,
"date_added":"2021-03-28T10:54:09-07:00",
"rating":2,
"basic_information":{
"id":9393649,
"master_id":353625,
"master_url":"https://api.discogs.com/masters/353625",
"resource_url":"https://api.discogs.com/releases/9393649",
"thumb":"",
"cover_image":"",
"title":"Ten Summoner's Tales",
"year":2016
}
}
]
}
Any help would be greatly appreciated. I'm so close but missing something... :(
First of all
json.releases[3].date_added
doesn't work with the given JSON, it will crash because there is only one release.
In MyReleases you have to add CodingKeys to map the snake_case name(s)
struct MyReleases: Codable {
var dateAdded: String
let basicInformation: BasicInformation?
private enum CodingKeys: String, CodingKey {
case basicInformation = "basic_information", dateAdded = "date_added"
}
}
or add the .convertFromSnakeCase key decoding strategy.
You can even decode dateAdded as Date with the .iso8601 date decoding strategy.

Swift 4 decodable nested json with random key attributes

I'm having problems decoding json. I've followed lots of tutorials but non use complex json structures. For simplicity I minimized the code and use Dog as example.
In following json i'm mostly only interested in the Dog structs. The json "Data" attribute contains random dog names. So I cannot use coding keys because I dont know the attribute name.
{
"Response": "success"
"BaseLinkUrl": "https://wwww.example.com",
"Data": {
"Max": {
"name": "Max",
"breed": "Labrador"
},
"Rocky": {
"name": "Rocky",
"breed": "Labrador"
},
...
}
}
I have following structs:
struct DogResponse : Decodable {
let data : DogResponseData
enum CodingKeys: String, CodingKey {
case data = "Data"
}
}
struct DogResponseData: Decodable {
let dog: Dog //this is a random variable name
enum CodingKeys: String, CodingKey {
case dog = "??random_variable_dog_name??"
}
}
struct Dog: Decodable {
let name: String
let type: String
enum CodingKeys: String, CodingKey {
case name
case type = "breed"
}
}
collecting the Dog structs:
let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
print(dogResponse)
What do I need to do in my "DogResponseData" struct for swift to recognize a random variable that contains my Dog struct?
A possible solution is to write a custom initializer to decode the dictionaries as [String:Dog] and map the values to an array
struct Dog : Decodable {
let name : String
let breed : String
}
struct DogResponse : Decodable {
let dogs : [Dog]
private enum CodingKeys: String, CodingKey {
case data = "Data"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let data = try values.decode([String : Dog].self, forKey: .data)
dogs = Array(data.values)
}
}
let dogResponse = try JSONDecoder().decode(DogResponse.self, from: data)
print(dogResponse.dogs)
===========================================================================
Or if you want to keep the dictionary structure it's still shorter
struct Dog : Decodable {
let name : String
let breed : String
}
struct DogResponse : Decodable {
let dogs : [String : Dog]
private enum CodingKeys: String, CodingKey {
case dogs = "Data"
}
}
It's worth keeping in mind that CodingKey is a protocol, not necessarily an enum. So you can just make it a struct and it will accept any random string value you throw at it.