-
[Spring] Could not write JSON: Infinite recursion (StackOverflow) 에러 해결이슈 해결 2018. 6. 27. 13:12
Could not write JSON: Infinite recursion
해당 이슈는 JSON 파싱 중 Stack Overflow가 발생하는 이슈이다.
찾아보면 무한 재귀발생요소를 찾아볼 수 있지만 이번에 내가 경험한건 찾아볼 수 없는 요소였기에 개발중 직접 발생한 JSON 파싱 오류를 해결한 사안을 작성해본다.
내가 경험한 오류는 말 그대로 파싱자체에서 오류가 발생한거였다.
특정 타 사이트의 정보를 크롤링하며 데이터를 가공하고 가공한 데이터를 내 사이트에 표현하려는 작업 중이었다.
아래에 그림을 보여주며 설명할 것이지만 그 전에 먼저 설명하자면
1. view페이지에서 Ajax 비동기 통신으로 Controller에 필요한 크롤링 데이터를 요청한다.
2. Controller에서 Ajax의 요청을 받고 크롤링을 담당하는 클래스의 메소드를 호출한다.
3. 크롤링 클래스에서 Controller에 의해 호출된 메소드가 크롤링을 작업하고 결과물을 전달해준다.
4. Controller에서 전달받은 결과물을 view페이지에 응답해준다.
이러한 과정이고 응답받은 Ajax에서 status 0 코드를 떨구고 사용자에게 파싱이 에러난 것을 알려준다.
그림을 보자.
셀렉트박스와 버튼이 구성되어있다.
옵션을 선택하고 버튼을 클릭하면 아래의 Ajax 함수가 호출된다.
json 데이터 타입으로 서버에 요청을 보낸다.
요청을 전달받은 Controller에서 크롤링을 작업하는 클래스의 메소드를 호출하고 그 정보를 리턴한다.
응답을 받은 Ajax는 crawler.getPremireLeague()라는 메소드의 데이터를 JSON파싱을 시도한다.
그러나 crawler.getPremireLeague() 메소드가 리턴하는 결과를 JSON파싱하는데 에러를 발생시킨다.
아래는 Console에 찍힌 에러로그이다.
1234567891011121314151617181920212223242526272829303132333435366월 27, 2018 10:58:02 오전 org.apache.catalina.core.StandardWrapperValve invoke심각: Servlet.service() for servlet [action] in context with path [] threw exception [Request processing failed;nested exception is org.springframework.http.converter.HttpMessageNotWritableException:Could not write JSON: Infinite recursion (StackOverflowError)(through reference chain: org.jsoup.select.Elements[0]->org.jsoup.nodes.Element["allElements"]->org.jsoup.select.Elements[0]->org.jsoup.nodes.Element["allElements"]->org.jsoup.select.Elements[0]->org.jsoup.nodes.Element["allElements"]->org.jsoup.select.Elements[0]->org.jsoup.nodes.Element["allElements"]->org.jsoup.select.Elements[0]->org.jsoup.nodes.Element["allElements"]->...org.jsoup.nodes.Element["allElements"])] with root causejava.lang.StackOverflowErrorat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(Unknown Source)at java.security.SecureClassLoader.defineClass(Unknown Source)at org.apache.catalina.loader.WebappClassLoaderBase.findClassInternal(WebappClassLoaderBase.java:3175)at org.apache.catalina.loader.WebappClassLoaderBase.findClass(WebappClassLoaderBase.java:1372)at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1860)at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1734)at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:166)at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)at org.codehaus.jackson.map.ser.std.StdContainerSerializers$IndexedListSerializer.serializeContents(StdContainerSerializers.java:122)at org.codehaus.jackson.map.ser.std.StdContainerSerializers$IndexedListSerializer.serializeContents(StdContainerSerializers.java:71)at org.codehaus.jackson.map.ser.std.AsArraySerializerBase.serialize(AsArraySerializerBase.java:86)at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)at org.codehaus.jackson.map.ser.std.StdContainerSerializers$IndexedListSerializer.serializeContents(StdContainerSerializers.java:122)at org.codehaus.jackson.map.ser.std.StdContainerSerializers$IndexedListSerializer.serializeContents(StdContainerSerializers.java:71)at org.codehaus.jackson.map.ser.std.AsArraySerializerBase.serialize(AsArraySerializerBase.java:86)at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)...cs ...이라고 해놓은건 루프로 인해서 같은 내용이 계속해서 찍혀졌기 때문에 너무 길어서 생략한거다.
org.jsoup.select.Elements[0] -> org.jsoup.nodes.Element["allElements"] 라는 내용이 계속해서 루프가 발생하는 것을 알 수 있다.
이런 작업들로 인해 strack trace도 보면 같은 내용이 반복해서 찍혀졌다.
왜 org.jsoup.select.Elements[0] -> org.jsoup.nodes.Element["allElements"] 이 행동이 무한 루프가 발생한 것인지는 모르겠으나
여기서 한가지 확실한건 서버에서는 crawler객체에서의 작업은 정상적으로 이루어졌고, Controller에서 view페이지로 return하는 것 까지 정상적이었다는 걸 알 수 있다.
왜냐면 Console에서 로그를 찍어봤을 때 아무런 문제가 없었으니까...
그럼 결국 View페이지, Ajax처리에서 데이터를 파싱하는 과정에서 문제가 발생했다는 것이다.
그럼 Ajax에서 어떤 데이터를 파싱하려고 했는지를 알아보자.
crawler클래스의 getPremireLeague() 메소드를 찾아가보자.
sb라는 변수를 toString()메소드로 캐스팅하고 Jsoup객체의 parse()메소드를 이용해서 파싱한 데이터를 Document 객체의 doc 변수에 담았다.
이후 Document객체의 select()메소드를 통해 tableContainer라는 클래스 이름을 가진 태그를 지정해서 Elements 객체에 담았다.
그리고 Elements 객체를 Controller로 return한다.
그럼 Ajax는 Controller로부터 Elements 객체를 리턴받은 것이다.
그런데 Elements 객체는 Jsoup 라이브러리에서 제공하고 있는 커스텀 객체이므로 일반적인 객체가 아니다.
JSON 파싱을 하기 위해서는 일반적인 객체여야만 파싱이 가능할텐데 어디서 온건지 모를 Elements라는 읽어낼 수 없는 비표준 객체를 파싱하려고 하니 에러가 발생한것.
이를 해결하기 위해서 한가지 방법을 생각해봤다.
Elements 객체는 Jsoup 라이브러리의 커스텀 객체이긴 하나 HTML요소이니 dataType을 html로 지정해보면 파싱이 가능하지 않을까?
응 아니야. 안돼.
역시 읽을 수 없는 비표준 객체타입은 파싱할 수가 없는 것 같다.
그럼 Elements 객체 중 내용물을 표준 객체로 리턴하는 메소드가 없을까?
한번 찾아봤다.
Jsoup API Reference - https://jsoup.org/apidocs/org/jsoup/select/Elements.html#html--
딩동!
html()이라는 메소드는 엘리멘트 태그를 "<div> hello </div>" 형식의 String으로 반환한다.
그럼 리턴 데이터를 Elements 객체를 반환하는게 아니라 Elements.html() 메소드를 통해 String으로 반환해보자.
그리고 다시 dataType을 json으로 바꾸고 시도해보자.
서버를 실행하고 검색버튼을 클릭해보면
딩동! 에러없이 정상적으로 처리되었다.
마무리해보면
1. Ajax 통신을 통해 서버로부터 전달받은 Jsoup의 Elements라는 비표준 객체를 JSON 파싱하려고 시도했다.
2. 비표준 객체를 파싱하려고 시도했기 때문에 파싱에 대한 에러가 발생했고
3. 이를 해결하고자 비표준 객체가 아닌 표준 객체를 리턴하도록 방법을 찾아보니 Elements 객체에서 html()이라는 String으로 반환하는 메소드를 찾았다.
4. html()메소드로 데이터를 String으로 반환하니 Ajax에서 JSON파싱이 정상적으로 이루어졌다.
끗-
'이슈 해결' 카테고리의 다른 글
[Intelli J] no java SDK of appropriate version found. in addition to the intellij platform plugin SDK, you need to define a JDK with the same java version. (0) 2018.11.21 [Python] ImportError: cannot import name 'ascii_letters' from 'string' (1) 2018.11.16 [Eclipse] 개발PC에서 파일업로드 후 바로 적용하기 (0) 2018.06.11 [Spring] Ajax 200 에러 원인과 해결방법 (0) 2018.05.30 [Spring] 404 에러 -1- (0) 2018.05.29