Spring JSON

김정훈·2024년 7월 17일

Spring

목록 보기
15/24

JSON 응답과 요청 처리

1. JSON 개요

JSON(JavaScript Object Notation) : 자바스크립트 객체 표기업
{"이름":"값", "이름":"값",...}

참고) Rest

Rest (RepresEntational State Transfer) : 표현적 상태 전이
GET /api/member/list - 조회 상태 URL
POST /api/member

2. Jackson 의존 설정

jackson-databind
jackson-datatype-jsr310 - Data & Time API - java.time 패키지

implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2' //Jackson Databind » 2.17.2
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.2' //Jackson Datatype: JSR310 » 2.17.2

ObjectMapper 클래스

  • writeObjectAsString(자바객체) : 자바객체 👉 JSON문자열
  • readValue(...) : JSON문자열 👉 자바객체

3. @RestController로 JSON 형식 응답

반환값

  • 자바 객체(getter 존재) : JSON 문자열 👉 자바 객체 출력
    • 응답 헤더 : Content-Type : application/jsoin
  • ❌(없는 경우) : 응답 body가 비어있음
  • 문자열 : 문자열 그대로 출력
    • 응답 헤더 : Content-Type : text/plain
@Slf4j
@RestController
@RequestMapping("/api/member")
@RequiredArgsConstructor
public class ApiMemberController {

    private final MemberMapper mapper;

    @GetMapping("/info/{email}")
    public Member info(@PathVariable("email") String email){
        //content-Type : application/json
        Member member = mapper.get(email);

        return member;
    }
    
    @GetMapping("/list") //리스트형
    public List<Member> list(){
        List<Member> members = IntStream.rangeClosed(1,10)
                .mapToObj(i -> Member.builder()
                        .email("user" + i + "test.org")
                        .password("12345678")
                        .userName("사용자" + i)
                        .regDt(LocalDateTime.now())
                        .build())
                .toList();
        return members;
    }

    @GetMapping(path="/test", produces = "text/html;charset=UTF-8")
    public String test(){
        //content-Type : text/palin
        return "ㅎㅇ";
    }

    @GetMapping("/test2")
    public void test2(){
        log.info("test2");
    }
}

반환값 : 자바객체 👉 JSON문자열

4. @ResponseBody 애노테이션

@Controller로 설정된 일반 컨트롤러 메서드를 Rest로 응답 하게 만들어주는 애노테이션
자바 객체, 문자열, 반환값 없음

MemberController

@ResponseBody
@GetMapping("/list")
public List<Member> list(){
    List<Member> members = IntStream.rangeClosed(1,10)
            .mapToObj(i -> Member.builder()
                    .email("user" + i + "test.org")
                    .password("12345678")
                    .userName("사용자" + i)
                    .regDt(LocalDateTime.now())
                    .build())
            .toList();
    return members;
}

5. @Jsonlgnore를 이용한 제외 처리

JSON 변환시 제외
👉 민감한 정보 예) 비밀번호 등..

@Data
@Builder
@NoArgsConstructor @AllArgsConstructor
//@Table
public class Member {
    @Id //기본키 설정
    @Column("ID")
    private Long seq;
    private String email;
    
    @JsonIgnore
    private String password;
    private String userName;
    private LocalDateTime regDt;
}

6. 날짜 형식 변환 처리: @JsonFormat 사용

@Data
@Builder
@NoArgsConstructor @AllArgsConstructor
//@Table
public class Member {
    @Id //기본키 설정
    @Column("ID")
    private Long seq;
    private String email;

    @JsonIgnore
    private String password;
    private String userName;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    private LocalDateTime regDt;
}

1) 날짜 형식 변환 처리 : extendMessageConverters 사용

MvcConfig

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    ObjectMapper mapper = Jackson2ObjectMapperBuilder
            .json()
            .serializerByType(LocalDateTime.class,new LocalDateSerializer(formatter))
            .build();
	converters.add(new MappingJackson2HttpMessageConverter(mapper)); //가장 먼저 적용될수있도록 
}

2) Json이 아닌 XML로 반환

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    ObjectMapper mapper = Jackson2ObjectMapperBuilder
            .xml()
            .serializerByType(LocalDateTime.class,new LocalDateSerializer(formatter))
            .build();

    converters.add(new MappingJackson2HttpMessageConverter(mapper)); //가장 먼저 적용될수있도록
}
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2' //Jackson Dataformat XML » 2.17.2

7. @RequestBody JSON 요청 처리

커맨드 객체 변환 기준 - Content-Type:application/x-www.form-urlencoded;
커맨드객체 앞에 @RequestBody를 추가하면 Content-Type:application/json으로 판단하고 데이터 변환

POST
PUT
PATCH

POSTMAN : REST 테스트
ARC(Advanced Rest Client) : REST 테스트

MockMvc :

8. ResponseEntity

객체 리턴하고 응답 코드 지정
응답 헤더, 바디쪽을 상세하게 설정하는 경우

1) ResponseEntity를 이용한 응답 데이터 처리

2) ResponseEntity.status(상태코드).body(객체)

응답 상태 코드 + 출력 데이터

ApiMemberController

