java-带有动态密钥的JSON中的DTO

我试图弄清楚如何为Spring Boot应用编写出色的DTO,该应用将搜索功能代理到另一个(Python)服务.

所以我目前有一个几乎完美的设置.我只是在将我从Elasticsearch获得的聚合表示为Java端的对象时遇到问题.

这是当前的汇总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键是一个问题,因为它是一个动态名称.由于存储桶可以嵌套在Elasticsearch中,因此它也将是Bucket类型.

关于如何将这些存储桶表示为自定义对象而不仅仅是Map的想法?

解决方法:

您可以使用自定义序列化器来构建动态JSON响应.
但是您应该以某种方式将动态类别名称传递给此序列化程序.

在我的示例中,我将其存储为Entity成员-私有String实例. (如果使用JPA实体,请使用@Transient批注,以免将此字段映射到数据库结构)

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可以获取所有字段名称.我解决了该问题,声明了所有已知的字段名称,然后找到了不是该列表成员的字段.您也可以使用switch来通过字段名称调用设置器,也可以创建另一个映射器.您还可以查看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
  }
}

响应使用标准名称序列化,因为我没有使用任何自定义序列化器.您也可以使用我在原始帖子中建议的自定义序列化器对其进行自定义.

上一篇:如何在Spring Boot中使用Dozer?


下一篇:java8的List