Swift JSONDecode decoding arrays fails if single element decoding fails

swift decode array
swift codable null
swift decode json array
swift codable optional
swift catch decodingerror
swift codable array
swift decode array of dictionaries
decodingerror swift

While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder to skip elements in an array. For example, I have the following JSON:

[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]

And a Codable struct:

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

When decoding this json

let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)

Resulting products is empty. Which is to be expected, due to the fact that the second object in JSON has no "points" key, while points is not optional in GroceryProduct struct.

Question is how can I allow JSONDecoder to "skip" invalid object?

One option is to use a wrapper type that attempts to decode a given value; storing nil if unsuccessful:

struct FailableDecodable<Base : Decodable> : Decodable {

    let base: Base?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.base = try? container.decode(Base.self)
    }
}

We can then decode an array of these, with your GroceryProduct filling in the Base placeholder:

import Foundation

let json = """
[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
""".data(using: .utf8)!


struct GroceryProduct : Codable {
    var name: String
    var points: Int
    var description: String?
}

let products = try JSONDecoder()
    .decode([FailableDecodable<GroceryProduct>].self, from: json)
    .compactMap { $0.base } // .flatMap in Swift 4.0

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

We're then using .compactMap { $0.base } to filter out nil elements (those that threw an error on decoding).

This will create an intermediate array of [FailableDecodable<GroceryProduct>], which shouldn't be an issue; however if you wish to avoid it, you could always create another wrapper type that decodes and unwraps each element from an unkeyed container:

struct FailableCodableArray<Element : Codable> : Codable {

    var elements: [Element]

    init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var elements = [Element]()
        if let count = container.count {
            elements.reserveCapacity(count)
        }

        while !container.isAtEnd {
            if let element = try container
                .decode(FailableDecodable<Element>.self).base {

                elements.append(element)
            }
        }

        self.elements = elements
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(elements)
    }
}

You would then decode as:

let products = try JSONDecoder()
    .decode(FailableCodableArray<GroceryProduct>.self, from: json)
    .elements

print(products)

// [
//    GroceryProduct(
//      name: "Banana", points: 200,
//      description: Optional("A banana grown in Ecuador.")
//    )
// ]

Swift JSONDecode decoding arrays fails if single element - php, While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder to skip elements in an array. Flattening JSON data and decoding arrays with decoding containers Mirroring the structure of JSON data often produces types used only for decoding. If you want to break free from the constraints of the data you decode, you need to write custom decoding code using decoding containers.

I would create a new type Throwable, which can wrap any type conforming to Decodable:

enum Throwable<T: Decodable>: Decodable {
    case success(T)
    case failure(Error)

    init(from decoder: Decoder) throws {
        do {
            let decoded = try T(from: decoder)
            self = .success(decoded)
        } catch let error {
            self = .failure(error)
        }
    }
}

For decoding an array of GroceryProduct (or any other Collection):

let decoder = JSONDecoder()
let throwables = try decoder.decode([Throwable<GroceryProduct>].self, from: json)
let products = throwables.compactMap { $0.value }

where value is a computed property introduced in an extension on Throwable:

extension Throwable {
    var value: T? {
        switch self {
        case .failure(_):
            return nil
        case .success(let value):
            return value
        }
    }
}

I would opt for using a enum wrapper type (over a Struct) because it may be useful to keep track of the errors that are thrown as well as their indices.

Swift 5

For Swift 5 Consider using the Result enum e.g.

struct Throwable<T: Decodable>: Decodable {
    let result: Result<T, Error>

    init(from decoder: Decoder) throws {
        result = Result(catching: { try T(from: decoder) })
    }
}

To unwrap the decoded value use the get() method on the result property:

let products = throwables.compactMap { try? $0.result.get() }

