Десериализация XML Джексона, исключение InvalidDefinitionException: Конфликтующие определения геттера для свойства «время»

#spring-boot #spring-mvc #jackson #json-deserialization #xml-deserialization

Вопрос:

У меня есть такой DTO:

 package ...;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.*;
import lombok.experimental.FieldDefaults;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
@JacksonXmlRootElement(localName = "root")
public class RootDto {
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    LocalDateTime time;

    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    @JacksonXmlElementWrapper(localName = "times")
    @JacksonXmlProperty(localName = "time")
    List<InternalTimeDto> times;

    ...
}
 
 package ...;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import lombok.*;
import lombok.experimental.FieldDefaults;

import java.time.LocalDateTime;

@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class InternalTimeDto {
    @JacksonXmlProperty(isAttribute = true)
    Long id;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    LocalDateTime time;
}
 

Насколько я понимаю, проблема в том, что time поле и элементы times поля имеют одно и то же имя. Есть ли способ разрешить конфликт, не переименовывая time его во что-то другое? Теоретически, конфликта вообще не должно быть, так time как поле и элементы списка находятся на разных уровнях.
Пример XML:

 <root>
    <time>2020-11-18 12:34</time>
    <times>
        <time id="5">2020-11-18 10:00</time>
        <time id="6">2020-11-17 15:30</time>
    </times>

    ...
</root>
 

Я нашел пару ответов, но они решают проблему, когда два разных объекта с одинаковыми именами находятся на одном уровне.

Также используется JSON:

 {
  "time": "2020-11-18 12:34",
  "times": [
    {
      "id": 5,
      "time": "2020-11-18 10:00"
    },
    {
      "id": 6,
      "time": "2020-11-17 15:30"
    }
  ],
  ...
}
 

Ответ №1:

Один из способов решить проблему стирания конфликта из-за множественного определенного свойства time , сохраняющего ваш формат xml, — это создать Times класс, в который будут переноситься ваши List<InternalTimeDto> времена :

 public class Times {
    @JacksonXmlProperty(localName = "time")
    @JacksonXmlElementWrapper(useWrapping = false)
    List<InternalTimeDto> times;   
}
 

Таким образом, ваш RootDto класс может быть переписан таким образом:

 @Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@FieldDefaults(level = AccessLevel.PRIVATE)
@JacksonXmlRootElement(localName = "root")
public class RootDto {
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    LocalDateTime time;

    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    private Times times; //<-- times field instead of List<InternalTimeDto> times 
}
 

Вашему InternalTimeDto классу нужна JacksonXmlText аннотация для time поля:

 @Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class InternalTimeDto {
    @JacksonXmlProperty(isAttribute = true)
    Long id;

    @JacksonXmlText
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    LocalDateTime time;
}
 

Комментарии:

1. Спасибо, теперь это отлично работает для XML. К сожалению, чтобы все было просто, я не упомянул, что также используется JSON. Я обновил вопрос.

2. @YaroslavTarasenko Добро пожаловать. Использование моих классов В случае json в RootDto замене класса Times на List<InternalTimeDto> times , в противном случае, если вы хотите использовать один и тот же код для xml и json, единственной альтернативой является написание пользовательского десериализатора для Times класса.

3. Да, похоже, что пользовательский десериализатор-единственное решение в моем случае. Спасибо.