我有一个JSON文档,它是解析一堆文件的结果:
{
"offer": {
"clientName": "Tom",
"insuranceCompany": "INSURANCE",
"address": "GAMLE BONDALSVEGEN 53",
"renewalDate": "22.12.2018",
"startDate": "22.12.2017",
"too_old": false,
"products": [
{
"productType": "TRAVEL",
"objectName": "Reiseforsikring - Holen, Tom Andre",
"name": null,
"value": null,
"isExclude": false,
"monthPrice": null,
"yearPrice": 1637,
"properties": {}
}
]
},
"documents": [
{
"clientName": "Tom",
"insuranceCompany": "INSURANCE",
"fileName": "insurance_tom.pdf",
"address": "GAMLE BONDALSVEGEN 53",
"renewalDate": "22.12.2019",
"startDate": "22.12.2018",
"issuedDate": "20.11.2018",
"policyNumber": "6497777",
"products": [
{
"productType": "TRAVEL",
"objectName": "Reiseforsikring - Holen, Tom Andre",
"name": null,
"value": null,
"isExclude": false,
"monthPrice": null,
"yearPrice": 1921,
"properties": {
"TRAVEL_PRODUCT_NAME": "Reise Ekstra",
"TRAVEL_DURATION_TYPE": "DAYS",
"TRAVEL_TYPE": "FAMILY",
"TRAVEL_DURATION": "70",
"TRAVEL_INSURED_CLIENT_NAME": "Holen, Tom Andre, Familie"
}
},
我想遍历products
部分中的所有documents
,并从properties
部分中将错过的products
设置为offer
。
在JSON上提供和深度相同的文档。
使用Stream API的实现如下:
private void mergePropertiesToOffer(InsuranceDocumentsSession insuranceSession) {
Validate.notNull(insuranceSession, "insurance session can't be null");
if (insuranceSession.getOffer() == null) return;
log.info("BEFORE_MERGE");
// merge all properties by `objectName`
Stream.of(insuranceSession).forEach(session -> session.getDocuments().stream()
.filter(Objects::nonNull)
.flatMap(doc -> doc.getProducts().stream())
.filter(Objects::nonNull)
.filter(docProduct -> StringUtils.isNotEmpty(docProduct.getObjectName()))
.filter(docProduct -> MapUtils.isNotEmpty(docProduct.getProperties()))
.forEach(docProduct -> Stream.of(session.getOffer())
.flatMap(offer -> offer.getProducts().stream())
.filter(Objects::nonNull)
.filter(offerProduct -> MapUtils.isEmpty(offerProduct.getProperties()))
.filter(offerProduct -> StringUtils.isNotEmpty(offerProduct.getObjectName()))
.filter(offerProduct -> offerProduct.getObjectName().equals(docProduct.getObjectName()))
.forEach(offerProduct -> {
try {
ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
log.info("BEFORE_PRODUCT: {}", mapper.writeValueAsString(offerProduct));
offerProduct.setProperties(docProduct.getProperties());
log.info("UPDATED_PRODUCT: {}", mapper.writeValueAsString(offerProduct));
} catch (JsonProcessingException e) {
log.error("Error converting product to offer: {}", e.getCause());
}
})));
log.info("AFTER_MERGE");
}
工作正常。但是,实施比将来的维护要快得多。
我有两次使用Stream.of()
工厂方法来获取不同级别的2个实体的流。另外,尽可能多地使用flatMap()
和所有空检查。
问题是这个实现不是太困难了吗?
是否应该将其重构并分成较小的部分?如果是的话,采用良好的编程原则应该如何?
解决方案:
非常感谢nullpointer
的回答。
最终解决方案如下:
Map<Integer, InsuranceProductDto> offerProductMap = session.getOffer().getProducts()
.stream()
.filter(this::validateOfferProduct)
.collect(Collectors.toMap(InsuranceProductDto::getYearPrice, Function.identity(), (first, second) -> first));
Map<Integer, InsuranceProductDto> documentsProductMap = session.getDocuments()
.stream()
.flatMap(d -> d.getProducts().stream())
.filter(this::validateDocumentProduct)
.collect(Collectors.toMap(InsuranceProductDto::getYearPrice, Function.identity(), (first, second) -> first));
documentsProductMap.forEach((docPrice, docProduct) -> {
if (offerProductMap.containsKey(docPrice)) {
offerProductMap.compute(docPrice, (s, offerProduct) -> {
setProductProperties(offerProduct, docProduct);
return offerProduct;
});
}
});
// after finishing execution `offerProductMap` contains updated products
答案 0 :(得分:3)
首先,您可以为这些链接的过滤器创建一个通用的Predicate
,
.filter(offerProduct -> MapUtils.isEmpty(offerProduct.getProperties()))
.filter(offerProduct -> StringUtils.isNotEmpty(offerProduct.getObjectName()))
.filter(offerProduct -> offerProduct.getObjectName().equals(docProduct.getObjectName()))
您可以写一个Predicate
这样
Predicate<OfferProduct> offerProductSelection = offerProduct -> MapUtils.isEmpty(offerProduct.getProperties())
&& StringUtils.isNotEmpty(offerProduct.getObjectName())
&& offerProduct.getObjectName().equals(docProduct.getObjectName());
然后将其用作单个过滤器
.filter(offerProductSelection);
顺便说一句,您最好将其移动到返回boolean
的方法中,然后在过滤器中使用它。
就所使用的数据类型和实用程序类而言,并不精确,但为表示起见,您可以执行以下操作:
private void mergePropertiesToOffer(InsuranceDocumentsSession insuranceSession) {
Validate.notNull(insuranceSession, "insurance session can't be null");
if (insuranceSession.getOffer() == null) return;
Map<String, InsuranceProductDto> offerProductMap = insuranceSession.getOffer().getProducts()
.stream()
.filter(this::validateOfferProduct)
.collect(Collectors.toMap(InsuranceProductDto::getObjectName, Function.identity())); // assuming 'objectName' to be unique
Map<String, InsuranceProductDto> documentsProductMap = insuranceSession.getDocuments()
.stream()
.filter(Objects::nonNull)
.flatMap(d -> d.getProducts().stream())
.filter(this::validateDocumentProduct)
.collect(Collectors.toMap(InsuranceProductDto::getObjectName, Function.identity())); // assuming 'objectName' to be unique
Map<String, Product> productsToProcess = new HashMap<>(documentsProductMap);
productsToProcess.forEach((k, v) -> {
if (offerProductMap.containsKey(k)) {
offerProductMap.compute(k, (s, product) -> {
Objects.requireNonNull(product).setProperties(v.getProperties());
return product;
});
}
});
// now the values of 'offerProductMap' is what you can set as an updated product list under offer
}
private boolean validateDocumentProduct(InsuranceProductDto product) {
return Objects.nonNull(product)
&& MapUtils.isNotEmpty(product.getProperties())
&& StringUtils.isNotEmpty(product.getObjectName());
}
private boolean validateOfferProduct(InsuranceProductDto offerProduct) {
return Objects.nonNull(offerProduct)
&& MapUtils.isEmpty(offerProduct.getProperties())
&& StringUtils.isNotEmpty(offerProduct.getObjectName());
}
编辑 :根据评论,
objectName对于一堆产品可以相同
您可以更新代码以将合并功能用作:
Map<String, InsuranceProductDto> offerProductMap = insuranceSession.getOffer().getProducts()
.stream()
.filter(this::validateOfferProduct)
.collect(Collectors.toMap(InsuranceProductDto::getObjectName, Function.identity(),
(a,b) -> {// logic to merge and return value for same keys
}));
答案 1 :(得分:1)
对于每个会话,所有要约产品的属性都将引用最后一个合格文档产品的属性,对吗?
因为内部流始终将评估为相同的结果,而与当前文档产品无关。
因此,在纠正此问题的同时,我将建议以下重构:
final class ValueWriter
{
private final static ObjectMapper mapper = new ObjectMapper();
static
{
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
static String writeValue(final Object value) throws JsonProcessingException
{
return mapper.writeValueAsString(value);
}
}
private Optional<Product> firstQualifiedDocumentProduct(final InsuranceDocumentsSession insuranceSession)
{
return insuranceSession.getDocuments().stream()
.filter(Objects::notNull)
.map(Document::getProducts)
.flatMap(Collection::stream)
.filter(docProduct -> StringUtils.isNotEmpty(docProduct.getObjectName()))
.filter(docProduct -> MapUtils.isNotEmpty(docProduct.getProperties()))
.findFirst()
;
}
private void mergePropertiesToOffer(final InsuranceDocumentsSession insuranceSession)
{
Validate.notNull(insuranceSession, "insurance session can't be null");
if(insuranceSession.getOffer() == null) return;
log.info("BEFORE_MERGE");
final Optional<Product> qualifiedDocumentProduct = firstQualifiedDocumentProduct(insuranceSession);
if (qualifiedDocumentProduct.isPresent())
{
insuranceSession.getOffer().getProducts().stream()
.filter(Objects::nonNull)
.filter(offerProduct -> MapUtils.isEmpty(offerProduct.getProperties()))
.filter(offerProduct -> StringUtils.isNotEmpty(offerProduct.getObjectName()))
.filter(offerProduct -> offerProduct.getObjectName().equals(qualifiedDocumentProduct.get().getObjectName()))
.forEach(offerProduct ->
{
try
{
log.info("BEFORE_PRODUCT: {}", ValueWriter.writeValueAsString(offerProduct));
offerProduct.setProperties(qualifiedDocumentProduct.get().getProperties());
log.info("BEFORE_PRODUCT: {}", ValueWriter.writeValueAsString(offerProduct));
}
catch (final JsonProcessingException e)
{
log.error("Error converting product to offer: {}", e.getCause());
}
})
;
}
}