我试图弄清楚如何为Spring Boot应用编写一个漂亮的DTO,它将搜索功能代理到另一个(Python)服务。
所以我目前有一个近乎完美的设置。我只是在代表我从Elasticsearch作为Java端的对象返回的聚合时遇到问题。
这是当前的Aggregation
DTO:
package com.example.dto.search;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter @Setter @NoArgsConstructor
public class Aggregation {
private List<Map<String, Object>> buckets;
private int docCountErrorUpperBound;
private int sumOtherDocCount;
}
查看JSON表示,如下所示:
{
"aggregations": {
"categories": {
"buckets": [
{
"doc_count": 12,
"key": "IT",
"sub_categories": {
"buckets": [
{
"doc_count": 12,
"key": "Programming"
}
],
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0
}
},
{
"doc_count": 1,
"key": "Handy Man",
"sub_categories": {
"buckets": [
{
"doc_count": 1,
"key": "Plumbing"
}
],
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0
}
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
},
....
我非常确定我可以像这样更改buckets
属性:
package com.example.dto.search;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
import java.util.Map;
@Getter @Setter @NoArgsConstructor
public class Aggregation {
private List<Bucket> buckets;
private int docCountErrorUpperBound;
private int sumOtherDocCount;
}
使用像这样的
开始的桶类package com.example.dto.search;
public class Bucket {
private int docCount;
private String key;
//What do I do here for sub_categories???
}
但是从JSON可以看出,sub_categories
键是问题所在,因为它是一个动态名称。它也是Bucket
类型,因为桶可以嵌套在Elasticsearch中。
有关如何将这些存储桶表示为自定义对象的任何想法,而不仅仅是Map
?
答案 0 :(得分:0)
您可以使用自定义序列化程序来构建动态JSON响应。 但是你应该以某种方式将动态类别名称传递给此序列化程序。
在我的示例中,我将其存储为Entity成员 - 私有String实例。 (如果您使用JPA实体,请使用@Transient
注释不将此字段映射到DB结构)
package com.example.dto.search;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.ArrayList;
@JsonSerialize(using = BucketSerializer.class)
public class Bucket {
private int docCount;
private String key;
// can be more specific if you have some superclass on top of all subcategories
private List<Object> subCategoryElements = new ArrayList<>();
private String nameOfSubcategory;
// getters
}
序列化器类:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.Optional;
public class BucketSerializer extends StdSerializer<Bucket> {
public BucketSerializer() {
this(null);
}
public BucketSerializer(Class<Bucket> t) {
super(t);
}
@Override
public void serialize(Bucket bucket, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("docCount", bucket.getDocCount());
gen.writeStringField("key", bucket.getKey();
gen.writeObjectField(Optional.ofNullable(bucket.getNameOfSubcategory()).orElse("unnamedCategory"),
bucket.getSubCategoryElements());
gen.writeEndObject();
}
}
Maven依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
<强> _____ EDIT_1 _____ 强>
我复制了你的案子并提出了一些建议。
发布代码我是如何解决这个问题的:
<强>模型强>:
// Aggregation
public class Aggregation {
private Categories categories;
public Categories getCategories() {
return categories;
}
public void setCategories(Categories categories) {
this.categories = categories;
}
}
// Cetagories
import java.util.ArrayList;
import java.util.List;
public class Categories {
private List<Bucket> buckets = new ArrayList<>();
private int docCountErrorUpperBound;
private int sumOtherDocCount;
public List<Bucket> getBuckets() {
return buckets;
}
public void setBuckets(List<Bucket> buckets) {
this.buckets = buckets;
}
public int getDocCountErrorUpperBound() {
return docCountErrorUpperBound;
}
public void setDocCountErrorUpperBound(int docCountErrorUpperBound) {
this.docCountErrorUpperBound = docCountErrorUpperBound;
}
public int getSumOtherDocCount() {
return sumOtherDocCount;
}
public void setSumOtherDocCount(int sumOtherDocCount) {
this.sumOtherDocCount = sumOtherDocCount;
}
}
//Bucket
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@JsonDeserialize(using = BucketDeserializer.class)
public class Bucket {
private int docCount;
private String key;
private Categories subCategories;
public int getDocCount() {
return docCount;
}
public void setDocCount(int docCount) {
this.docCount = docCount;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Categories getSubCategories() {
return subCategories;
}
public void setSubCategories(Categories subCategories) {
this.subCategories = subCategories;
}
}
<强>解串器强>:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class BucketDeserializer extends StdDeserializer<Bucket> {
public static final String DOC_COUNT = "doc_count";
public static final String KEY = "key";
public static final List<String> knownFieldNames = Arrays.asList(DOC_COUNT, KEY);
public BucketDeserializer() {
this(null);
}
public BucketDeserializer(Class<Bucket> c) {
super(c);
}
@Override
public Bucket deserialize(JsonParser jsonParser, DeserializationContext desContext) throws IOException {
Bucket bucket = new Bucket();
JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser);
ObjectMapper objectMapper = new ObjectMapper();
bucket.setDocCount(jsonNode.get(DOC_COUNT).asInt());
bucket.setKey(jsonNode.get(KEY).asText());
String unknownField = getUnknownField(jsonNode.fieldNames());
if (unknownField != null)
bucket.setSubCategories(objectMapper.convertValue(jsonNode.get(unknownField), Categories.class));
return bucket;
}
public String getUnknownField(Iterator<String> fieldNames) {
while (fieldNames.hasNext()) {
String next = fieldNames.next();
if (!knownFieldNames.contains(next))
return next;
}
return null;
}
}
主要想法是找到未知/动态字段/ json密钥。
从JsonNode可以获得所有字段名称。我解决了声明所有已知字段名称,然后找到不是此列表成员的字段。您也可以使用开关按字段名称调用setter或创建另一个映射器。您还可以查看org.json.JSONObject
类,它可以按索引号创建检索值。
你不关心嵌套桶,因为这个反序列化器也会处理它们。
这是我使用的JSON请求主体:
{
"categories": {
"buckets": [
{
"doc_count": 12,
"key": "IT",
"it_category": {
"buckets": [
{
"doc_count": 12,
"key": "Programming"
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
}
},
{
"doc_count": 1,
"key": "Handy Man",
"plumb_category": {
"buckets": [
{
"doc_count": 1,
"key": "Plumbing"
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
}
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
}
}
这是我得到的回应:
{
"categories": {
"buckets": [
{
"docCount": 12,
"key": "IT",
"subCategories": {
"buckets": [
{
"docCount": 12,
"key": "Programming",
"subCategories": null
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
}
},
{
"docCount": 1,
"key": "Handy Man",
"subCategories": {
"buckets": [
{
"docCount": 1,
"key": "Plumbing",
"subCategories": null
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
}
}
],
"docCountErrorUpperBound": 0,
"sumOtherDocCount": 0
}
}
响应是使用标准名称序列化的,因为我没有使用任何自定义序列化程序。你也可以使用我在原帖中提出的自定义序列化器来自定义它。