Uploaded image for project: 'Swift'
  1. Swift
  2. SR-5953

Decodable: Allow "Lossy" Array Decodes

    XMLWordPrintable

    Details

    • Type: Improvement
    • Status: Open
    • Priority: Medium
    • Resolution: Unresolved
    • Component/s: Foundation
    • Labels:
      None

      Description

      In the current implementation of Decodable, it is impossible to attempt to decode an array of Decodable's and allow individual items to fail.

      For example:

      enum Season: String, Decodable {
          case winter
          case spring
          case summer
          case autumn
      }
      
      let json = """
      ["winter", "spring", "summer", "fall"]
      """.data(using: .utf8)!
      
      let seasons = try! JSONDecoder().decode([Season].self, from: json)
      

      This will raise an error of the following:

      Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 3", intValue: Optional(3))], debugDescription: "Cannot initialize Season from invalid String value fall", underlyingError: nil))
      

      It may be considered desirable in certain use cases that this decode succeeds, but omits individual items that fail. As such, the desired output value would be:

      [.winter, .spring. summer]
      

      One may attempt to circumvent this by using an outer, wrapper type combined with a customized initializer that ignores inner decode errors, such as the following:

      struct Climate: Decodable {
          var seasons: [Season]
      
          enum CodingKeys: String, CodingKey {
              case seasons
          }
      
          init(from decoder: Decoder) throws {
              let container = try decoder.container(keyedBy: CodingKeys.self)
              var seasonsContainer = try container.nestedUnkeyedContainer(forKey: .seasons)
      
              var seasons = [Season]()
              while !seasonsContainer.isAtEnd {
                  if let season = try? seasonsContainer.decode(Season.self) {
                      seasons.append(season)
                  }
              }
              self.seasons = seasons
          }
      }
      
      let json = """
      { "seasons": ["winter", "spring", "summer", "fall"] }
      """.data(using: .utf8)!
      
      let climate = try! JSONDecoder().decode(Climate.self, from: json)
      

      However, the above code will result in an infinite loop due to the implementation of UnkeyedDecodingContainer.currentIndex. This is because the currentIndex is not incremented unless a decode succeeds.

        Attachments

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              cocoahero Jonathan Baker
            • Votes:
              20 Vote for this issue
              Watchers:
              30 Start watching this issue

              Dates

              • Created:
                Updated: