Тип динамического поля в DTO - PullRequest
2 голосов
/ 30 апреля 2019

Я использую 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.SSON JSON обрабатываются com.fasterxml.jackson.core

Ответы [ 10 ]

5 голосов
/ 13 мая 2019

Использование пользовательского десериализатора - это один из способов решения вашей проблемы

    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
  ]
}
0 голосов
/ 13 мая 2019

Вы можете попробовать полностью изменить архитектуру.Разделить основную сущность с помощью связанных коллекций.

Предоставьте независимую услугу для добавления / удаления / установки статусов для вашей организации.Таким образом, вы можете легко предоставлять 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.версия.Это также уменьшит количество звонков на отдых.Но наличие отдельных методов для работы непосредственно с коллекцией предметов очень поможет.

0 голосов
/ 13 мая 2019

Вы можете просто добавить геттер, который возвращает Integer в SimpleDto

Добавьте метод получения, который возвращает List<Integer> в FooDTO, используя простой поток Java, который отображается на Integer, используя метод получения DTO

states.stream().map(SimpleDto::getValue).collect(Collectors.toList());
0 голосов
/ 10 мая 2019

Введите dto

public class FooDTO {

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

Введите ваше DTO в классе обслуживания и обработайте исключение

0 голосов
/ 12 мая 2019

Другой подход к повторному моделированию:

 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)
   ...

... и, соответственно, используйте его для «get» (индивидуальный доступ к меткам) и для «post» (без меток;)

‐------------------------‐----

и даже лучше:

   Map<Integer, String> states; // !?
0 голосов
/ 30 апреля 2019

Вы можете использовать JsonCreator аннотацию и два конструктора для элемента 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
}

Кодовые надписи:

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

0 голосов
/ 30 апреля 2019

Я бы предложил не добавлять другой 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;

}
0 голосов
/ 30 апреля 2019

Я предлагаю вам использовать разные классы: FooInfoDTO, FooDetailsDTO.Обычно используется, когда у вас есть формы мастер-деталей.В мастере (таблица) вы показываете краткую информацию об объектах (один DTO), а затем переходите к деталям и извлекаете полные данные об объекте (другой DTO)

0 голосов
/ 30 апреля 2019

вы можете использовать generics, изменить List<Integer> states на List<?> states

0 голосов
/ 30 апреля 2019

Используйте Spring Type Converter для вашего типа DTO.Таким образом, клиент может опубликовать stateId, и конвертер разрешит правильный тип DTO для данного идентификатора.

Вот пример: https://www.baeldung.com/spring-type-conversions

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...