DTO中的动态字段类型

时间:2019-04-30 01:30:52

标签: java spring spring-mvc jackson

我正在使用Spring-MVC,并且具有如下所示的DTO,以便从客户端(一个JSON实体)接收foo数据,并使用JPA将其保存到数据库中:

public class FooDTO {

    public Integer id;
    public String label;
    public Double amount;
    public List<Integer> states;
    ...

但是当客户想要编辑foo实体时,我必须像下面这样构造它

public class FooDTO {

    public Integer id;
    public String label;
    public Double amount;
    public List<SimpleDto> states;
    ...

使用SimpleDto

public class SimpleDto {
    public Integer value;
    public String label;
}

区别只是states类型,有时是List<SimpleDto>,有时是List<Integer>,而且我不想创建另一个dto。

那么如何在dto(json)中实现动态字段类型?

P.S JSON数据由com.fasterxml.jackson.core

处理

10 个答案:

答案 0 :(得分:5)

使用自定义解串器是解决问题的一种方法

http

发送请求并回打印的控制器

    public class DynamicDeserializer extends JsonDeserializer {
    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String requestString = jp.readValueAsTree().toString();
        JSONArray jo = new JSONArray(requestString);
        List<SimpleDto> simpleDtoList = new ArrayList<>();
        List<Integer> integers = new ArrayList<>();
        if(jo!=null && jo.length()>0) {
            for (int i = 0; i < jo.length(); i++) {
                Object string = jo.get(0);
                if(string!=null && string instanceof JSONObject){
                    JSONObject value = jo.getJSONObject(i);
                    SimpleDto simpleDto = new SimpleDto();
                    simpleDto.setValue(value.getInt("value"));
                    simpleDtoList.add(simpleDto);
                }else{
                    integers.add(jo.getInt(0));
                }
            }
        }


        return integers.isEmpty() ? simpleDtoList:integers;
    }
}

存在通用映射的pojo类

@PostMapping("/test")
    public Optional<TestObject> testDynamicMapper(
            @RequestBody final TestObject testObject) {
        List<Object> states = testObject.getStates();

        for (Object object:states) {
            if(object instanceof SimpleDto){
                SimpleDto dto = (SimpleDto)object;
                System.out.println(dto.getValue());
            }
            if(object instanceof Integer){
                Integer dto = (Integer)object;
                System.out.println(dto);
            }
        }


        return Optional.of(testObject);
    }

对象列表的输入有效载荷

public class TestObject implements Serializable {

    @JsonDeserialize(using = DynamicDeserializer.class)
    private List<Object> states;


    public List<Object> getStates() {
        return states;
    }

    public void setStates(List<Object> states) {
        this.states = states;
    }


}

整数列表的输入有效载荷

{
  "states": [
    {
      "label": "1",
      "value": 0
    }
  ]
}

答案 1 :(得分:0)

为您的DTO类型使用弹簧类型转换器。这样,客户端可以发布stateId,并且Converter将为给定ID解析正确的DTO类型。

下面是一个示例:https://www.baeldung.com/spring-type-conversions

答案 2 :(得分:0)

我建议您使用其他类:FooInfoDTO,FooDetailsDTO。当您有主从表单时,通常使用它。在主表中,显示有关对象的简短信息(一个DTO),然后导航至详细信息,以获取完整的对象数据(另一个DTO)

答案 3 :(得分:0)

我建议不要添加另一个促进重复的DTO。 但是,您仍然需要添加另一个专用于您各自服务的DTO。您只需使用层次结构定义DTO。

public class FooDTO {

    public Integer id;
    public String label;
    public Double amount;
}

通过扩展通用详细信息DTO(即FooDTO)来定义您的响应DTO以提供详细信息,

public class FooDetailsOutDTO extends FooDTO {

    public List<Integer> states;

}

要进行编辑,请按以下方式定义DTO,

public class FooUpdateDetailsInDTO extends FooDTO {

