【问题标题】:Dynamic field type in DTODTO 中的动态字段类型
【发布时间】:2019-04-30 01:30:52
【问题描述】:

我正在使用Spring-MVC,并且我有一个如下结构的 DTO,用于从客户端(foo 实体)接收JSON 数据,并使用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&lt;SimpleDto&gt;,有时是 List&lt;Integer&gt; 我不想创建另一个 dto。

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

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

【问题讨论】:

  • 我不明白。只需将其设为Object 并使用简单的instance of,为什么这么复杂?
  • @Eugene 是的 instance of 对于原始类型,但对于我的 SimpleDto,我认为我必须使用 @ManojRamanan 吗?
  • 你知道instanceOf 是做什么的吗?你说你有SimpleDto vs Integer。我也不明白你为什么不创建另一个 DTO,它并不复杂。
  • @Eugene 我不想重复自己,因为我的 DTO 有很多共享属性。
  • 在这种情况下,创建一个具有共享字段的公共类和扩展该字段的两个新类。

标签: java spring spring-mvc jackson


【解决方案1】:

使用自定义反序列化器是解决问题的一种方法

    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;
    }
}

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

@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);
    }

有泛型映射的 pojo 类

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
    }
  ]
}

整数列表的输入负载

{
  "states": [
      1,2
  ]
}

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我不想使用额外的 DTO
    【解决方案3】:

    写一个dto

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

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

    【讨论】:

      【解决方案4】:

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

      这是一个例子:https://www.baeldung.com/spring-type-conversions

      【讨论】:

        【解决方案5】:

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

        public class FooDTO {
        
            public Integer id;
            public String label;
            public Double amount;
        }
        

        定义您的响应 DTO 以通过扩展通用详细信息 DTO 来提供详细信息,即 FooDTO,如下所示,

        public class FooDetailsOutDTO extends FooDTO {
        
            public List<Integer> states;
        
        }
        

        对于编辑,您将 DTO 定义如下,

        public class FooUpdateDetailsInDTO extends FooDTO {
        
             public List<SimpleDto> states;
        
        }
        

        【讨论】:

        • 我不想使用额外的 DTO
        【解决方案6】:

        您可以为项目POJO 使用JsonCreator 注释和两个构造函数。如果数组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
        }
        

        上面的代码打印:

        Foo{id=1, label='LABEL', amount=1.23, states=[State{value=1, label='null'}, State{value=2, label='null'}, State{value=3, label='LAB'}]}
        

        使用版本:2.9.8

        【讨论】:

        • 我不想使用空标签(null)
        • @Youssef,这只是toString 方法实现,它以这种方式显示这些对象。标签属性设置为 null 而不是 "null" String。请尝试我的解决方案,你会看到。
        • 我知道它有效,我在发布这个问题之前尝试了它......但我正在寻找具有真正动态字段类型的更好解决方案
        • @Youssef,real dynamic field type 是什么意思?您想如何区分Integers 和DTO 的列表?你可以创建List&lt;Object&gt;,一旦你收到Integer,对象就是Map。无法为IntegerDTO 动态映射它。您需要为此字段编写自定义反序列化器。我的解决方案在这两种情况下都为您提供相同的类型,您甚至可以将原语与 DTO 混合使用。
        【解决方案7】:

        另一种重新建模方法:

         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; // !?
        

        【讨论】:

          【解决方案8】:

          您可以在SimpleDto 中添加一个返回整数的getter

          使用简单的 java Stream 添加一个在 FooDTO 中返回 List&lt;Integer&gt; 的 getter,该 Stream 使用 DTO getter 映射到 Integer

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

          【讨论】:

            【解决方案9】:

            您可以尝试完全重新设计架构。使用相关集合拆分主实体。

            提供独立的服务来为您的实体添加/删除/设置状态。通过这种方式,您可以轻松地为您的客户端提供 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 版本中一样。它还将减少休息电话的数量。但是使用单独的方法直接处理项目集合会很有帮助。

            【讨论】:

              【解决方案10】:

              你可以用generics,把List&lt;Integer&gt; states改成List&lt;?&gt; states

              【讨论】:

                猜你喜欢
                • 2020-04-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-02-03
                • 2018-04-07
                • 2014-06-11
                • 2022-01-19
                • 1970-01-01
                相关资源
                最近更新 更多