Firebase Firestore: Encoder from custom structs not working - swift

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

Related

Error: "Expected to decode Dictionary<String, Any> but found an array instead." — but I haven't defined a dictionary? [duplicate]

This question already has answers here:
Decoding Error -- Expected to decode Dictionary<String, Any> but found an array instead
(2 answers)
Closed 1 year ago.
I'm working on a creative project, and I'm trying to decode content from an API database using Swift's JSONDecoder() function. I've built my structs, a getData() function, and I've set up a do-try-catch for the JSONDecoder() function. I'm having difficulty understanding what I'm doing to get the error I'm getting.
Here are my structs:
struct Response: Codable {
let foundRecipes: [Recipe]
let foundIngredients: [Ingredient]
}
struct Recipe: Codable {
let id: Int
let title: String
let image: String
let imageType: String
let usedIngredientCount: Int
let missedIngredientCount: Int
let missedIngredients: [Ingredient]
let usedIngredients: [Ingredient]
let unusedIngredients: [Ingredient]
let likes: Int
}
struct Ingredient: Codable {
let id: Int
let amount: Int
let unit: String
let unitLong: String
let unitShort: String
let aisle: String
let name: String
let original: String
let originalString: String
let origianalName: String
let metaInformation: [String]
let meta: [String]
let image: String
}
Here's my getData() function:
func getData(from url: String) {
URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong.")
return
}
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("")
print(String(describing: error)) // Right here is where the error hits.
}
guard let json = result else {
return
}
print(json.foundRecipes)
}).resume()
}
Here's a link to the API's documentation. The URL I'm calling in getData() links to the same structure of search as shown in their example: https://spoonacular.com/food-api/docs#Search-Recipes-by-Ingredients — and here's a screenshot of the url results for the exact search I'm working on: https://imgur.com/a/K3Rn9SZ
And finally, here's the full error that I'm catching:
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
My understanding of this error is that it's saying I told the JSONDecoder() to look for a Dictionary of <String, Any>, but it's at the link and only seeing an array. I'm confused, because I don't know where it thinks I'm providing a dictionary. Where am I screwing up? Not looking for specific code changes, just some guidance on what I'm missing.
Thanks in advance :)
As you can see in your image of the API data and in the API documentation you linked to, the API is returning an array (in the documentation, for example, you can see that it is surrounded by [...]). In fact, it looks like the API returns an array of Recipe.
So, you can change your decoding call to this:
var result: [Recipe]?
do {
result = try JSONDecoder().decode([Recipe].self, from: data)
print(result)
} catch {
print(error)
}
Perhaps your idea for Response came from somewhere else, but the keys foundRecipes or foundIngredients don't show up in this particular API call.
Also, thanks to #workingdog's for a useful comment about changing amount to a Double instead of an Int in your model.

In Swift, how to serialize an arbitrary object in Toml format?

Thanks for your help. I need interaction with Toml files in my macOS Swift application. I am using the TOMLDecoder library to parse the Toml format. The library works by specifying a Swift struct type that conforms to Codable, and have the library create the object for us. From the docs:
struct Discography: Codable {
struct Album: Codable {
let name: String
struct Song: Codable {
let name: String
}
let songs: [Song]
}
let albums: [Album]
}
If we take a sample Toml file:
[[albums]]
name = "Born to Run"
[[albums.songs]]
name = "Jungleland"
[[albums.songs]]
name = "Meeting Across the River"
[[albums]]
name = "Born in the USA"
[[albums.songs]]
name = "Glory Days"
[[albums.songs]]
name = "Dancing in the Dark"
We can parse it with:
let tomlData = try? Data(contentsOf: URL(fileURLWithPath: "/path/to/file"))
let discography = try? TOMLDecoder().decode(Discography.self, from: tomlData)
Here comes my question. The library does not provide a way to reverse the process, so to serialize back the object, so I would like to write that on my own, and, possibly, I would like to achieve a solution in clean Swift, if I understand correctly, by the use of the T type, thus allowing any kind of Codable conforming object to be serializable. The decode function in the library is:
public func decode<T: Decodable>(_ type: T.Type, from text: String) throws -> T {
let topLevel: Any
do {
topLevel = try TOMLDeserializer.tomlTable(with: text)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid TOML.", underlyingError: error))
}
let decoder = TOMLDecoderImpl(referencing: self, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
I have started to write my encode function like the following:
class TOMLEncoder: TOMLDecoder {
func encode<T>(sourceObject: T) -> String {
return "Sample serialized text..."
}
}
I really don't know how to proceed... from my very limited knowledge I should iterate somehow on the sourceObject properties and create the TOML file from the contents of those properties, but I am not sure if that is the correct approach and how to achieve it. Any help is greatly appreciated. Thanks

How can I encode a Codable type by specifying a subset of its CodingKeys?

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

Decoding raw data from URLSession the right way in Swift [duplicate]

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)

Swift 4 Json Decoding, Which is the best/latest method, Using Codable or Decodable?

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.