Home > Net >  Asymmetric Encoding/Decoding with Codable and Firestore?
Asymmetric Encoding/Decoding with Codable and Firestore?

Time:01-29

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 vegetarian and id

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))
  •  Tags:  
  • Related