【问题标题】:DTO from JSON with dynamic key来自带有动态密钥的 JSON 的 DTO
【发布时间】:2017-05-26 14:22:58
【问题描述】:

我试图弄清楚如何为 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 的任何想法?

【问题讨论】:

  • 假设您在评论的地方有 SubCategory 的列表,它是每个可能类别的超类。是否要使用 List 中对象类的名称?
  • 不完全是,名称可能与类别完全无关。所以每个 bucket 唯一的共同点是具有docCountkey 和其他一些子bucket 的结构。其中可能有更多的子桶。无止境。
  • 您将如何确定该字段的名称?有什么办法可以将它作为 Bucket 内的瞬态字符串传递?
  • 请原谅我的无知,但我将如何做到这一点,那又能做什么呢?如果我弄错了,请原谅我,但是在序列化时没有将属性transient not 转移?我的流程需要这个,因为事件流程是客户端 -> Java App ->(Rest 调用)Python App -> 在 JSON 之上返回到 Jav 应用程序 -> 返回给用户。所以理论上我可以只使用 Map 将相同的 JSON 传输回用户,但我想要类型安全性并且以后能够使用 DTO。
  • 好吧,我关于瞬态的错误。但无论如何,如果 sub_categories 的名称是动态的,它应该在某个阶段确定,对吧?例如,您是否能够将 private String subCategoryName 存储在 Bucket 中,或者您将如何确定此子类别名称适合当前响应?

标签: java json dto


【解决方案1】:

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

在我的示例中,我将其存储为实体成员 - 私有字符串实例。 (如果您使用 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 你可以得到所有的字段名。我解决了声明所有已知字段名称的问题,然后找到不属于此列表成员的字段。您也可以使用 switch 来通过字段名称调用 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
  }
}

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

【讨论】:

  • 虽然序列化不是问题 - 反序列化它是一种痛苦。我不知道 sub_category 字段的键名,因为它可能是任何东西。
猜你喜欢
  • 2021-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 2016-11-06
  • 1970-01-01
  • 2011-05-10
  • 1970-01-01
相关资源
最近更新 更多