来自JSON的DTO和动态密钥

我正在试图弄清楚如何为一个Spring Boot应用程序编写一个很好的DTO,它将搜索function代理到另一个(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> 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 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任何想法?

您可以使用自定义序列化程序来构建动态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 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 { public BucketSerializer() { this(null); } public BucketSerializer(Class 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依赖:

  com.fasterxml.jackson.core jackson-databind 2.8.8  

_____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 buckets = new ArrayList<>(); private int docCountErrorUpperBound; private int sumOtherDocCount; public List getBuckets() { return buckets; } public void setBuckets(List 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 { public static final String DOC_COUNT = "doc_count"; public static final String KEY = "key"; public static final List knownFieldNames = Arrays.asList(DOC_COUNT, KEY); public BucketDeserializer() { this(null); } public BucketDeserializer(Class 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 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 } } 

响应使用标准名称序列化,因为我没有使用任何自定义序列化程序。 您可以使用我在原始post中提出的自定义序列化程序来自定义它。