Codable: Tips and Tricks, One option is to use a shell type that tries to decode a given value; saving nil in case of failure: struct FailableDecodable<Base : Decodable> : Decodable { let  CurrentIndex is not incremented unless a decode succeed, in this case decode is not succeding because there are missing fields which are NON-OPTIONALS. Lossy array decodes just doesn't work. Great.

The problem is that when iterating over a container, the container.currentIndex isn’t incremented so you can try to decode again with a different type.

Because the currentIndex is read only, a solution is to increment it yourself successfully decoding a dummy. I took @Hamish solution, and wrote a wrapper with a custom init.

This problem is a current Swift bug: https://bugs.swift.org/browse/SR-5953

The solution posted here is a workaround in one of the comments. I like this option because I’m parsing a bunch of models the same way on a network client, and I wanted the solution to be local to one of the objects. That is, I still want the others to be discarded.

I explain better in my github https://github.com/phynet/Lossy-array-decode-swift4

import Foundation

    let json = """
    [
        {
            "name": "Banana",
            "points": 200,
            "description": "A banana grown in Ecuador."
        },
        {
            "name": "Orange"
        }
    ]
    """.data(using: .utf8)!

    private struct DummyCodable: Codable {}

    struct Groceries: Codable 
    {
        var groceries: [GroceryProduct]

        init(from decoder: Decoder) throws {
            var groceries = [GroceryProduct]()
            var container = try decoder.unkeyedContainer()
            while !container.isAtEnd {
                if let route = try? container.decode(GroceryProduct.self) {
                    groceries.append(route)
                } else {
                    _ = try? container.decode(DummyCodable.self) // <-- TRICK
                }
            }
            self.groceries = groceries
        }
    }

    struct GroceryProduct: Codable {
        var name: String
        var points: Int
        var description: String?
    }

    let products = try JSONDecoder().decode(Groceries.self, from: json)

    print(products)

📡 🕔 👨‍🎨 Swift JSONDecode decoding arrays fail if one element fails , While using Swift4 and Codable protocols I got the following problem - it looks like there is no way to allow JSONDecoder to skip elements in  { "name": "Taylor Swift" } And now you can load your JSON into your struct in just a single line of code: let user = Bundle.main.decode(User.self, from: "data.json") The extension is capable of loading any kind of decodable data – your structs, arrays of your structs, and so on.

There are two options:

  1. Declare all members of the struct as optional whose keys can be missing

    struct GroceryProduct: Codable {
        var name: String
        var points : Int?
        var description: String?
    }
    
  2. Write a custom initializer to assign default values in the nil case.

    struct GroceryProduct: Codable {
        var name: String
        var points : Int
        var description: String
    
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
            name = try values.decode(String.self, forKey: .name)
            points = try values.decodeIfPresent(Int.self, forKey: .points) ?? 0
            description = try values.decodeIfPresent(String.self, forKey: .description) ?? ""
        }
    }
    

Swift JSONDecode decoding arrays fails if single element , When using Codable to parse an array of JSON objects, the decoder /swift-​jsondecode-decoding-arrays-fails-if-single-element-decoding-  When using Codable to parse an array of JSON objects, the decoder fails the whole array if one of the element fails. This is not nice, at least not most people would expect or desire (I guess). This is not nice, at least not most people would expect or desire (I guess).

Ive put @sophy-swicz solution, with some modifications, into an easy to use extension

fileprivate struct DummyCodable: Codable {}

extension UnkeyedDecodingContainer {

    public mutating func decodeArray<T>(_ type: T.Type) throws -> [T] where T : Decodable {

        var array = [T]()
        while !self.isAtEnd {
            do {
                let item = try self.decode(T.self)
                array.append(item)
            } catch let error {
                print("error: \(error)")

                // hack to increment currentIndex
                _ = try self.decode(DummyCodable.self)
            }
        }
        return array
    }
}
extension KeyedDecodingContainerProtocol {
    public func decodeArray<T>(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
        var unkeyedContainer = try self.nestedUnkeyedContainer(forKey: key)
        return try unkeyedContainer.decodeArray(type)
    }
}

Just call it like this

