4

i'm parsing this API with swift Codable

"total": 7,
"searchResult": [
    null,
    {
        "name": "joe"
        "family": "adam"
    },
    null,
    {
        "name": "martin"
        "family": "lavrix"
    },
    {
        "name": "sarah"
        "family": "mia"
    },
    null,
    {
        "name": "ali"
        "family": "abraham"
    }
]

with this PaginationModel:

class PaginationModel<T: Codable>: Codable {
    var total: Int?
    var data: T?

    enum CodingKeys: String, CodingKey {
        case total
        case data = "searchResult"
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.total = try container.decodeIfPresent(Int.self, forKey: .total)
        self.data = try container.decodeIfPresent(T.self, forKey: .data)
    }
}

and User Model:

struct User: Codable {
    var name: String?
    var family: String?
}

i call jsonDecoder like this to parse API json:

let responseObject = try JSONDecoder().decode(PaginationModel<[User?]>.self, from: json)

now my problem is null in searchResult Array. it parsed correctly and when i access to data in paginationModel i found null in array.

how can i ignore all null when parsing API, and result will be an array without any null

2
  • @user28434 i updated my question Commented Mar 15, 2019 at 13:38
  • yes with this ?, it normally parsed to null. i want an array without null object Commented Mar 15, 2019 at 13:43

4 Answers 4

4

In the first place, I would advise to always consider PaginationModel to be composed from arrays. You don't have to pass [User] as the generic type, you can just pass User. Then the parser can use the knowledge that it parses arrays and handle null automatically:

class PaginationModel<T: Codable>: Codable {
    var total: Int?
    var data: [T]?

    enum CodingKeys: String, CodingKey {
        case total
        case data = "searchResult"
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.total = try container.decodeIfPresent(Int.self, forKey: .total)

        self.data = (try container.decodeIfPresent([T?].self, forKey: .data))?.compactMap { $0 }
    }
}

You might want to remove optionals here and use some default values instead:

class PaginationModel<T: Codable>: Codable {
    var total: Int = 0
    var data: [T] = []

    enum CodingKeys: String, CodingKey {
        case total
        case data = "searchResult"
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.total = (try container.decodeIfPresent(Int.self, forKey: .total)) ?? 0

        self.data = ((try container.decodeIfPresent([T?].self, forKey: .data)) ?? []).compactMap { $0 }
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

I'd also recommend making total (likely) and data (definitely) required rather than Optional. That will simplify this a lot by getting rid of the decodeIfPresent. (Similarly, are name and family really Optional? Is there a difference between "missing" and empty string?)
@RobNapier Very true.
this is exactly what i want. tnx @Sulthan
@RobNapier i agree with you. but our server side developer said that if suddenly they came with null value ( because of bugs from server or ... ) you must handle it
Even so, you're better off making them mandatory, and defaulting them to empty strings in the decoder so you don't have to treat them as optional everywhere in the program.
|
1

Simple solution, filter data after decoding

let responseObject = try JSONDecoder().decode(PaginationModel<[User?]>.self, from: data)
responseObject.data = responseObject.data?.filter{$0 != nil}

4 Comments

this can be a solution, but i'm looking for a better solution. especially all thing to be done in PaginationModel
The problem is that generic T can be a single object as well as an array. The best solution is to filter the null values already on the server.
@Sajjad, PaginationModel currently doesn't have enough information about actual type used in generic, you would have to pass info that you're actually looking for Array and then in init(from decoder: Decoder) decode it as [T?] first and then compactMap it to [T] and save into property.
ok. if i change data : T? to data: [T?], then can i handle it?
0

You may add an array type check within decode :

  required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.total = try container.decodeIfPresent(Int.self, forKey: .total)
    self.data = try container.decodeIfPresent(T.self, forKey: .data)

    //add the following:
    if let array =  self.data as? Array<Any?> {
        self.data = ( array.compactMap{$0} as? T)
    }
}

Comments

0

Note, you can just define the decodable variable that may be null/nil as [Float?] (or whatever type), with the optional '?' inside the array brackets.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.