I have the following struct:
struct Recipe: Codable {
@DocumentID var id: String?
var vegetarian: Bool?
private enum CodingKeys: String, CodingKey {
case id
case vegetarian
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
vegetarian = try container.decode(Bool.self, forKey: .vegetarian)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(vegetarian, forKey: .vegetarian)
}
}
I am trying to:
- Decode only
vegetarian - Encode both
vegetarianandid
This is my data model:
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.recipe = try document.data(as: Recipe.self)
let recipeFromFirestore = Recipe(
id: self.recipe!.id,
vegetarian: self.recipe!.vegetarian)
self.recipes.append(recipeFromFirestore)
}
catch {
print("Error: \(error)")
}
}
}
}
I'm getting the following error in my getDocument: Missing argument for parameter 'from' in call.
This error doesn't happen if I comment out my init(from decoder: Decoder) and func encode(to encoder: Encoder) in my struct. Should I be doing something different for this asymmetric encoding/decoding?
CodePudding user response:
I don't get where you are seeing that error from - a line reference would be useful - but it's not directly related to your en/decoding. (As an aside, that method really is not a data model)
As you want to decode both properties with JSON keys that match their property names, there is no need to specify CodingKeys or write a custom decoder; you can rely on the synthesised decoder and let Codable do the work for you.
For the decoding you will need a custom solution else Decodable will decode both fields. This will require both a CodingKey enum (note the singular, i.e. the protocol, not the defaut enun name) and a custom encoder to use that.
You end up with a far simpler implementation of your struct. I've also added a simple initialiser as you lose the synthesised memberwise initialiser as soon as you define the init(from:). This was just so I could test it.
struct Recipe: Codable {
var id: String?
var vegetarian: Bool?
init(id: String, vegetarian: Bool){
self.id = id
self.vegetarian = vegetarian
}
init(from decoder: Decoder) throws {
enum DecodingKeys: CodingKey {
case vegetarian
}
let container = try decoder.container(keyedBy: DecodingKeys.self)
vegetarian = try container.decode(Bool.self, forKey: .vegetarian)
}
}
If you test this you will find that it will just decode the vegetarian property but encode both. Simple testing shows:
let recipe = Recipe(id: "1", vegetarian: true)
let data = try! JSONEncoder().encode(recipe)
print(String(data: data, encoding: .utf8)!) //{"id":"1","vegetarian":true}
let decodedRecipe = try! JSONDecoder().decode(Recipe.self, from: data)
print(decodedRecipe) //Recipe(id: nil, vegetarian: Optional(true))
