본문 바로가기
Web Development/Java

[Java] BufferedReader 다시 읽는 방법 (feat.mark, reset 사용법)

by 감자맹고우 2022. 3. 18.
728x90
반응형

프로젝트 진행 중에 외부 API 데이터를 끌어와 json 형태로 보여주는 기능을 구현해야 했다.

기능 자체는 이미 API에서 제공하고 있기에 금방 구현했지만, 간헐적으로 에러가 콘솔창에 찍혔다.

 

< 구현 코드 >

/** Java */
JSONObject jsonObject = new JSONObject();
ObjectMapper mapper = new ObjectMapper();

//BufferedReader 할당된 내용 생략 -> BufferedReader bufferedReader;
jsonObject = mapper.readValue(bufferedReader, JSONObject.class);

 

< 발생 에러 >

unexpected character ('<' (code 60)): expected a valid value

 

이 에러가 발생한 이유는 ObjectMapper의 readValue를 통해, bufferedReader의 내용을 JSONObject 타입으로 읽어오려고 하는데 '<'라는 JSON 형태가 아닌 데이터가 읽어졌기 때문이다.

그리고 보통 API에서 넘어온 데이터 중 '<' 는 HTML 데이터인 경우가 많다.

아니나 다를까, readLine을 통해서 출력해보니 HTML 데이터가 넘어오고 있었다.

 

/** Java */
while((inputLine = bufferedReader.readLine()) != null) {
	System.out.println(inputLine);
}

 

문제는 이미 외부 API에 datatype을 json타입으로 명시하여 데이터를 요청하고 있었다는 것이다.

json으로 요청을 했는데도 HTML로 오다니...

결국 API에서 간헐적으로 흘러들어오는 html데이터는 막을 수 없는 것이다. 별 수 있나? 예외처리해줄 수 밖에...

 

반응형

 

 

[ 해결 방법 ]

 

해결을 위해서는 결국 BufferedReader로 넘어온 내용이 Json인지 Html인지 확인을 해주어야 했는데, BufferedReader는 휘발성 메모리이기 때문에 검사를 위해서 한번 읽고나면, 이후의 처리가 불가능하다.

 

예를 들어, readLine으로 읽어 검사한 후 json이라서 objectmapper의 readValue로 다시 처리를 하려고 하면 readLine으로 읽으면서 메모리가 날아갔기 때문에 readValue를 하려는 데이터는 이미 null인 상태인 것이다.

 

그래서 처음 생각한 것은, 따로 복사를 해두는 방법이었다.

그러나 BufferedReader br2 = bufferedReader; 와 같이 새로운 변수에 할당해봤자 얕은 복사로 같은 메모리를 가리키기 때문에 다시 쓸 수 없다. 그러면 깊은 복사를 어떻게 할 수 있을까? 찾아보니 방법을 쉽게 찾을 수 없었다.

 

어떻게 하면 될까?

 

 

그러나

 

우리는 언제나 답을 찾을 것이다. 언제나 그래왔듯이... (깨알 같이 써보는 인터스텔라 대사)

 

구글링을 해보니, BufferedReader는 mark와 reset이라는 기능을 제공하고 있었다.

이것이 무엇이냐면, RDBMS의 Savepoint와 Rollback과 유사한 기능이라고 볼 수 있다.

BufferedReader에서 mark를 해두면 reset으로 해당 마크 지점으로 되돌아갈 수 있다.

 

mark와 reset의 사양에 대해 간단히 알아보자면,
- mark는 parameter로 Integer 타입의 readAheadlimit 데이터를 받는다.
- reset은 parameter를 받지 않는다.

 

즉, mark로 받아올 데이터의 크기를 지정하기만 하면 reset으로 처음부터 다시 읽어올 수 있다.

구글링에서는 Integer.MAX_VALUE 로 크기를 지정했지만, 너무 커서 에러가 발생하는 경우가 있다.

그래서 구현한 코드는 다음과 같다.

 

public static JSONObject parseJSON(BufferedReader bufferedReader) {
    JSONObject jObj = new JSONObject();
    ObjectMapper mapper = new ObjectMapper();

    int BUFF_SIZE = 262144;

    try {
        String inputLine;
        bufferedReader.mark(BUFF_SIZE);
        //readLine으로 '<' 확인 후, 있을 시 초기화된 JSONObject를 그대로 반환 
        while((inputLine = bufferedReader.readLine()) != null) {
            if(inputLine.indexOf("<") != -1) return jObj;
        }
        //검사를 통과하면 JSONObject로 간주하고 reset => API 사양/필요에 따라 추가 처리
        bufferedReader.reset();
        jObj = mapper.readValue(bufferedReader, JSONObject.class);
    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        try {
            bufferedReader.close();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
	
    return jObj;
}

 

위와 같이 구현하면, BufferedReader 데이터를 다시 읽어서 HTML 데이터를 예외처리하고 JSONObject만을 읽어낼 수 있다!

이 때, 상황에 맞게 (1)BUFF_SIZE 크기(2)indexOf로 검사할 문자(<) 만 신경써주면 된다.

 

🤞 도움이 되셨기를 바랍니다. 한 번의 클릭과 댓글은 어딘가의 누군가에게 진실로 큰 힘이 됩니다. 🐱‍🏍

 

728x90
반응형

댓글