Home > Enterprise >  swift change struct model into another model if have similar id
swift change struct model into another model if have similar id

Time:09-30

I get the response from the api as in the JSON code below

{
    "timestamp": 1632838801,
    "status": "ok",
    "message": "ok",
    "data": [
        {
            "id": 1,
            "id_category": 6,
            "products": [
                {
                    "id": 12,
                    "product_name": "product one",
                }
            ]
        },
        {
            "id": 2,
            "id_category": 6,
            "products": [
                {
                    "id": 20,
                    "product_name": "product two"
                }
            ]
        },
        {
            "id": 3,
            "id_category": 13,
            "products": [
                {
                    "id": 994,
                    "product_name": "product three"
                }
            ]
        }
    ]

}

I'm success to decode the response into a struct like this:

struct ProductDataResponse {
    let timestamp: Int
    let status: Int
    let message: String
    let data: [Data]
}
struct Data {
    let id: Int
    let idCategory: Int
    let products: [Product]
}
struct Product {
    let id: Int
    let productName: String
    let idJelajah: Int
}

the problem is, I want my data model different from the response, if the data has the same id_cateogry then the existing product data will be grouped into the same id_category,below i attach the expected result of struct and the expected json. the result is 2 array of diffrent id_category and product

struct ListCategory {
    let idCategory
    let category: [Category]
    
}
struct Category {
    var id: Int
    var product: [Product]
}
struct Product {
    let id: Int
    let productName: String
    let idJelajah: Int
}



{
    "timestamp": 1632838801,
    "status": "ok",
    "message": "ok",
    "data": [
        {
            "id_category": 6,
            "products": [
                {
                    "id": 12,
                    "product_name": "product one",
                },
                {
                    "id": 20,
                    "product_name": "product two"
                }
            ]
        },
        {
            "id_category": 13,
            "products": [
                {
                    "id": 994,
                    "product_name": "product three"
                }
            ]
        },
    ]
}

CodePudding user response:

With model being:

struct ProductDataResponse: Codable {
    let timestamp: Double
    let status: String
    let message: String
    let data: [Data]

    struct Data: Codable {
        let id: Int
        let idCategory: Int
        let products: [Product]
    }
    struct Product: Codable {
        let id: Int
        let productName: String
    }
}

struct ProductDataOutput: Codable {
    let timestamp: Double
    let status: String
    let message: String
    let data: [Category]

    struct ListCategory: Codable {
        let idCategory: Int
        let category: [Category]

    }
    struct Category: Codable {
        var id: Int
        var product: [Product]
    }
    struct Product: Codable {
        let id: Int
        let productName: String
    }
}

Side note, to be working with your current code, I changed timestamp into a Double & status into a String, else it would cause decoding issue.
I also put them inside each other, to avoid naming. For instance, you'd need to differentiate Swift.Data & ProductDataResponse.Data, and the two Products would collide.
Also, I added a top level structure that you didn't give: ProductDataOutput.

And a helper for converting Product into Product (ie the one with structure of the response, and the one with the target one). Of course, we could use the same struct, but I wanted to separate them, just in case as you wrote it yourself.

extension ProductDataOutput.Product {
    init(with otherProduct: ProductDataResponse.Product) {
        self.id = otherProduct.id
        self.productName = otherProduct.productName
    }
}

The following code is inside a

do {
    //HERE
} catch {
    print("Error: \(error)")
}

That I'll skip, but keep it for debugging and catch issue.

Let's parse into the model as the Response first:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let parsed = try decoder.decode(ProductDataResponse.self, from: Data(initialJSONStr.utf8))
print(parsed)

Then, I'd use reduce(into:_:) to group by category id the products.

let reduced = parsed.data.reduce(into: [Int: [ProductDataOutput.Product]]()) { partialResult, current in
    var existing = partialResult[current.idCategory, default: []]
    let newProducts = current.products.map { ProductDataOutput.Product(with: $0) }
    existing.append(contentsOf: newProducts)
    partialResult[current.idCategory] = existing
}
print(reduced)

Then, let's create our target structure. I used a quick map() to transform the reduced into a [ProductDataOutput.Category].

let output = ProductDataOutput(timestamp: parsed.timestamp,
                               status: parsed.status,
                               message: parsed.message,
                               data: reduced.map { ProductDataOutput.Category(id: $0.key, product: $0.value) })

Then, to debug the output as JSON (as it's the sample you gave):

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
let encodedOutput = try encoder.encode(output)
print(String(data: encodedOutput, encoding: .utf8)!)

With this, you should be fine.

  • Related