@GetMapping("/list")
public ResponseEntity<List<Member>> list(){
    List<Member> members = IntStream.rangeClosed(1,10)
            .mapToObj(i -> Member.builder()
                    .email("user" + i + "test.org")
                    .password("12345678")
                    .userName("사용자" + i)
                    .regDt(LocalDateTime.now())
                    .build())
            .toList();
    return ResponseEntity.status(HttpStatus.OK).body(members);
}

Test

@Test
void test2() throws Exception{
    mockMvc.perform(get("/api/member/list"))
            .andDo(print());
}

3) ResponseEntity.status(상태코드).build();

응답 상태 코드 / 출력 데이터 ❌

ApiMemberController

@PostMapping()
public ResponseEntity join(@RequestBody RequestJoin form){
    joinService.process(form);
    //응답코드 201, 출력바디 x
    return ResponseEntity.status(HttpStatus.CREATED).build();
}

Test

@Test
void test1() throws Exception{

    ObjectMapper om = new ObjectMapper();
    om.registerModule(new JavaTimeModule());
    RequestJoin form = new RequestJoin();
    form.setEmail("user99@test.org");
    form.setPassword("12345678");
    form.setConfirmPassword("12345678");
    form.setUserName("사용자99");
    form.setAgree(true);

    String json = om.writeValueAsString(form);
    mockMvc.perform(
            post("/api/member")
                    .contentType(MediaType.APPLICATION_JSON) //요청헤드
                    .content(json) //요청바디
    ).andDo(print()).andExpect(status().isCreated());

4) ReponseEntity.ok(member)

ApiMemberController

@GetMapping("/info/{email}")
public ResponseEntity<Member> info(@PathVariable("email") String email){
    //content-Type : application/json
    Member member = mapper.get(email);
    return ResponseEntity.ok(member);
}

5) noContent() : 204

6) badRequest() : 400

7) notFound() : 404

JSON 통일성

JSONData

@Data
@NoArgsConstructor
@RequiredArgsConstructor
public class JSONData {
    private HttpStatus status = HttpStatus.OK; //상태코드, 기본값 200, 성공이 많고 간혹 실패하기 때문에
    private boolean success = true; //성공여부, 기본값 true, 성공이 많고 간혹실패하기 떄문에
    private String message; //실패시 메세지
    @NonNull
    private Object data; //성공시 데이터
}

9. @ExceptionHandler 적용 메서드에서 ResponseEntity로 응답하기

10. @Valid 에러 결과를 JSON으로 응답하기

UriComponentsBuilder

@SpringJUnitWebConfig
@ContextConfiguration(classes = MvcConfig.class)
public class Ex01 {

    @Test
    void test1(){
        UriComponents url = UriComponentsBuilder
                .fromUriString("https://www.naver.com")
                .path("/news")
                .queryParam("t1","v1")
                .queryParam("t2","v2")
                .queryParam("t3","한글")
                .fragment("hash")
                .encode()
                .build();
        System.out.println(url.toUriString());
    }
}
//>>https://www.naver.com/news?t1=v1&t2=v2&t3=%ED%95%9C%EA%B8%80#hash

build(Object... uriVariables) : 반환값 URI, 인스턴스를 빌드 URI하고 URI 템플릿 변수를 배열 값으로 바꿉니다.

@SpringJUnitWebConfig
@ContextConfiguration(classes = MvcConfig.class)
public class Ex01 {

    @Test
    void test1(){
        URI url = UriComponentsBuilder
                .fromUriString("https://www.naver.com")
                .path("/news/{0}") //{0} = uri변수
                .queryParam("t1","v1")
                .queryParam("t2","v2")
                .queryParam("t3","한글")
                .queryParam("t4","aa{1}")
                .fragment("hash")
                .encode()
                .build("AAAA","BBBB");
        System.out.println(url);
    }
}
//>>https://www.naver.com/news/AAAA?t1=v1&t2=v2&t3=%ED%95%9C%EA%B8%80&t4=aaBBBB#hash

RestTemplate

  1. <T> ResponseEntity<T> getForEntity(...)
  2. <T> T getForObject
  3. <T> ResponseEntity<T> postForEntity
  4. <T> T postForObject
  5. <T> ResponseEntity<T> exchange(...)

HttpEntity - 헤더와 바디 함께 전송

@Data
public class PostData {
    private long userId;
    private long id;
    private String title;
    private String body;
}
@Test
void test3() throws Exception{
    RestTemplate restTemplate = new RestTemplate();
    String body = restTemplate.getForObject("https://jsonplaceholder.typicode.com/posts/1", String.class);

    // 단일 객체 변환
    PostData data = om.readValue(body, PostData.class);
    System.out.println(data);

    //복합 데이터 객체 변환 - List, Set, Mao..
    String itemsBody = restTemplate.getForObject("https://jsonplaceholder.typicode.com/posts/", String.class);
    List<PostData> items = om.readValue(itemsBody, new TypeReference<List<PostData>>(){});
    items.forEach(System.out::println);
}

Errors

getFieldErrors() : 필드별 전체 에러 정보
getGlobalErrors() : 커맨드 객체 자체 에러 정보 (reject(...) ...)
getAllErrors() : 전체 에러 정보

Swagger API

profile
안녕하세요!

0개의 댓글