Anviking / Decodable

[Probably deprecated] Swift 2/3 JSON unmarshalling done (more) right
MIT License
1.04k stars 73 forks source link

Parsing an array of parseable sub-objects, while omitting sub-objects that fail to parse #152

Closed irace closed 7 years ago

irace commented 7 years ago

Say I have an type like:

struct Foo: Decodable {
  let bar: String

  static func decode(_ json: Any) throws -> Foo {
    return try Foo(bar: json => "bar")
  }
}

Given the following JSON:

[
  { "bar": "a" },
  { "some_non_bar_key": "b" },
  { "bar": "c" }
]

I want to parse this response such that I end up with an array containing only the two sub-objects that can actually be decoded into Foo instances. In other words, I just want to ignore { "some_non_bar_key": "b" } and keep the other two.

This is pretty easily done with the following code:

struct ArrayDecoder {
    private let json: Any

    init(json: Any) {
        self.json = json
    }

    func parseableObjects<T: Decodable>(inArrayWithKey key: String) -> [T] {
        guard let dictionary = json as? [String: Any], let array = dictionary[key] as? [Any] else { return [] }

        return array.flatMap({ (object: Any) -> T? in
            return try? T.decode(object)
        })
    }
}

But I’m wondering if there’s a more idiomatic way to do this, given functionality that Decodable already provides?

Thanks!

Anviking commented 7 years ago

There's this method on Array that you can use:

[Foo].decode(json, ignoreInvalidObjects: true)

which expands out to:

[Foo?].decoder { try? Foo.decode($0) }(json).flatMap {$0}

If type-inference is desired (e.g it's used a lot) I'd consider something like this

func ignoreInvalids<T: Decodable>(_ json: Any) throws -> [T]  { // name?
    return try [T].decode(json, ignoreInvalidObjects: true)
}

let objects: [Foo] = try ignoreInvalids(json => "some key")