This question already has an answer here:
iOS Generic type for codable property in Swift
(1 answer)
Closed 2 years ago.
The response for most of the endpoints in my API is something like this -
{
"status":"success",
"data":[
{
"id":"1",
"employee_name":"Tiger Nixon",
"employee_salary":"320800",
"employee_age":"61",
"profile_image":""
},
{
"id":"2",
"employee_name":"Garrett Winters",
"employee_salary":"170750",
"employee_age":"63",
"profile_image":""
}
]
}
And this is what my Employee model looks like
struct Employee: Codable {
let id, employeeName, employeeSalary, employeeAge: String
let profileImage: String?
enum CodingKeys: String, CodingKey {
case id
case employeeName = "employee_name"
case employeeSalary = "employee_salary"
case employeeAge = "employee_age"
case profileImage = "profile_image"
}
}
typealias Employees = [Employee]
I just want to extract the data part of the API response using JSONDecoder and pass it to my completion handler
completionHandler(try? JSONDecoder().decode(Employees.self, from: data), response, nil)
I was able to get around by creating a struct Employees like this -
struct Employees: Codable {
let status: String
let data: [Employee]
}
But this is just a workaround and I would have to do it for every almost every model. So is there a better and less redundant way of extracting data from the response?
What I would do if I were you is just create a template-based wrapper and use it for all of your model objects.
struct ModelWrapper<T: Codable>: Codable {
let status: String
let data: [T]
}
let decoder = JSONDecoder()
let wrapper = try decoder.decode(ModelWrapper<Employee>.self, from: json)
Related
So I created a function in which I try to create a document in my Firestore in which user data is stored. But when upgrading my project to the Xcode 13.0 beta, the Firebase encoder has stopped working. Anyone else experiencing a similar problem?
My model looks like this:
struct User: Identifiable, Codable {
#DocumentID var id: String?
var auth_id: String?
var username: String
var email: String
enum CodingKeys: String, CodingKey {
case auth_id
case username
case email
}
}
And the call to Firebase like this:
let newUser = User(auth_id: idToken, username: name, email: email)
try await databasemodel.database.collection("users").document(idToken).setData(newUser)
The Document is created with this code doesn't exist yet, but that worked earlier.
Now when I compile I get the error: "Cannot convert value of type 'User' to expected argument type '[String : Any]'"
No other errors are displayed, and I'm pretty sure the rest of the code works as expected.
So I ran into a similar issue with Codables... I've made this little extension that's saved me. Maybe it works for you too :)
extension Encodable {
/// Convenience function for object which adheres to Codable to compile the JSON
func compile () -> [String:Any] {
guard let data = try? JSONEncoder().encode(self) else {
print("Couldn't encode the given object")
preconditionFailure("Couldn't encode the given object")
}
return (try? JSONSerialization
.jsonObject(with: data, options: .allowFragments))
.flatMap { $0 as? [String: Any] }!
}
}
You can then do
try await databasemodel.database.collection("users").document(idToken).setData(newUser.compile())
Note. I didn't test this. I'm simply providing the extension which solved my problem when faced with the same thing.
Just use Firestore.Encoder
extension Encodable {
var dictionary: [String: Any]? {
return try? Firestore.Encoder().encode(self)
}
}
I’m developing an app with Swift 5 and Firestore.
I created structs Identifiables and Codables and it works well but I have problem decoding DocumentID for nested documents.
I wasn’t able to find something similar online.
I would like to know if Firestore structure I implemented is right in my use case and how to solve id problem.
I have to show a list of activities with just some info about the User and the Sport (like name and image). That's why I'm trying to nest in the Activity object just the info I need to not query the entire User and the Sport for each activity of the list.
On Firestore:
The activity document contains:
info (String)
organizer:
- name (String)
- image (String)
- ref (Reference)
date (Date)
sport:
- name (String)
- image (String)
- ref (Reference)
Here is my code:
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
struct User: Codable {
#DocumentID var id: String? = UUID().uuidString
let name: String
let email: String?
let image: String
let birthday: Date?
let description: String?
}
struct Sport: Codable {
#DocumentID var id: String? = UUID().uuidString
let name: String
let image: String
}
struct Activity: Codable {
#DocumentID var id: String? = UUID().uuidString
let organizer: User
let info: String
let date: Date
let sport: Sport
static func getActivities(completion: #escaping (_ activities: [Activity]?) -> Void) {
Activity.collectionReference.getDocuments { (querySnapshot, error) in
guard error == nil else {
print("Error: \(error?.localizedDescription ?? "Generic error")")
return
}
let activities = querySnapshot?.documents.compactMap({ (queryDocumentSnapshot) -> Activity? in
return try! queryDocumentSnapshot.data(as: Activity.self)
})
completion(activities)
}
}
}
Calling Activity.getActivities I'm able to get everything I need but decoding the user and sport directly from activity object they get the activity ID instead of their own. That's wrong because in this way I have the wrong reference to the object. The idea is to keep the reference to use it in the details page and fetching the complete object.
Can someone told me what's the best way to structure this kind of architecture?
Thank you in advance.
I have types for which I provide CodingKeys with custom names for their data members as appropriate. I would like to encode, as required by different call sites, only a subset of a type's CodingKeys before sending the data to a server. The required encodings change from call site to call site, so there's no default implementation to consider.
Example types (for demonstration purposes):
struct Account: Codable
{
let name: String;
var balance: Float;
mutating func set(balance: Float)
{
self.balance = balance; // verifications, etc.
}
}
struct User: Codable
{
enum CodingKeys: String, CodingKey
{
case
id = "db_id",
name = "db_name",
account
// many more keys
}
let id: Int;
let name: String;
var account: Account;
// many more data members
}
Creating an instance:
var user = User(
id: 1, name: "john", account: Account(name: "checking", balance: 10_000));
Using a JSONEncoder works as expected; it produces the following:
{
"db_id" : 1,
"db_name" : "john",
"account" : {
"balance" : 10000,
"name" : "checking"
}
}
I want to encode a subset of the User type in order to send that data back to a server so that I can update specific data fields instead of updating the entire set of properties of my type. Mock usage example:
user.account.set(balance: 15_000);
let jsonEncoding = JSONEncodeSubset.of(
user, // instance to encode
keys: [User.CodingKeys.id, User.CodingKeys.account] // data to include
);
The resulting produced JSON would look like so:
{
"db_id" : 1,
"account" : {
"balance" : 15000,
"name" : "checking"
}
}
Server side, we now have the exact data we need to perform our desired update.
Another example: somebody entered the wrong name for our user, therefore another update request looks like this:
user.name = "jon"; // assume the model was modified to make this mutable
let jsonEncoding = JSONEncodeSubset.of(
user, // instance to encode
keys: [User.CodingKeys.id, User.CodingKeys.name] // only encode id & name
);
The expected resulting JSON encoding:
{
"db_id" : 1,
"name" : "jon"
}
Note that we exclude the information that isn't part of the update (user's account). The objective is to optimize the encoding to include only data that is relevant to that specific request.
Considering I have a large list of objects to update, with different call sites updating different things, I'd like to have a succinct way to perform the encoding task.
Encoding a subset of a type's data members improves encoding performance/memory footprint.
Sending a larger count of smaller encoded objects to the server becomes more efficient.
Does Swift provide any such support for encoding subsets of a type's CodingKeys?
Expanding on Joakim Danielson's answer you can use CodingUserInfoKey to pass data to userInfo property of JSONEncoder.
extension CodingUserInfoKey {
static let keysToEncode = CodingUserInfoKey(rawValue: "keysToEncode")!
}
extension JSONEncoder {
func withEncodeSubset<CodingKeys>(keysToEncode: [CodingKeys]) -> JSONEncoder {
userInfo[.keysToEncode] = keysToEncode
return self
}
}
You need to make CodingKeys conform to CaseIterable and implement a custom encode(to:) method:
struct User: Codable {
enum CodingKeys: String, CodingKey, CaseIterable { // add `CaseIterable`
...
}
...
func encode(to encoder: Encoder) throws {
let keysToEncode = encoder.userInfo[.keysToEncode] as? [CodingKeys] ?? CodingKeys.allCases
var container = encoder.container(keyedBy: CodingKeys.self)
for key in keysToEncode {
switch key {
case .id:
try container.encode(id, forKey: .id)
case .account:
try container.encode(account, forKey: .account)
case .name:
try container.encode(name, forKey: .name)
}
}
}
}
And use it like this:
let encoder = JSONEncoder().withEncodeSubset(keysToEncode: [User.CodingKeys.id, .account])
let encoded = try encoder.encode(user)
print(String(data: encoded, encoding: .utf8)!)
// prints: {"db_id":1,"account":{"name":"checking","balance":15000}}
This solution requires that you write a custom encode(to:) for all properties but once that is done it should be easy to use.
The idea is to let the encode(to:) encode only those properties/keys that exists in a given array. So given the example above the function would look like this
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for key in selectedCodingKeys {
switch key {
case .id:
try container.encode(id, forKey: .id)
case .account:
try container.encode(account, forKey: .account)
case .name:
try container.encode(name, forKey: .name)
}
}
}
selectedCodingKeys is new property in the struct
var selectedCodingKeys = [CodingKeys]()
and we could also add a specific function for encoding
mutating func encode(for codingKeys: [CodingKeys]) throws -> Data {
self.selectedCodingKeys = codingKeys
return try JSONEncoder().encode(self)
}
and then the decoding could be done like in this example
var user = User(id: 1, name: "John", account: Account(name: "main", balance: 100.0))
do {
let data1 = try user.encode(for: [.id, .account])
let data2 = try user.encode(for: [.id, .name])
} catch {
print(error)
}
Im running into an issue with ObjectMapper and the way a json response is coming back from a server.
Is there a way to have ObjectMapper parse this response and create an array of rooms?
Currently I cannot straight map the json as the keys are unique, and will change every request, as well I cannot use the ObjectMapper way of accessing nested objects with dot notation, rooms.0.key as I am unsure how many objects there are, as well as the key that will show up.
Is there a simple way of parsing this reponse.
"rooms": {
"165476": {
"id": "165476",
"area": {
"square_feet": 334,
"square_meters": 31
},
"165477": {
"id": "165477",
"area": {
"square_feet": 334,
"square_meters": 31
},
Use Codable to parse the above JSON response.
Models:
struct Rooms: Codable {
let rooms: [String:Room]
}
struct Room: Codable {
let id: String
let area: Area
}
struct Area: Codable {
let squareFeet: Int
let squareMeters: Int
}
Parse the JSON data like so,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode(Rooms.self, from: data)
print(response.rooms.first?.key) //for the above json it will print "165476"
} catch {
print(error)
}
In Swift 4.1 we can decode JSON like this:
struct Person: Decodable {
let id: Int
let name: String
let imageUrl: String
}
let people = [Person]()
func parseJSON(data: Data) {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStratergy = .convertFromSnakeCase
self.people = try decoder.decode([Person].self, from: data)
} catch let error {
print(error as? Any)
}
}
Q1. What is the difference if I use Codable instead of Decodable? Which is better?
Q2. How to use decodeIfPresent here?
Q3. How to decode if there is no key present in the JSON?
The best way to encode and decode JSON in Swift4
Here is the JSON representation of a simple object User, let’s see how to deserialise those data into an object.
{
"id": 13,
"firstname" : "John",
"lastname" : "Doe",
"email" : "john.doe#lost.com"
}
Decoding JSON into object
I use a struct type to represent my object and include the protocol Decodable to allow deserialisation.
struct User : Decodable {
let id : Int
let firstname : String
let lastname : String
let email : String
}
Now we’re ready to decode it using JSONDecoder.
// assuming our data comes from server side
let jsonString = "{ \"id\": 13, \"firstname\" : \"John\", \"lastname\" : \"Doe\", \"email\" : \"john.doe#lost.com\" }"
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(User.self, from: jsonData)
print("Hello \(user.firstname) \(user.lastname)")
} catch {
print("Unexpected error: \(error).")
}
Pretty easy right? Let’s see how to serialise it now.
Encoding object into JSON
First, we need update our struct to allow encoding. To do so, we just need to include protocol Encodable.
struct User : Encodable, Decodable {
...
}
Our object is ready to be serialised back to JSON. We follow the same process as before, using JSONEncoder this time. In that case, I’ll also convert the data into a String to be sure it’s working
// assuming we have an object to serialise
That’s still pretty easy! So what is Codable all about?
Well, Codable, is just alias of Encodable and Decodable protocols as you can see in it’s definition
public typealias Codable = Decodable & Encodable
If you don’t want your JSON keys to drive your naming, you can still customise them using CodingKeys. Described as an enum, it will automatically be picked up while encoding / decoding
struct User : Codable {
var id : Int
var firstname : String
var lastname : String
var email : String?
// keys
private enum CodingKeys: String, CodingKey {
case id = "user_id"
case firstname = "first_name"
case lastname = "family_name"
case email = "email_address"
}
}
To go further
https://medium.com/#phillfarrugia/encoding-and-decoding-json-with-swift-4-3832bf21c9a8
https://benoitpasquier.com/encoding-decoding-json-swift4/
Answer of Question 3: https://www.calhoun.io/how-to-determine-if-a-json-key-has-been-set-to-null-or-not-provided/
Please if possible try to post separated questions, it is easier to answer and to read after.
For your first question: Codable is a protocol extending Encodable and Decodable.
Decodable is to parse from data to your models. For example from JSON data to structs.
Encodable is to make data from your models.
Codable contains both.