     public List<SimpleDto> states;

}

答案 4 :(得分:0)

您可以使用JsonCreator批注和项目Node的两个构造函数。如果数组POJO中有一个原语,将使用构造函数。对于完全设置的对象1-arg,将使用构造函数。参见以下示例:

2-arg

上面的代码显示:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

public class JsonApp {

    public static void main(String[] args) throws Exception {
        String json = "{\"id\":1,\"label\":\"LABEL\",\"amount\":1.23,\"states\":[1,{\"value\":2},{\"value\":3,\"label\":\"LAB\"}]}";
        ObjectMapper mapper = new ObjectMapper();

        Foo foo = mapper.readValue(json, Foo.class);
        System.out.println(foo);
    }
}

class Foo {

    private Integer id;
    private String label;
    private Double amount;
    private List<State> states;

    // getters, setters, toString
}

class State {

    private Integer value;
    private String label;

    @JsonCreator(mode = Mode.DELEGATING)
    public State(@JsonProperty("value") Integer value) {
        this(value, null);
    }

    @JsonCreator
    public State(@JsonProperty("value") Integer value, @JsonProperty("label") String label) {
        this.value = value;
        this.label = label;
    }

    // getters, setters, toString
}

使用的版本:2.9.8

答案 5 :(得分:0)

写一个dto

public class FooDTO {

    public Integer id;
    public String label;
    public Double amount;
    public List<Object> states;
}

在Service类中对DTO进行类型转换并处理异常

答案 6 :(得分:0)

另一种重新建模方法:

 public class FooDTO {

    public Integer id;
    public String label;
    public Double amount;
    //not null!
    public List<Integer> states;
    //nullable!!
    ... List<String> stateLabels;
   // you should ensure "stable/consistent index/ordering" (relative to states)
   ...

...并相应地将其用于“获取”(单独访问标签)和“发布”(忽略标签;)

---------------------------------------------

甚至更好:

   Map<Integer, String> states; // !?

答案 7 :(得分:0)

您只需添加一个在SimpleDto

中返回Integer的getter即可。

添加使用简单的Java Stream在List<Integer>中返回FooDTO的getter,该Java流使用DTO getter映射到Integer

states.stream().map(SimpleDto::getValue).collect(Collectors.toList());

答案 8 :(得分:0)

您可以尝试重新设计整个体系结构。使用相关集合拆分主要实体。

提供独立服务,以根据您的实体添加/删除/设置状态。通过这种方式,您可以轻松地向您的客户端提供REST服务,这些服务将很容易使用。

这是通过REST接口实现的一组可能的方法:

@Path(../foo)
@Produces
public interface FooService {
  //CRUD methods on Foo itself which work with attributes of Foo only
  ...
  @GET
  @Path("/{fooId}")
  FooDTO findById(@PathParam("fooId") int fooId);

  //status-related methods:
  @GET
  @Path("/{fooId}/status")
  List<SimpleDto> statuses(@PathParam("fooId") int fooId);

  @Post
  @Path("/{fooId}/status")
  void addStatus(@PathParam("fooId") int fooId, int statusId);

  @DELETE
  @Path("{fooId}/status")
  void deleteStatus(@PathParam("fooId") int fooId, int statusId);

  @PUT
  @Path("/status")
  void setStatuses(@PathParam("fooId") int fooId, List<Integer> newStatuses);
}

使用此解决方案,还有一些替代选项,我希望返回:

  @GET
  @Path("/{fooId}/status")
  List<Integer> statuses(@PathParam("fooId") int fooId);

代替DTO列表。然后提供一项服务,以获取所有状态及其名称,而无需连接到Foo:

public interface StatusService {
  List<SimpleDto> statuses();
}

为简化GUI组件的实现,您可以创建Rest服务,该服务将返回合并的数据,例如在您的第二个FooDto版本中。它还将减少休息电话的数量。但是使用单独的方法直接处理一组项目会很有帮助。

答案 9 :(得分:-1)

您可以使用generics,将List<Integer> states更改为List<?> states