아마 Moneycomb 프로젝트를 할 때였던 것 같다. 나는 그때도 JSON으로 메시지를 변환하기 위해, 책과 튜토리얼에서 빈번하게 등장하고, 다들 문제없이 쓰는 것으로 보이던 MappingJacksonHttpMessageConverter를 '나도 당연히 되겠지?' 하며 튜토리얼들을 따라 서블릿 컨텍스트에 빈을 설정했다.


지옥의 시작

왜인가? 어째서인가? 남들 다 된다고 댓글에도 "Thank you!"가 넘치고, 누구하나 되지 않는다는 이 없는데, 나는 왜 안되는 것인가.

지금도 그렇지만 그 때에도 Google의 JSON 라이브러리인 Gson을 즐겨썼다. API가 복잡하지 않고 설정도 간단했기 때문이다.

하지만 스프링에서 기본적으로 제공하는 JSON 메시지 컨버터는 Jackson에 의존성이 있었는데, 일단 Jackson이라는 라이브러리 자체가 낯설었고, 스프링 MVC의 동작을 깊이 파악하고 있지 않았던 탓에 하라는 대로 하는 것 외에는 손대볼 수 있는게 없었다.

열심히 설정하고 코드를 따라서 친 후 실행, 두근거리는 마음으로 Postman으로 메시지를 날려보았다.

@RequestBody 애너테이션이 붙어있는 파라미터에서도 변환이 되질 않고, @ResponseBody 애너테이션이 붙어있는 파라미터에서도 변환이 되질 않는다.

이때는 log4j 설정도 잘 몰라서, 콘솔에서는 어떠한 로그도 뜨지 않길래, 스프링이 아무말 없이 응답으로만 "안돼"라고 하는 줄로만 알았다.


Content-Type, Accept

스프링 컨트롤러에서 @RequestMapping 애노테이션에는 Content-Type과 Accept를 설정할 수 있는 consumes와 produces 속성이 있다. JSON의 미디어 타입이 application/json 인 것은 웹 하는 사람이라면 누구라도 알고 있을 것이다.

튜토리얼에서는 명시적으로 지정해주지 않았지만, 안되길래 consumes와 produces에 "application/json"이라고 적어주었다. 안된다. 이게 문제가 아닌가보다.


오늘도 삽질

그 때와 똑같은 행태를 반복해서였는지는 모르겠지만 오늘도 똑같은 현상이 나타났다. JSON 직렬화도, 역직렬화도 안된다.

그 때는 해결하지 못했던 문제였지만 오늘은 나름 해결을 하긴 했다.

여러가지 오해와 착각과 습관과 편견과 게으름의 결과였던 듯 하다.


1. 반환 타입

다른 테스트 클래스를 만들거나 모델 클래스를 반환하려니 값을 넣기도 귀찮아서

@RequestMapping(....)

@ResponseBody

public Object test(@RequestBody ....) { ... }

위 처럼 Object를 반환했다. JSON 자체에서도 { } 라고 쓰기도 하거니와, Gson에서는 Object 객체도 잘 변환한다. 빈 객체를 표현할 수도 있으니 당연하다고 생각했는데, Jackson에서는 Object 객체를 JSON으로 직렬화할 수 없다.

"Can not create bean serializer for Object.class" 란다. 어떤 정책으로 인해서 이렇게 정해졌는지는 알 수 없지만, 이것 때문에 한방.


2. 테스트 Ajax 요청

테스트로 JSON 객체를 보낼 때도, 필드 몇 개를 대충 보내거나 했는데, 이 전 버전의 Jackson(codehaus)은 JSON에는 있지만 변환될 자바 타입에서 없는 속성, 빈 문자열은 변환할 수 없고 예외를 발생시킨다.

애초에 테스트 JSON 문자열을 잘 썼으면 문제가 없었을지도 모르지만... 이 역시 Gson을 쓰면서 Gson이 동작하는 대로 생각해서 였던 것 같다. Gson에서는 없는 프로퍼티는 무시하고, 빈 JSON 문자열의 경우 null을 반환한다.


어쨌든 이번 일로 인해 좀 더 MessageConverter의 동작을 명확히 알게 되었고, 더불어 아래와 같은 것 잡지식(?)이 늘었다.

- com.fasterxml.jackson은 org.codehaus.jackson를 승계한 프로젝트

- com.fasterxml.jackson은 MappingJackson2HttpMessageConverter와 바인딩

- org.codehaus.jackson은 MappingJacksonHttpMessageConverter와 바인딩

- Spring 4부터 GsonHttpMessageConveter가 내장

- Spring 4부터 MappingJacksonHttpMessage는 없어짐





Gson 기반의 메시지 컨버터 설정 (Maven, Spring 4.1.6.RELEASE)


Maven Dependency 추가

<dependency>

<groupId>com.google.code.gson</groupId>

<artifactId>gson</artifactId>

<version>2.3.1</version>

</dependency>


servlet-context.xml 설정

<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">

<beans:property name="messageConverters">

<beans:list>

<beans:ref bean="jsonMessageConverter"/>

</beans:list>

</beans:property>

</beans:bean>


<beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.GsonHttpMessageConverter">

   </beans:bean>



※ 참고 자료

- http://www.leveluplunch.com/java/tutorials/023-configure-integrate-gson-spring-boot/

