{
"name": "유관순",
"birthday": "1902-12-16",
"age": 17,
"related": ["남동순", " 류예도"]
}
값 -> 문자열, 숫자, boolean, null, 배열, 다른 객체
jackson-databind
jackson-datatype-jsr310
-> Date & Time API - java.time 패키지


ObjectMapper
자바 객체를 JSON 문자열로 바꿔줌
-> writeObjectAsString메서드 (자바 객체)
JSON 문자열 -> 자바객체
-> readValue() 메서드
Jackson은 자바 객체와 JSON간의 변환을 처리한다.

Jackson은 프로퍼티(get 메서드 또는 설정에 따라 필드)의 이름과 값을 JSON 객체의 (이름, 값) 쌍으로 사용한다.
REST(RepresEntational State Transfer): 표현적 상태 전이
GET /api/member/list - 조회 상태 URL
POST /api/member
rest컨트롤러의 경우 반환값이 void도 가능함(일반 컨트롤러는 불가능), 내부적으로(콘솔) 호출만가능

🔼 rest형태 만들곳!
예제) 반환값이 getter가 있는 자바객체인 경우
/*json 형태로 응답하는 RestController*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/member")
public class ApiMemberController {
private final MemberMapper mapper;
@GetMapping("/info/{email}")
public Member info(@PathVariable("email") String email){
Member member = mapper.get(email); //개별조회
return member; //반환값은 자바객체
}//이메일로 회원조회 후 출력
}
응답을 json형태로 하는 rest컨트롤러임

예쁘게 안나오면 크롬에 추가하기

응답헤더에 content-type은 application/json
예제) 반환값이 문자열인 경우
응답헤더 따로 설정없이 돌려보니 한글이 깨진다? 확인해보니 charset이 잘못 설정되어있었음

produces = "text/html;charset=UTF-8": 이 메서드가 반환하는 응답의 콘텐츠 타입을 text/html로 설정하고, 문자 인코딩을 UTF-8로 지정


임의로 회원 정보 넣고 리스트 형태 가져오기


문자열 데이터 json형태로 출력
RestController을 정의하지 않고 사용하는 방법
@Controller로 설정된 일반 컨트롤러 메서드를 Rest로 응답하게 만들어주는 애노테이션
@ResponseBody 애노테이션
@RestController
🔹MemberController


json 출력 결과를 보면 비밀번호 내용까지 출력된다.
보통 암호와 같이 민감한 데이터는 응답 결과에 포함시키면 안되므로 응답 결과에서 제외시켜야한다.

중요 개인정보 json 변환에서 제외 시킴


날짜 형식을 변환할 모든 대상에 이 애노테이션을 매번 붙이기도 번거롭다.
🔽 다른 방법 🔽
@JsonFormat 주석하기!!
MvcConfig

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 날짜와 시간을 "yyyy-MM-dd HH:mm:ss" 형식으로 포맷하는 DateTimeFormatter 생성
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// ObjectMapper 생성 및 설정
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
.json() // JSON 형식으로 ObjectMapper 빌드 시작
.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter))
// LocalDateTime 타입을 지정된 포맷으로 직렬화하는 설정 추가
.build();
// 커스텀 ObjectMapper를 사용하는 MappingJackson2HttpMessageConverter 생성 후 converters 리스트의 맨 앞에 추가
converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));
}
}
📌직렬화: 객체를 저장하거나 전송할 수 있도록 데이터 형식으로 변환하는 과정
DateTimeFormatter 설정: LocalDateTime을 yyyy-MM-dd HH:mm:ss 형식의 문자열로 변환하는 포맷터를 설정합니다.
ObjectMapper 설정: Jackson의 ObjectMapper를 사용하여, LocalDateTime을 위에서 설정한 포맷터를 이용해 직렬화하도록 설정합니다.
HttpMessageConverter 추가: 이 설정을 사용하는 변환기를 Spring의 HTTP 메시지 변환기 목록에 추가하여, 컨트롤러가 JSON 응답을 반환할 때 이 설정을 사용하도록 합니다.
🔽xml로 응답하는 형태🔽

implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.2'

응답을 xml로 바꿔주면 됨

하지만 json을 더 많이씀!
rest 방식에서
POST
PUT
PATCH 사용할 수 있는 테스트 툴
서버를 껐다 키지 않고 rest 테스트를 하려면
-> MockMVc 사용
ApiMemberController

테스트

테스트쪽에도 환경변수 설정



커맨드 객체 변환 기준 - Content-Type: application/x-www-form-urlencoded;
Content-Type:application/json으로 판단하고 데이터 변환함


회원가입 되는지 확인


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);
//출력데이터가 포함된 바디 형태
//응답코드 200
}
...
ApiMemberControllerTest
...
@Test
void test2() throws Exception{
mockMvc.perform(get("/api/member/list"))
.andDo(print());
}

바디데이터 담겨있음
@Slf4j
@RequestMapping("/api/member")
@RestController
@RequiredArgsConstructor
public class ApiMemberController {
private final MemberMapper mapper;
private final JoinService joinService;
@PostMapping //POST /api/member -> 회원가입으로 동작
public ResponseEntity join(@RequestBody RequestJoin form)
{
joinService.process(form);
// 응답코드 201, 출력 바디 없음
return ResponseEntity.status(HttpStatus.CREATED).build();
}
...
...
ApiMemberControllerTest
...
@Test
void test1() throws Exception {
// Content-Type: application/json
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
RequestJoin form = new RequestJoin();
form.setEmail("user100@test.org");
form.setPassword("12345678");
form.setConfirmPassword("12345678");
form.setUserName("사용자100");
form.setAgree(true);
String json = om.writeValueAsString(form);
mockMvc.perform(
post("/api/member")
.contentType(MediaType.APPLICATION_JSON) //요청헤더
.content(json) //요청바디
).andDo(print())
.andExpect(status().isCreated());
//201응답코드인지 테스트
}

응답코드 201, 출력바디 없음
🔸ResponseEntity사용해서 응답 상세하게 보기

ApiMemberController





주소 오류
✍️JSON 응답은 형식을 고정해서 응답하는것이 중요하다.
🔹예를들어 개발자마다 에러코드를 message or msg or 메시지 이렇게 서로 다르게 지정하면 다 다르게 메시지가 출력된다. ->
예상 가능하게 형식을 고정시켜야함 통일성!!
🔖JsonData

success: true이면 성공 false이면 실패
message: 주로 실패시 띄울 메시지
응답 성공이 많고 응답코드 200이 많이 뜰거니까 고정값으로 true, 응답코드200d으로 설정해준것
data는 바꿀수있어야 하기때문에 NonNull로 지정
🔖ApiMemberController


컨트롤러 커맨드객체에 검증 필요함

/???
임의로 예외 발생
🔖 ApiMemberController

응답 실패시 공통적으로 에러처리 할 메서드
에러도 통일성 있게 JSONData형태로! 상태코드 응답

응답 상태 출력


🔼공통적인 값 유지, 공통적인 예외처리 위해 사용된다.

...
// 모든 컨트롤러에서 발생하는 예외를 중앙에서 처리하고, 일관된 형태의 JSON 응답을 반환할 수 있다.
@RestControllerAdvice("org.choongang")
public class RestCommonControllerAdvice {
@ExceptionHandler(Exception.class)
public ResponseEntity<JSONData> errorHandler(Exception e) { //모든 예외를 잡아서 처리
Object message = e.getMessage(); //예외메시지 가져와서 저장
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; // 기본 상태 코드 500
if (e instanceof CommonException commonException) { //내가 정의한 예외 타입인지
status = commonException.getStatus(); //내가 정의한 상태코드
Map<String, List<String>> errorMessages = commonException.getErrorMessages();
if (errorMessages != null) message = errorMessages; //커맨드 객체 검증 에러일때
}
JSONData data = new JSONData(); //응답 데이터 구성
data.setSuccess(false); //요청 실패
data.setMessage(message); //예외 메시지 또는 맵 설정
data.setStatus(status); //필드에 상태코드 설정
e.printStackTrace();
return ResponseEntity.status(status).body(data); //반환된 응답은 HTTP 응답으로 변환되어 클라이언트에게 전달
}
}



요청데이터는 json 형식으로~

ApiMemberController

이쪽으로 유입 된것!
Errors
발생한 에러 정보가 담겨있는 메서드


🔼 field Error
공백일수없습니다.. 말고
내가 정의한 메시지 쓰고싶을때!
프로퍼티스에 있는 메시지 쓰기위해 -> messageSource
참고) 메시지 프로퍼티 파일
코드 = 문자열형태
👩💻필드오류
필드 오류는 특정 입력 필드에 대한 유효성 검사에서 발생한 오류임. 예를 들어, 회원가입 폼에서 이메일 필드가 빈 값이거나 형식이 잘못된 경우, 해당 필드에 대한 오류가 발생한다. 필드 오류는 특정 입력 필드에 직접 연관되어 있다.
👩 글로벌 오류
글로벌 오류는 특정 필드에 직접 연관되지 않은 폼 전체에 대한 유효성 검사 오류이다. 예를 들어, 사용자가 입력한 두 개의 비밀번호가 일치하지 않는 경우, 이 오류는 특정 필드보다는 폼 전체에 대한 오류로 간주된다. 글로벌 오류는 폼의 일반적인 규칙에 위배될 때 발생
가공 하는 이유 -> 에러 코드를 그대로 보여주면 사용자가 인식하지 못할수도있다
ex) NotBlank.password


getErrorMessages 메서드는 getCodeMessages 메서드를 사용하여 오류 코드를 메시지로 변환한다.
commonException, BadRequestException 수정
RestControllerAdvice

ApiMemberController

및 코드 수정..
Utils 추가



💡Json응답요청 관련 코드 분석 해보기!!!
UriComponentsBuilder
package org.tests;
import org.junit.jupiter.api.Test;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
public class Ex02 {
@Test
void test2(){
URI url = UriComponentsBuilder.fromUriString("http://www.naver.com")
.path("/news/{0}")
.queryParam("t1","v1")
.queryParam("t2","v2")
.queryParam("t3","한글")
.queryParam("t4","aa{1}")
.fragment("hash")
.encode()
.build("AAAAA","BBBBB");
System.out.println(url);
}
}


주석 처리 안하면 계속 에러 던져질것이다 무시무시
ResponseEntity는 Spring Framework에서 HTTP 응답을 나타내는 클래스
HTTP 응답의 상태 코드(status), 헤더(바디 정보 담겨있음), 본문(body) 등을 포함하고 있어서, 클라이언트가 HTTP 요청을 보내고 그 결과를 상세히 처리할 수 있도록 돕는다.
🔽 메서드 🔽
@Test
@DisplayName("일반 양식 형식으로 전송 - Content-Type: application/x-www-form-url-urlencoded")
void test5() {
RestTemplate restTemplate = new RestTemplate();
// 요청 데이터를 키:값 형태로 담음
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("email","user998@test.org"); //put은 하나의 이름에 여러개 담을때, 단일 데이터일경우 add
params.add("password","12341234");
params.add("confirmPassword","12341234");
params.add("userName","사용자998");
params.add("agree","true"); //약관동의
//post형태로 바디 데이터 만듦
//헤더
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //전송하는 데이터 형식 알려줌
//바디데이터의 종류를 알려주기
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
String url = "http://localhost:5000/day05/member/join";
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
System.out.println(response);
}
<302 FOUND Found,[Location:"/day05/member/login", Content-Language:"ko-KR", Content-Length:"0", Date:"Thu, 18 Jul 2024 06:29:25 GMT", Keep-Alive:"timeout=20", Connection:"keep-alive"]>
가입된 회원일경우 자세한 정보로 에러 메시지 나온다.

@SpringJUnitWebConfig
@ContextConfiguration(classes = MvcConfig.class)
public class Ex02 {
@Autowired
private ObjectMapper om;
@Test
void test1() {
...
}
@Test
void test2() {
RestTemplate restTemplate = new RestTemplate();
PostData data = restTemplate.getForObject("https://jsonplaceholder.typicode.com/posts/1", PostData.class);
System.out.println(data);
}
@Test
void test3() {
...
}
}

TypeReference

HttpEntity -> 헤더 바디 등 함께 전송시 필요

가장 유연한 형태의 HTTP 요청을 보내고, 그 결과를 ResponseEntity 형태로 반환받음
HTTP 메서드(GET, POST, PUT, DELETE 등)와 함께 요청을 보낼 수 있다.

요청과 응답의 상세 정보(헤더, 본문 등)를 제어할 수 있음
반환값 ResponseEntity 객체