ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • @JsonTypeInfo 를 통해 유연하게 Json과 Java객체 매핑하기
    Web-Spring 2025. 4. 14. 13:42

    상황

    게시판에 대해서 JPA와 Springboot를 통해 CRUD를 개발하려 하는데

    게시판은 종류가 여럿 존재하고(자유 게시판, 공지 게시판 등등),

    게시판 종류와 상관없는 전체 조회의 기능도 제공해야 했었다.

    또한 게시판별 종류별로 세부 요소들이 다를 수 있었다.

     

    예를들어 자유게시판은 텍스트 에디터로 만들어진 콘텐츠가 있으며,

    공지 게시판에는 다른 게시판에는 없는 "공지 종류"와 같은 추가적인 요소가 존재하는것이었다.

     

    따라서 생각한 DB 테이블 설계는 

    게시판별로 다른 요소를가진 테이블을 게시판 종류별로 설계하고

    공통요소들 (조회수, 좋아요수, 생성날짜, 게시날짜, 업데이트 날짜 등) 을 하나의 테이블로 관리하는것이다.

    공통요소가 있는 테이블과 게시판 종류별 테이블과의 1:1 관계를 맺는것으로 결론지었다.

    GPT로 생성한 대충 내 머릿속 테이블

     

    이 과정에서 Java에서는 상속을 통해 공통 요소를 묶는 작업을 충분히 수행할 수 있다.

     

    하지만 Java 객체로만 우리가 통신하지는 않기에, 프론트가 보내어주는 Json에는 상속정보를 알 수 없다.

     

    즉, 일반적으로 RequestBody를 매핑하는 과정에서 서로다른 클래스정보를 넘기는것이 일반적이었지만

    그렇다고 게시판 종류별로 DTO를 다 만들기에는 너무 귀찮다.

     

    이 문제를 해결했던 과정을 기술하였다.


    문제정의

    어떻게 하면 게시판 종류별 DTO를 만들지 않고 Json 을 읽어서 게시판 종류를 보고 상속된 Java Object 로의 직렬화 역직렬화를 하도록 유도할수 있을까?

    해결

    사실 상속에 관한 정보는 프론트로부터 받을 수 없지만, 어떤 게시판 종류인지에 대한 정보는 받을 수 있다.

     

    그럼 생각했을때, 요청본문의 Json이 Java객체로 Spring 내부동작에서 누군가가 바꿔줄 것이고

    Java객체로 변환하기전 "Json에 특정키가 특정한 값이라면, 이건 결국 OOO 게시판의 클래스로 매핑해야 합니다." 라는 정보를 주면 되지 않을까? 라는 생각이다.

     

    이걸 가능토록, Json에 타입정보를 부여하는 어노테이션이 바로 @JsonTypeInfo이다.

    해당 어노테이션에는 여러 프로퍼티가 있는데, 사용한 프로퍼티를 간략하게 설명하자면

     

    use: 후술에 나올 property 값이 어떤 종류인지 나타냄

    ex) use = JsonTypeInfo.Id.NAME: 별칭 문자열, JsonTypeInfo.Id.CLASS: FQCN(Full Qualified Class Name) 이라는 의미

    property:

    • Java Object => Json 의 경우: 해당 클래스로 매핑을 시도하는 Json 내에 타입을 나타내는 값이 있는 key
    • Json => Java Object 의 경우: 런타임의 인스턴스 내에 Object 내부 필드변수 명

    visible: true라면 property 와 use를 사용해서 얻은 타입정보를 소비만 하지말고 다시 Json에 넣어놓으라는 뜻

     

    이제 들어온 Json이 어떤 타입인진 알았다.

    하지만, property와 use 로 해석한 타입 문자열이 뭘 의미하는지 함께 알려줘야 한다.

    해당하는 의미정보를 넘기는 어노테이션이 바로 @JsonSubTypes이다.

    사용한 프로퍼티를 간단하게 설명하자면

     

    기본프로퍼티(value): 타입이 name으로 매칭될때 어떠한 클래스로 매핑해야하는지 정보

    name: 정의하고자 하는 JsonTypeInfo로 정의된 타입값

     

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "boardType", visible = true)
    @JsonSubTypes({
        @JsonSubTypes.Type(value = FreeBoard.class, name = "FREE"),
    })

     

    위를 이제 예로 해석해보자면

     

    "Json에서 boardType 이라는 key의 value값이 별칭 문자열인 타입정보이며, 사용만 하지말고 Json에 돌려놔라"

    "해당 타입 별칭문자열에서 FREE라는 문자열이라면 FreeBoard 의 클래스 정보로 매핑해라"

     

    로 해석해볼 수 있다.

     


    Case 

    다시 본론으로 넘어가서, 위의 @JsonTypeInfo 어노테이션과 @JsonSubTypes를 사용한 사례를 보자.

    @RestController
    @RequestMapping("/board")
    @RequiredArgsConstructor
    public class BoardController {
    
    	private final BoardService boardService;
        
    	@PostMapping
    	public ResponseEntity<PublishedResponse.newArticle> writeArticle(
    		@AuthenticationPrincipal AuthUser authUser,
    		@RequestBody final BoardRequest.BoardBase requestBody,
    		@PathVariable Optional<Long> boardId
    	) {
    
    		//.. 대충 boardService에서 메소드를 호출하는 등의 행동
    	}
    }

     

    위의 상황에서 /board 의 경로로 POST 요청으로 아래의 Json을 요청본문으로 담은체 Http 요청이 발생했다고 가정하자

    {
        "boardType": "FREE",
        "title": "",
        "imageToken": "",
        "imageSrc": [],
        "content": "",
        "password": "",
        "thumbnail": ""
    }

     

    그러면 젤먼저 writeArticle 메소드가 반응할 것이고

    이에따라 요청본문의 Json은 BoardRequest.BoardBase 클래스의 정보로 우선 Json => DTO 매핑을 시도할 것이다.

    public class BoardRequest {
    
    	@Getter
    	@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "boardType", visible = true)
    	@JsonSubTypes({
    		@JsonSubTypes.Type(value = FreeBoard.class, name = "FREE"),
    	})
    	public static class BoardBase {
    
    		private String title;
    		private String boardType;
    		private String thumbnail;
    		private String password;
    	}
        
    	@Getter
    	public static class FreeBoard extends BoardBase {
    
    		private String content;
    	}
    }

     

    그런데 BoardBase 클래스를 보니 @JsonTypeInfo 어노테이션이 있고,

    property와 use를 보고 boardType 프로퍼티에 값을 하나의 타입으로 인식하고

     

    @JsonSubTypes에 정의된 종류별 클래스 정보에 의하여 Json은 결국 FreeBoard 클래스로 매핑되게 된다.

    'Web-Spring' 카테고리의 다른 글

    쿠키와 SameSite, Secure, HttpOnly  (0) 2025.04.17
    JUnit  (2) 2023.12.12
    mybatis & hikari 설정  (9) 2023.12.05
Designed by Tistory.