Search code examples
jsonswiftdecodable

Decodable that inits from an array


I have a JSON array of objects. Simplified, it has this form:

[
  {"name": "Tinky Winky"},
  {"name": "Dipsy"},
  {"name": "Laa-Laa"},
  {"name": "Po"}
]

I can create a struct Tubby can can decode an individual instance from the array:

struct Tubby: Codable {
  let name: String
}

I'd like to create a struct Tubbies that can decode from the array of JSON:

struct Tubbies: Codable {
   let tubbies: [Tubby]

   init(from decoder: Decoder) throws {
     // What goes here?
     tubbies = ???
   }

… but now I'm stuck with how I should decode? I'd like to just do:

   init(from decoder: Decoder) throws {
     // What goes here?
     tubbies = decoder.decode([Tubby].self)
   }

But Decoder doesn't offer a decode. It has:

    /// Returns the data stored in this decoder as represented in a container
    /// keyed by the given key type.
    ///
    /// - parameter type: The key type to use for the container.
    /// - returns: A keyed decoding container view into this decoder.
    /// - throws: `DecodingError.typeMismatch` if the encountered stored value is
    ///   not a keyed container.
    func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey

    /// Returns the data stored in this decoder as represented in a container
    /// appropriate for holding values with no keys.
    ///
    /// - returns: An unkeyed container view into this decoder.
    /// - throws: `DecodingError.typeMismatch` if the encountered stored value is
    ///   not an unkeyed container.
    func unkeyedContainer() throws -> UnkeyedDecodingContainer

    /// Returns the data stored in this decoder as represented in a container
    /// appropriate for holding a single primitive value.
    ///
    /// - returns: A single value container view into this decoder.
    /// - throws: `DecodingError.typeMismatch` if the encountered stored value is
    ///   not a single value container.
    func singleValueContainer() throws -> SingleValueDecodingContainer

Both singleValueContainer (this was a mistake, as the answers clarify – thanks!) and unkeyedContainer throw for the array with a message suggesting that they don't support an array. Can I use container(keyedBy:), and what should I pass as the key?


Solution

  • I don't think it was a good approach, since [Tubby].self is simple enough to use anywhere. But if you just want to wrap the array inside another type, you should have something like this:

    Detailed version:

    struct Tubbies: Codable {
        let tubbies: [Tubby]
        
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            self.tubbies = try container.decode([Tubby].self)
        }
    }
    

    Shorter version:

    struct Tubbies: Codable {
        let tubbies: [Tubby]
        
        init(from decoder: Decoder) throws {
            tubbies = try [Tubby](from: decoder)
        }
    }
    

    Usage:

    let tubbies: Tubbies = try! JSONDecoder().decode(Tubbies.self, from: jsonData)