简体   繁体   中英

Parse valid objects from JSON array in Swift

I have a codable struct like this

struct User: Codable {
    let id: String
    let registrationId: String
    let firstName: String?
    let lastName: String?
}

Now, the response from the server contains an array like this

 [
  {
    "id": "1",
    "registrationId": "r1",
    "firstName": "Jon",
    "lastName": "Doe"
  },
  {
    "id": "2",
    "registrationId": null,
    "firstName": null,
    "lastName": null
  },
  {
    "id": "3",
    "registrationId": null,
    "firstName": null,
    "lastName": null
  },
  {
    "id": "4",
    "registrationId": "r4",
    "firstName": "Jon",
    "lastName": "Snow"
  }
]

I want to parse this as [User] but only those who have a valid(not null) registrationId . I know how to parse JSON in swift. But the problem here is because of the two invalid data in the middle the whole response will run into decoding error. But I want to parse it as an array of [User] containing valid ones(in this case first and last object).

Any hints or help is much appreciated.

1- Make registrationId an optional

 let registrationId: String?

2-

 let res = try JSONDecoder().decode([User].self,from:data)
 let filtered = res.filter { $0.registrationId != nil }

after all, this data must come from a database or an array. By making the id parameter as the primary key, the registrationId parameter as a foreign key, and if you are working on the registrationId parameter, you can make a productive sequence or if it is on the array, you can link the method that generates the sequence for that registrationId.

Now I know how to achieve this.

struct User: Codable {
    let id: String
    let registrationId: String
    let firstName: String?
    let lastName: String?
}

struct WrappedDecodableArray<Element: Decodable>: Decodable {
    let elements: [Element]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var elements = [Element]()
        while !container.isAtEnd {
            if let element = try? container.decode(Element.self) {
                elements.append(element)
            } else {
                // move the container currentIndex forward
                _ = try container.decode(Block.self)
            }
        }
        self.elements = elements
    }

    private struct Block: Decodable {}
}

func testUserParsing() {
    let jsonStr = """
    [
      {
        "id": "1",
        "registrationId": "r1",
        "firstName": "Jon",
        "lastName": "Doe"
      },
      {
        "id": "2",
        "registrationId": null,
        "firstName": null,
        "lastName": null
      },
      {
        "id": "3",
        "registrationId": null,
        "firstName": null,
        "lastName": null
      },
      {
        "id": "4",
        "registrationId": "r4",
        "firstName": "Jon",
        "lastName": "Snow"
      }
    ]
    """

    let jsonData = jsonStr.data(using: .utf8)!
    let wrappedArray = try! JSONDecoder().decode(WrappedDecodableArray<User>.self, from: jsonData)
    print(wrappedArray.elements)
}

It would have been more elegant if we could override the init(from decoder: Decoder) for Array under certain conditions like extension Array where Element == User . But looks like this is not possible. Initializer inside the extension can not override the original one and hence never get called. So for now looks like wrapping with a struct is the only solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM