alickbass / CodableFirebase

Use Codable with Firebase
MIT License
690 stars 91 forks source link

Decoding Arrays results in type mismatch #51

Open vikdenic opened 6 years ago

vikdenic commented 6 years ago

My data structure looks like this. "humans" is a dictionary of keys whose values are the dictionary of a human. And that human can have a dictionary of keys whose values are the dictionary of a dog.

  "humans" : {
    "abc123" : {
      "name" : "Vince",
      "pets" : [ {
        "animal" : "dog",
        "name" : "Clifford"
      }, {
        "animal" : "fish",
        "name" : "Nemo"
      } ]
    },
    "xyz789" : {
      "name" : "Jack"
    }
  }

And so my Swift structs looks like this to match it:

struct Human: Codable {
    var name: String!
    var pets: [Pet]?
}

struct Pet: Codable {
    var name: String!
    var animal: Animal!
}

enum Animal: String, Codable {
    case cat
    case dog
    case fish
}

I try to decode like this: let human = try FirebaseDecoder().decode([Human].self, from: value)

But I am getting the following error when trying to encode objects that have arrays of some object:

typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Not an array", underlyingError: nil))

How can I properly encode a dictionary's values as an array?

DevAndArtist commented 6 years ago

I have no idea how this library works exactly, plus I'm just a passenger on this repo, but the above snapshot which is like a json does not work as an array. The humans key contains key value pairs instead of just values.

"humans": {
    "key" : { ... },
    ...
  }
}

Instead it should be:

"humans": [
    { ... },
    ...
  ]
}

Therefore the following 'might' work:

let dictionary = try FirebaseDecoder().decode([String: Human].self, from: value)
let humans = dictionary.map { $0.value } // no order preserved
vikdenic commented 6 years ago

@DevAndArtist So Firebase does not allow values to be arrays; everything must be a dictionary. Otherwise I would have designed the data model according to your response.

Instead to solve this, I simply pass the 'values' of the dictionary / snapshot.value to the decoding function:

guard let value = snapshot.value as? [String : Any] else { return }

do {
  let humans = try FirebaseDecoder().decode([Human].self, from: Array(value.values))
} catch let decodeError {
  print(decodeError)
}

The magic is in passing Array(value.values) instead of just value

DevAndArtist commented 6 years ago

Ah sorry, you were referring to the old database, while I had Firestore in mind when I wrote my reply which does support arrays and maps.

alickbass commented 5 years ago

@vikdenic Firebase does allow array as values. You can save a NSArray to the firebase. Try it in some test project

AmitaiB commented 2 years ago

Also read https://firebase.googleblog.com/2014/04/best-practices-arrays-in-firebase.html for some of the rationale in Google's choice.