init(from decoder: Decoder) throws {

    let container = try decoder.container(keyedBy: CodingKeys.self)

    self.items = try container.decodeArray(ItemType.self, forKey: . items)
}

For the example above:

let json = """
[
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange"
    }
]
""".data(using: .utf8)!

struct Groceries: Codable 
{
    var groceries: [GroceryProduct]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        groceries = try container.decodeArray(GroceryProduct.self)
    }
}

struct GroceryProduct: Codable {
    var name: String
    var points: Int
    var description: String?
}

let products = try JSONDecoder().decode(Groceries.self, from: json)
print(products)

Safely Decode `Decodable` Arrays – Haoxin Li, Encoding and Decoding of Json Objects Through Java - As you know JSON can One Simple Integer Swift JSONDecode decoding arrays fails if single element​  Swift 4 JSON Decodable simplest way to decode type change. swift decodable dictionary (5) According to your needs, you may choose one of the two following ways in order to solve your problem.

Jsondecoder swift, To handle this automatically, you can give JSONEncoder one of these To decode it, you can provide JSONDecoder with a decoding strategy: If the API is returning an array as the root element, parsing the response looks like this: JSON at all, but perhaps an HTML error page from a failed API call. If you store your json-string in an utf8-file and read it with file_get_contents, please make sure to strip leading BOM (byte order mark) before decoding it with json_decode. Otherwise json_decode will fail creating an associative array.

Ultimate Guide to JSON Parsing with Swift, In this tutorial, you'll learn all about encoding and decoding in Swift, exploring If this enum exists, only the cases present here will be used for This will give you an error, because GiftEmployee is not Codable yet. This JSON array is polymorphic because it contains both default and custom employees. decode of JSON array into swift Array 789 Views 1 Reply. Latest I did get it to work with a single JSON object earlier, but now there is an array, no luck.

Encoding and Decoding in Swift, Another example of a key/object pair is one that maps to an array. Codable will fail to parse the JSON if it searches for a key that it's expecting  PMJSON. PMJSON provides a pure-Swift strongly-typed JSON encoder/decoder as well as a set of convenience methods for converting to/from Foundation objects and for decoding JSON structures. The entire JSON encoder/decoder can be used without Foundation, by removing the files ObjectiveC.swift and DecimalNumber.swift from the project.

Comments
  • We can't skip the invalid objects but you can assign default values if it is nil.
  • Why can't points just be declared optional?
  • What if the base object isn't an array, but it contains one? Like { "products": [{"name": "banana"...},...] }
  • @ludvigeriksson You just want to perform the decoding within that structure then, for example: gist.github.com/hamishknight/c6d270f7298e4db9e787aecb5b98bcae
  • Swift's Codable was easy, until now.. can't this be made tad simpler?
  • @Hamish I don't see any error handling for this line. What happens if an error is thrown here var container = try decoder.unkeyedContainer()
  • @bibscy It's within the body of init(from:) throws, so Swift will automatically propagate the error back to the caller (in this case the decoder, which will propagate it back to the JSONDecoder.decode(_:from:) call).
  • I like this answer because I don't have to worry about writing any custom init
  • One variation, instead of an if/else I use a do/catch inside the while loop so I can log the error
  • This answer mentions the Swift bug tracker and has the simplest additional struct (no generics!) so I think it should be the accepted one.
  • This should be the accepted answer. Any answer that corrupts your data model is an unacceptable tradeoff imo.
  • Instead of try? with decode it's better to use try with decodeIfPresent in second option. We need to set default value only if there is no key, not in case of any decoding failure, like when key exists, but type is wrong.
  • hey @vadian do you know any other SO questions involving custom initializer to assign default values in case type doesn't match? I have a key that is an Int but sometimes will be a String in the JSON so I tried doing what you said above with deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000 so if it fails it will just put 0000 in but it still fails.
  • In this case decodeIfPresent is the wrong API because the key does exist. Use another do - catch block. Decode String, if an error occurs, decode Int
  • Ive wrapped this solution up in an extension github.com/IdleHandsApps/SafeDecoder