Adam Bardon
Adam Bardon

Reputation: 3889

Decode struct with generic array property

I have a following struct with generics for API response with pagination:

struct Paginable<Body> {
    let data: [Body]
    let meta: Meta
}

extension Paginable {
    struct Meta: Codable {
        let pagination: Pagination

        struct Pagination: Codable {
            let total: Int
            let count: Int
            let perPage: Int
            let currentPage: Int
            let totalPages: Int
        }
    }
}

And I want to be able to decode it like so:

let response  = try? response.decode(to: Paginable<Segment>.self)

So here's my attempt to make it Decodable:

extension Paginable where Body == Data {
    func decode<BodyType: Decodable>(to type: BodyType.Type) throws -> Paginable<BodyType> {
        guard let decodedJSON = try? JSONDecoder().decode(BodyType.self, from: data) else {
            throw APIError.decodingFailure
        }

        return Paginable<BodyType>(data: decodedJSON, meta: self.meta)
    }
}

This gives me two errors:

  1. Cannot convert value of type 'Paginable.Meta' to expected argument type 'Paginable<_>.Meta'

on the line with return statement

If I change the meta property to some primitive type like Int, the error disappears. But Meta itself is Codable, so what's to problem here?

  1. Cannot convert value of type '[Data]' to expected argument type 'Data'

on the line with guard statement

How to solve this one?

Upvotes: 0

Views: 334

Answers (1)

PGDev
PGDev

Reputation: 24341

You need to conform Paginable to Codable like,

struct Paginable<Body>: Codable where Body: Codable {
    let data: [Body]
    let meta: Meta
}

Change decode(data:to:) method in extension Paginable to,

extension Paginable {
    static func decode<BodyType: Decodable>(data: Data, to type: BodyType.Type) throws -> BodyType? {
        do {
            let response = try JSONDecoder().decode(BodyType.self, from: data)
            return response
        } catch {
            throw error
        }
    }
}

Usage:

if let data = str.data(using: .utf8) {
    do {
        let response = try Paginable<Segment>.decode(data: data, to: Paginable<Segment>.self)
        print(response)
    } catch {
        print(error)
    }
}

Edit:

JSON format:

{
  "data": [
      {
        "name": "Name-1"
      },
      {
        "name": "Name-2"
      }
    ],
    "meta": {
      "pagination": {
        "total": 100,
        "count": 10,
        "perPage": 5,
        "currentPage": 1,
        "totalPages": 10
      }
    }
}

Upvotes: 1

Related Questions