programing

잭슨이 역직렬화를 위해 노-아르그 컨스트럭터를 필요로 하는 시기는?

telebox 2023. 10. 29. 19:13
반응형

잭슨이 역직렬화를 위해 노-아르그 컨스트럭터를 필요로 하는 시기는?

봄 부츠 프로젝트에서 잭슨의 이상한 행동을 발견했습니다.인터넷으로 검색해보고 어떻게 해야할지 찾아봤지만, 왜 그런지는 아직도 모르겠어요.

사용자 대상:

@Setter
@Getter
@AllArgsConstructor
public class UserDto {

    private String username;

    private String email;

    private String password;

    private String name;

    private String surname;

    private UserStatus status;

    private byte[] avatar;

    private ZonedDateTime created_at;
}

새 사용자를 추가하는 것도 좋습니다.

TagDto:

@Setter
@Getter
@AllArgsConstructor
public class TagDto {

    private String tag;
}

새 태그를 추가하려고 하면 다음 오류가 발생합니다.

com.fasterxml.jackson.databind.exc.MismatchInputException: TagDto 인스턴스를 구성할 수 없음(Creator가 하나 이상 존재함): Object 값에서 deserialize할 수 없음(Delegate- 또는 Property-based Creator 없음)

이 문제에 대한 해결책은 TagDto 클래스에 zero-arg constructor를 추가하는 것이었습니다.

왜 잭슨은 UserDto로 잘 작동하면서 TagDto에서 deserialization을 위해 no-arg constructor를 요구합니까?

두 가지를 모두 추가할 때 동일한 방법을 사용합니다.내 태그 및 사용자 엔티티는 모두 다음과 같이 주석이 달렸습니다.

@Entity
@Setter
@Getter
@NoArgsConstructor

그리고 모든 arg compactor를 갖습니다.

@Entity
@Setter
@Getter
@NoArgsConstructor
public class User extends AbstractModel {

    private String username;

    private String password;

    private String email;

    private String name;

    private String surname;

    private UserStatus status;

    @Lob
    private byte[] avatar;

    @Setter(AccessLevel.NONE)
    private ZonedDateTime created_at;

    public User(final String username, final String password, final String email, final String name, final String surname) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.name = name;
        this.surname = surname;
        this.created_at = ZonedDateTime.now();
    }
}

@Entity
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Tag extends AbstractModel {

    private String tag;
}

@MappedSuperclass
@Getter
public abstract class AbstractModel {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
}

엔터티 생성:

    @PostMapping(path = "/add")
    public ResponseEntity<String> add(@Valid @RequestBody final D dto) {
        this.abstractModelService.add(dto);
        return new ResponseEntity<>("Success", HttpStatus.CREATED);
    }
    
    public void add(final D dto) {
    //CRUD repository save method
        this.modelRepositoryInterface.save(this.getModelFromDto(dto));
    }

    @Override
    protected Tag getModelFromDto(final TagDto tagDto) {
        return new Tag(tagDto.getTag());
    }

    @Override
    protected User getModelFromDto(final UserDto userDto) {
        return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
    }

JSON 구문 분석 시 오류 발생

{"tag":"example"}

우체부 localhost를 통해 발송됨:8081/태그/추가, 반품

{
    "timestamp": "2020-09-26T18:50:39.974+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "",
    "path": "/tag/add"
}

저는 롬복 v1.18.12와 스프링부트 2.3.3을 사용하고 있습니다.잭슨 v2.11.2와 함께 릴리즈.

TL;DR: 해결책은 끝에 있습니다.

