来自JSON的DTO和动态密钥

时间:2017-05-26 14:22:58

标签: java json dto

我试图弄清楚如何为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

1 个答案:

答案 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
  }
}

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