- http://www.studytrails.com/java/json/jackson-create-json.jsp

- http://docs.spring.io/spring/docs/current/javadoc-api//org/springframework/http/converter/json/GsonHttpMessageConverter.html

- http://erictus.tistory.com/entry/Spring-JSON-View-구현하기2-ResponseBody

리눅스에서 curl로 JSON을 받았을 때 Pretty Print가 되어있지 않으면 참 읽기 난감하다.

내용을 복사해서 JSON Viewer로 옮겨서 봐도 되지만 매번 그렇게 하기에는 번거로운 일이기도 해서 "cli json pretty print"을 키워드로 구글링하니 편한 방법을 찾을 수 있었다.


파이썬 2.6 이상이 설치되어 있다면 아래와 같이 커맨드를 실행시키면 된다.

$ echo '{"foo": "lorem", "bar": "ipsum"}' | python -m json.tool

{

    "bar": "ipsum", 

    "foo": "lorem"

}


그럼 위의 출력 결과처럼 보기 좋게 나온다.



참고 자료

http://stackoverflow.com/questions/352098/how-can-i-pretty-print-json


Netty를 이용해서 웹 소켓 서버를 만들고 클라이언트에서 웹 소켓을 열고 통신을 하다보니

웹 소켓에서는 아직 텍스트 통신만 지원한다.


따라서 양쪽에서 서로 쓰기 좋은 Json 포맷으로 통신하다보니 서버쪽은 자바로 구성되어 Gson 라이브러리를 쓰고,

클라이언트에서 자바스크립트로 Json <-> Object 가 필요해서 찾아보니 내장 객체로 JSON이 있다는 것을 알게 되었다.

(관련 Stack Overflow 글)


JSON Parsing 내장 객체를 지원하는 브라우저는 다음과 같다


(http://caniuse.com/json)


JSON 객체의 함수로는 Object를 Json 문자열로 변환하는 stringify 와 반대로 Json 문자열을 객체로 변환하는 parse 함수가 있다.


JSON에 관한 설명은 MSDN에 잘 나와있어 사용방법에 관한 내용은 생략하고 매개변수에 대한 내용만 스크랩했다.

http://msdn.microsoft.com/ko-kr/library/ie/cc836466(v=vs.94).aspx


1. JSON.stringify(value [, replacer] [,space])

value

필수 요소. JavaScript 값은 일반적으로 변환할 개체 또는 배열입니다.

replacer

선택 사항입니다. 결과를 변환하는 함수 또는 배열입니다.

replacer가 함수이면 JSON.stringify는 키와 각 멤버의 값을 전달하여 함수를 호출합니다. 반환 값은 원본 값 대신 사용됩니다. 함수가 undefined를 반환하면 멤버가 제외됩니다. 루트 개체의 키는 빈 문자열인 ""입니다.

replacer가 배열이면 배열에 키 값이 있는 멤버만 변환됩니다. 멤버가 변환되는 순서는 배열의 키 순서와 같습니다. replacer 배열은 value 인수도 배열인 경우 무시됩니다.

space

선택 사항입니다. 읽기 쉽도록 들여쓰기, 공백, 줄 바꿈 문자를 반환 값 JSON 텍스트에 추가합니다.

space가 생략되면 반환 값 텍스트가 추가 공백 없이 생성됩니다.

space가 숫자이면 반환 값 텍스트가 각 수준의 지정된 공백 수로 들여쓰기됩니다. space가 10보다 크면 텍스트가 10칸 들여쓰기됩니다.

space가 '\t'와 같이 빈 문자열이 아니면 반환 값 텍스트가 각 수준의 문자열의 문자로 들여쓰기됩니다.

space가 10자보다 긴 문자열이면 처음 10자가 사용됩니다.

변환할 객체에 toJSON(key) 함수가 있는 경우 해당 키를 가진 값을 변환하여 반환한다.



2. JSON.parse(value [,reviver])

value

필수 요소. JavaScript 값은 일반적으로 변환할 개체 또는 배열입니다.

replacer

선택 사항입니다. 결과를 변환하는 함수 또는 배열입니다.

replacer가 함수이면 JSON.stringify는 키와 각 멤버의 값을 전달하여 함수를 호출합니다. 반환 값은 원본 값 대신 사용됩니다. 함수가 undefined를 반환하면 멤버가 제외됩니다. 루트 개체의 키는 빈 문자열인 ""입니다.

replacer가 배열이면 배열에 키 값이 있는 멤버만 변환됩니다. 멤버가 변환되는 순서는 배열의 키 순서와 같습니다. replacer 배열은 value 인수도 배열인 경우 무시됩니다.

space

선택 사항입니다. 읽기 쉽도록 들여쓰기, 공백, 줄 바꿈 문자를 반환 값 JSON 텍스트에 추가합니다.

space가 생략되면 반환 값 텍스트가 추가 공백 없이 생성됩니다.

space가 숫자이면 반환 값 텍스트가 각 수준의 지정된 공백 수로 들여쓰기됩니다. space가 10보다 크면 텍스트가 10칸 들여쓰기됩니다.

space가 '\t'와 같이 빈 문자열이 아니면 반환 값 텍스트가 각 수준의 문자열의 문자로 들여쓰기됩니다.

space가 10자보다 긴 문자열이면 처음 10자가 사용됩니다.



+ Recent posts