Home > Back-end >  Java-Stream - Mapping a Model to Entity - One-to-Many transformation
Java-Stream - Mapping a Model to Entity - One-to-Many transformation

Time:09-18

I'm trying to map a model TradeValidationData to an entity TradeValidatioEntity using map() operation is the stream.

Here are the models:

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TradeValidationData {
    @JsonProperty("orderId")
    public Integer orderId;
    
    @JsonProperty("tradeValidations")
    public List<TradeValidation> tradeValidations;
}

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TradeValidation {
    @JsonProperty("clientReferenceId")
    public String clientReferenceId;
    
    @JsonProperty("tradeValidationMessages")
    public List<TradeValidationMessage> tradeValidationMessages;
}

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TradeValidationMessage {
    @JsonProperty("validationCode")
    public Integer validationCode;
    
    @JsonProperty("validationMessage")
    public String validationMessage;
}

Here is the entity:

public class TradeValidationEntity implements Serializable {
    
    private static final long serialVersionId = 1L;
    
    @EmbeddedId
    private TradeValidationCompositeKey tradeValidationCompositeKey;
    
    @Column(name = "validation_message")
    private String validationMessage;
}

public class TradeValidationCompositeKey implements Serializable {
    private static final long serialVersionId = 1L;
    
    @Column(name = "order_id")
    private Integer orderId;
    
    @Column(name = "client_reference_id")
    private String clientReferenceId;
    
    @Column(name = "function_type_id")
    private Integer functionTypeId;
    
    @Column(name = "validation_id")
    private Integer validationId;
}

I have a model object TradeValidationData which in turn has a List<TradeValidation>. I need to transform each model TradeValidation into an entity TradeValidationEntity.

Each TradeValidation contains a list of messages List<TradeValidationMessage>.

In order to initialize TradeValidationEntity I need to create a composite key TradeValidationCompositeKey, which requires data from an instance of TradeValidation and a message.

Here's my previous code which makes use of the Stream.forEach() which is working fine. I hope it would help to convey my intention.

var tradeValidationEntities = new ArrayList<TradeValidationEntity>();

tradeValidationData.tradeValidations.stream().forEach(tradeValidation -> {
    tradeValidation.tradeValidationMessages.stream().forEach(tradeValidationMessage -> {
        var tradeValidationCompositeKey = new TradeValidationCompositeKey(orderId,
            tradeValidation.clientReferenceId, functionTypeId, tradeValidationMessage.getValidationCode());
            
        TradeValidationEntity tradeValidationEntity = new TradeValidationEntity();
        tradeValidationEntity.setTradeValidationCompositeKey(tradeValidationCompositeKey);
        tradeValidationEntity.setValidationMessage(tradeValidationMessage.validationMessage);
        
                tradeValidationEntities.add(tradeValidationEntity);
    });
});

And here's the current problematic attempt to map TradeValidationData to TradeValidatioEntity:

List<TradeValidationEntity> tradeValidationEntities = tradeValidationData.tradeValidations.stream()
    .map(tradeValidation -> {
        var tradeValidationEntity = new TradeValidationEntity();
        tradeValidation.tradeValidationMessages.stream()
            .map(tradeValidationMessage -> {
                var tradeValidationCompositeKey = new TradeValidationCompositeKey();
                tradeValidationCompositeKey.setOrderId(orderId);
                tradeValidationCompositeKey.setClientReferenceId(tradeValidation.clientReferenceId);
                tradeValidationCompositeKey.setFunctionTypeId(functionTypeId);
                tradeValidationCompositeKey.setValidationId(tradeValidationMessage.validationId);
                tradeValidationEntity.setTradeValidationCompositeKey(tradeValidationCompositeKey);
                tradeValidationEntity.setValidationMessage(tradeValidationMessage.getValidationMessage());
                return tradeValidationEntity;
            });
        return tradeValidationEntity;
    })
    .collect(Collectors.toList());

I think the issue is with the second return statement I have for tradeValidationEntity. But I'm not sure what to return instead.

CodePudding user response:

You need to transform each TradeValidation object in the stream into multiple TradeValidationEntity instances.

map() operation is not the right tool for that purpose, it's meant for one-to-one transformation. Its function take one object and produces only one object.

To perform one-to-many transformation, you can use flatMap() operation. It expects a function, which take an element and produces a stream of elements.

That's how it might be implemented.

List<TradeValidationEntity> tradeValidationEntities = tradeValidationData.tradeValidations.stream()
    .flatMap(tradeValidation -> tradeValidation.tradeValidationMessages.stream()
        .map(tradeValidationMessage -> {
            var tradeValidationCompositeKey = new TradeValidationCompositeKey(orderId,
                tradeValidation.clientReferenceId, functionTypeId, tradeValidationMessage.getValidationCode());
    
            TradeValidationEntity tradeValidationEntity = new TradeValidationEntity();
            tradeValidationEntity.setTradeValidationCompositeKey(tradeValidationCompositeKey);
            tradeValidationEntity.setValidationMessage(tradeValidationMessage.validationMessage);
            
                    return tradeValidationEntity;
        })
    )
    .toList(); // for Java 16  or collect(Collectors.toList())

Note:

  • map() - is an intermediate operation, it doesn't produce a result, it spawns another stream.
  • A stream which doesn't ends with a terminal operation will not be executed (unless it's consumed by another stream via some operations like concat(), or flatMap()).
  • Multiline lambda expressions are difficult to read. Readability and conciseness is a main weapon of streams. I would advise considering extracting heavy logic from a function into a separate method with a self-explanatory name and replacing the multiline lambda with method reference.
  • Related