pburgov
pburgov

Reputation: 159

Stream of a nested POJOs List to a another POJO LIST

hope someone can help me. I have a POJO with the following structure:

public class Invoice{

private String docNum;
private String customer;
private ArrayList<InvoiceDetails> invoiceDetails;

/* Getters and setters*/
}

And another POJO with the following

public class InvoiceDetails{

private String vatGroup;
private Double vatAmount;
private Double amount;

/* Getters and setters*/
}

Besides I have a third one with the following

public class VatType{

private String vatGroup;
private Double vatAmount;
private Double amount;

/* Getters and setters*/
}

What I'm trying to do is to reduce a List of Invoiceto a Listof VatType grouped by vatGroup. Like de DISTINCT clause in SQL. Let's say I have the following List:

InvoiceDetails idA1 = new InvoiceDetails("S1", 100.0, 40.0);
InvoiceDetails idA2 = new InvoiceDetails("S2", 140.0, 40.0);
InvoiceDetails idA3 = new InvoiceDetails("A1", 50.0, 10.0);
ArrayList<InvoiceDetails> listA = new ArrayList<>();
listA.add(idA1);
listA.add(idA2);
listA.add(idA3);

Invoice invoiceA = new Invoice();
invoiceA.setDetailList(listA);

InvoiceDetails idB1 = new InvoiceDetails("S1", 200.0, 50.0);
InvoiceDetails idB2 = new InvoiceDetails("S2", 240.0, 50.0);
InvoiceDetails idB2 = new InvoiceDetails("A1", 100.0, 20.0);
ArrayList<InvoiceDetails> listB = new ArrayList<>();
listB.add(idB1);
listB.add(idB2);
listB.add(idB3);


Invoice invoiceB = new Invoice();
invoiceB.setDetailList(listB);

List<Invoice> invoiceList = new ArrayList<>();
invoiceList.add(invoiceA);
invoiceList.add(invoiceB);

The expected result would be a Listof VatTypewith the following items:

("S1",300.0,90.0)
("S2",380.0,90.0)
("A1",150.0,30.0)

How can I get this list using a streamin one shot. Avoiding to iterate through the Lists? Thanks in advance

Upvotes: 4

Views: 5389

Answers (4)

fps
fps

Reputation: 34460

Here's a way to do it using Java 8's new features of List and Map, without streams:

Map<String, VatType> map = new HashMap<>();

invoiceList.forEach(i -> i.getDetailList().forEach(d ->
    map.merge(
        d.getVatGroup(),
        new VatType(d.getVatGroup(), d.getVatAmount(), d.getAmount()),
        (left, right) -> {
            left.setAmount(left.getAmount() + right.getAmount());
            left.setVatAmount(left.getVatAmount() + right.getVatAmount());
            return left;
        })));

List<VatType> result = new ArrayList<>(map.values());

If you could add a constructor to VatType that accepts a InvoiceDetail instance and a merge method that combines two VatType instances:

public VatType(String vatGroup, Double vatAmount, Double amount) {
    this.vatGroup = vatGroup;
    this.vatAmount = vatAmount;
    this.amount = amount;
}

public VatType(InvoiceDetails details) {
    this(details.getVatGroup(), details.getVatAmount(), details.getAmount());
}

public VatType merge(VatType another) {
    this.vatAmount += another.vatAmount;
    this.amount += another.amount;
    return this;
}

Then, you could simplify the first snippet to:

Map<String, VatType> map = new HashMap<>();

invoiceList.forEach(i -> i.getDetailList().forEach(d ->
        map.merge(d.getVatGroup(), new VatType(d), VatType::merge)));

List<VatType> result = new ArrayList<>(map.values());

Upvotes: 1

Alex
Alex

Reputation: 136

You can also use dozer library if this is common task in your project.

Upvotes: 0

You can use Java stream Function:

Function<InvoiceDetails, VatType> myF = t -> new VatType(t.getVatGroup(), t.getVatAmount(), t.getAmount());

and then stream than using:

List<VatType> myLocations = 
        invoiceList.stream()
        .map(Invoice::getInvoiceDetails)
        .flatMap(Collection::stream)
        .map(myF)
        .collect(Collectors.<VatType>toList());

Upvotes: 1

Manos Nikolaidis
Manos Nikolaidis

Reputation: 22224

You will first need a flatMap to create a stream of all InvoiceDetails in all Invoice in your list. After that you can reduce with the variant of toMap that gets a merge method. Finally values() method of Map will get a Collection of VatType:

Collection<VatType> values = invoiceList.stream()
        .flatMap(invoices -> invoices.getInvoiceDetails().stream())
        .collect(toMap(InvoiceDetails::getVatGroup, VatType::new, (i1, i2) -> {
            i1.setAmount(i1.getAmount() + i2.getAmount());
            i1.setVatAmount(i1.getVatAmount() + i2.getVatAmount());
            return i1;
        }))
        .values();

Where this VatType constructor is used:

VatType(InvoiceDetails invoiceDetails) {
    vatGroup = invoiceDetails.getVatGroup();
    vatAmount = invoiceDetails.getVatAmount();
    amount = invoiceDetails.getAmount();
}

You can easily make a List from a Collection if you need one:

List<VatType> vatTypes = new ArrayList<>(values);

Upvotes: 2

Related Questions