본문 바로가기
에러

[Spring] Could not write JSON: (was java.lang.NullPointerException); (feat. Jackson 이해하기)

by joeun 2022. 4. 7.

문제 상황


@Controller
public class CommentController {
    @Autowired
    CommentService commentService;

    // 지정된 게시물의 모든 댓글을 가져오는 메서드
    @GetMapping("/comments")    // comments?bno=1080 GET
    @ResponseBody
    public List<CommentDto> list(Integer bno){
        List<CommentDto> list = null;

        try {
            list = commentService.getList(bno);
            System.out.println("list = " + list);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }
}

 

컨트롤러에서 게시글 번호(bno)를 쿼리 스트링으로 받아서

@ResponseBody로 해당 게시물의 댓글 객체 리스트를 화면에서 보여주도록 코드를 실행하던 중 해당 에러를 만났다. 

 

 

 

 

 

 

 

 

에러 메시지 및 원인 파악


500번대 에러 발생

 

메시지 

Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.fastcampus.ch4.domain.CommentDto["pcno"])

 

예외

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.fastcampus.ch4.domain.CommentDto["pcno"])

 

근본 원인 (root cause)

com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.fastcampus.ch4.domain.CommentDto["pcno"])

 

Controller에서 return 하기 전 DB에서 가져온 리스트를 System.out.println으로 찍는 코드까지는 실행되는 것으로 보아

DB에서 데이터를 가져와서 Dto 타입으로 리스트를 만드는 것 까지는 문제가 없어보이고...

에러 메시지를 보니 객체 리스트를 JSON 타입으로 return 하는 과정에서 null 관련 문제가 있는 듯 하다.  

 

 

 

 

 

 

 

 

다른 데이터로 시도해보니


 

DB에 저장된 데이터

 

 

 

bno 컬럼 값이 523인 데이터의 pcno 값은 null이 아닌 0이기 때문에

"/comments?bno=523"로 접근했을 때는 아래와 같이 값이 화면에 잘 출력된다. 

 

[{"cno":16,"bno":523,"pcno":0,"comment":"hi","commenter":"qwer","reg_date":1649312164000,"up_date":1649312164000}]

 

 

 

 

 

 

 

 

현재 코드 다시 살펴보기


변경 전 CommentDto 

public class CommentDto {
    private Integer cno;
    private Integer bno;
    private Integer pcno;
    private String comment;
    private String commenter;
    private Date reg_date;
    private Date up_date;

    public CommentDto() {
    }

    public CommentDto(Integer bno, Integer pcno, String comment, String commenter) {
        this.bno = bno;
        this.pcno = pcno;
        this.comment = comment;
        this.commenter = commenter;
    }

    public int getCno() {
        return cno;
    }

    public CommentDto setCno(int cno) {
        this.cno = cno;
        return this;
    }

    public int getBno() {
        return bno;
    }

    public CommentDto setBno(int bno) {
        this.bno = bno;
        return this;
    }

    public int getPcno() {
        return pcno;
    }

    public CommentDto setPcno(int pcno) {
        this.pcno = pcno;
        return this;
    }

    public String getComment() {
        return comment;
    }

    public CommentDto setComment(String comment) {
        this.comment = comment;
        return this;
    }

    public String getCommenter() {
        return commenter;
    }

    public CommentDto setCommenter(String commenter) {
        this.commenter = commenter;
        return this;
    }

    public Date getReg_date() {
        return reg_date;
    }

    public CommentDto setReg_date(Date reg_date) {
        this.reg_date = reg_date;
        return this;
    }

    public Date getUp_date() {
        return up_date;
    }

    public CommentDto setUp_date(Date up_date) {
        this.up_date = up_date;
        return this;
    }

}

 

기본형은 null을 처리하지 못한다 하여 멤버변수 타입을 모두 참조형으로 선언했음에도

매핑이 되지 않아 의문이 들었는데 서치하다 보니 그 답을 알게 되었다. 

 

 

 

 

 

 

 

 

답은 Jackson 동작 방식에 있었다.


 

stackoverflow 답변 中

멤버변수 타입을 참조형인 Integer로 줬지만 Getter 메서드 리턴타입이 기본형인 int 였기 때문에 생긴 문제였다. 

cno, bno의 Getter 리턴타입 역시 int 였지만 해당 값이 null이 아니었기 때문에 잘 매핑되었다. 

 

 

 

 

 

pcno의 Getter의 리턴타입을 Integer로 바꿔주니 null 값 그대로 잘 출력되었다. 

 

public Integer getPcno() {
    return pcno;
}
[{"cno":18,"bno":524,"pcno":null,"comment":"hello","commenter":"asdf","reg_date":1649313167000,"up_date":1649313167000}]

 

 

 

 

 

 

 

 

Jackson과 Getter 메서드


 

현재 Jackson 라이브러리를 사용하고 있는데 데이터 매핑 방식이 궁금하여 서치해보니

Jackson은 Getter를 통해 데이터를 매핑한다고 한다...!

 

 

 

 

 

실제로 아래의 두 경우를 실행해본 결과, 

1. pcno의 Getter를 주석 처리

2. pcno의 Getter 메서드에 @JsonIgnore 붙이기

 

[{"cno":18,"bno":524,"comment":"hello","commenter":"asdf","reg_date":1649313167000,"up_date":1649313167000}]

모두 에러 없이 pcno의 값을 제외한 객체가 반환되었다.

Getter 메서드가 없으니 해당 프로퍼티는 아예 취급하지 않은 것이다. 

 

 

 

 

 

사실 Java에는 프로퍼티를 제공하는 문법이 없고

Java의 프로퍼티는 Getter와 Setter의 이름 명명 규칙으로 정해진다고 한다. 

Jackson 라이브러리는 프로퍼티 개념으로 작동하기 때문에

멤버변수의 유무와 상관없이 Getter, Setter를 기준으로 동작한다.

때문에 멤버변수의 타입이 참조형이었음에도 null을 처리하지 못했던 것이다. 

 

 

 

Jackson 라이브러리 사용 시 Getter 메서드의 return 타입을 신경쓸 것!!

 

 

 


 

 

 

에러가 안 났으면 Jackson의 동작 방식에 대해 크게 생각 안 하고 넘어갔을 텐데 참 반가운 에러다.

지금도 정말 기본적인 내용만 습득한 상태라 앞으로 Jackson 라이브러리에 대해서 더 알아보고 정리할 예정이다.

 

 

 

 

 

 

 

 


 

 참고 

 

JsonMappingException (was java.lang.NullPointerException)

I've been searching for this for a while but haven't found any answers, so either I'm missing something so obvious noone has written anything about it, or I've hit an unusual problem. I'm hoping it...

stackoverflow.com

 

 

[Spring] Jackson 라이브러리 이해하기.

안녕하세요. 오늘은 Spring 프레임워크에 빼놓을 수 없는 라이브러리중 하나인 Jackson 에 대해 간단하게 포스팅 합니다. 주의!! 해당 포스팅은 Jackson 의 라이브러리 2.9.7 버전을 다룹니다. 또한, 프

mommoo.tistory.com