Jackson은 POJO를 만드는 여러 가지 방법을 지원합니다.다음은 가장 일반적인 방법을 나열하지만 전체 목록은 아닐 가능성이 높습니다.

  1. no-arg constructor를 사용하여 인스턴스를 만든 다음 setter 메서드를 호출하여 속성 값을 할당합니다.

    public class Foo {
        private int id;
    
        public int getId() { return this.id; }
    
        @JsonProperty
        public void setId(int id) { this.id = id; }
    }
    

    지정은 선택 사항이지만 , , , ...와 같은 주석과 함께 매핑을 미세 조정하는 데 사용할 수 있습니다.

  2. 인수가 있는 생성자를 사용하여 인스턴스를 만듭니다.

    public class Foo {
        private int id;
    
        @JsonCreator
        public Foo(@JsonProperty("id") int id) {
            this.id = id;
        }
    
        public int getId() {
            return this.id;
        }
    }
    

    시공자 지정은 선택 사항이지만 시공자가 둘 이상일 경우 필수라고 생각합니다.지정하기@JsonProperty매개 변수의 경우 선택 사항이지만 매개 변수 이름이 클래스 파일에 포함되지 않은 경우 속성 이름을 지정하는 데 필요합니다(-parameters컴파일러 옵션).

    매개 변수는 속성이 필요함을 나타냅니다.setter 메서드를 사용하여 옵션 속성을 설정할 수 있습니다.

  3. 공장 메서드를 사용하여 인스턴스를 만듭니다.

    public class Foo {
        private int id;
    
        @JsonCreator
        public static Foo create(@JsonProperty("id") int id) {
            return new Foo(id);
        }
    
        private Foo(int id) {
            this.id = id;
        }
    
        public int getId() {
            return this.id;
        }
    }
    
  4. 다음을 사용하여 텍스트 값에서 인스턴스(instance(인스턴스)String시공자

    public class Foo {
        private int id;
    
        @JsonCreator
        public Foo(String str) {
            this.id = Integer.parseInt(id);
        }
    
        public int getId() {
            return this.id;
        }
    
        @JsonValue
        public String asJsonValue() {
            return Integer.toString(this.id);
        }
    }
    

    이것은 POJO가 단순한 텍스트 표현을 가질 때 유용합니다. 예를 들어,LocalDate는 3가지 속성을 가진 POJO입니다(year,month,dayOfMonth), 그러나 일반적으로 단일 문자열로 직렬화되는 것이 가장 좋습니다 (yyyy-MM-dd형식).@JsonValue 직렬화 시 사용할 방법을 식별하고,@JsonCreator역직렬화 시 사용할 시공자/공장-method을 식별합니다.

    참고: 다음 이외의 JSON 값을 사용한 단일 값 구성에도 사용할 수 있습니다.String, 하지만 그것은 매우 드뭅니다.

네, 그게 배경 정보였어요.질문의 예시들에 대해 무슨 일이 일어나고 있는지, 그것은UserDto생성자가 하나밖에 없기 때문에 작동합니다(따라서).@JsonCreator필요 없음), 그리고 많은 논쟁들 (그래서.@JsonProperty필요 없음).

하지만, 에 대해서는TagDto주석이 없는 단일 논항 생성자만 존재하므로 잭슨은 해당 생성자를 유형 #2가 아닌 유형 #4(위의 제 목록에서)로 분류합니다.

즉, POJO가 값 클래스가 될 것으로 예상되며, 여기서 엔클로저 객체의 JSON이 될 것입니다.{ ..., "tag": "value", ... },것은 아니다.{ ..., "tag": {"tag": "example"}, ... }.

이 문제를 해결하려면 Jackson에게 다음을 지정하여 생성자가 값 유형 생성자(#4)가 아닌 속성 초기화 생성자(#2)임을 알려주어야 합니다.@JsonProperty건설업자의 주장에 관하여.

즉, Lombok에서 생성자를 생성하도록 할 수 없습니다.

@Setter
@Getter
public class TagDto {

    private String tag;

    public TagDto(@JsonProperty("tag") String tag) {
        this.tag = tag;
    }
}

언급URL : https://stackoverflow.com/questions/64080983/when-does-jackson-require-no-arg-constructor-for-deserialization

반응형