크지 않은 json 데이터를 파싱할 때는 한번에 파싱해도 메모리 사용량이 많지 않지만,
파싱해야할 json 데이터가 굉장히 크다면 메모리 사용량이 많기 때문에 주의가 필요한 부분입니다.
참고삼아 제 로컬 PC에서 jackson 라이브러리로 json 파일을 객체로 파싱하는 로직을 실행해서 visualvm으로 확인해본 결과는 다음과 같았습니다.
- 약 300 KB 파일을 한번에 객체로 파싱 : 약 60 MB 힙메모리 사용
- 약 50 MB 파일을 한번에 객체로 파싱 : 약 150 MB 힙메모리 사용
- 약 150 MB 파일을 한번에 객체로 파싱 : 약 750 MB 힙메모리 사용
jackson 사용 예제
streaming api는 json 데이터를 다음과 같은 토큰 단위로 읽어서 처리합니다.
- 객체의 시작 ( { ), 끝 ( } )
- 배열의 시작 ( [ ), 끝 ( ] )
- 필드명
- 필드값
토큰을 순회하며 원하는 데이터가 나왔을 때 처리를 하는 방식으로 구현하면 됩니다. (이터레이터 처리 방법과 유사)
예를 들어서 설명하면,
<sample.json>
{
"contentsId": "123456",
"title": "꽹과리",
"metadata": [
{
"propertyName": "category",
"propertyContent": "타악기"
},
{
"propertyName": "category",
"propertyContent": "동양악기"
}
]
}
위 데이터를 객체로 파싱하는 코드는 다음과 같이 작성할 수 있습니다.
토큰 단위로만 데이터를 읽을 수 있는 것은 아니고 한번에 읽을 수도 있습니다.
metadata 파싱하는 부분을 한번에 처리하는 방식으로 다음과 같이 변경할 수 있습니다.
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.MappingJsonFactory;
public class JacksonStream {
public static void main(String[] args) throws IOException {
JsonFactory jsonFactory = new MappingJsonFactory();
JsonParser jsonParser = jsonFactory.createParser(new File("sample.json")); // json 파서 생성
AudioContent audioContent = new AudioContent(); // 맵핑할 객체
while (jsonParser.nextToken() != JsonToken.END_OBJECT) { // END_OBJECT(}) 가 나올 때까지 토큰 순회
String fieldName = jsonParser.getCurrentName(); // 필드명, 필드값 토큰인 경우 필드명, 나머지 토큰은 null 리턴
if ("contentsId".equals(fieldName)) {
jsonParser.nextToken(); // 필드값 토큰으로 이동
audioContent.setContentsId(jsonParser.getText());
} else if ("title".equals(fieldName)) {
jsonParser.nextToken();
audioContent.setTitle(jsonParser.getText());
} else if ("metadata".equals(fieldName)) {
// metadata array 요소들 파싱
while (jsonParser.nextToken() != JsonToken.END_ARRAY) { // END_ARRAY(]) 가 나올 때까지 토큰 순회
Metadata metadata = parseMetadata(jsonParser);
audioContent.getMetadata().add(metadata);
}
}
}
jsonParser.close();
System.out.println(audioContent);
}
private static Metadata parseMetadata(JsonParser jsonParser) throws IOException {
Metadata metadata = new Metadata();
while (jsonParser.nextToken() != JsonToken.END_OBJECT) { // END_OBJECT(}) 가 나올 때까지 토큰 순회
String fieldName = jsonParser.getCurrentName();
if ("propertyName".equals(fieldName)) {
jsonParser.nextToken();
metadata.setPropertyName(jsonParser.getText());
} else if ("propertyContent".equals(fieldName)) {
jsonParser.nextToken();
metadata.setPropertyContent(jsonParser.getText());
}
}
return metadata;
}
}
하지만 한번에 파싱하는 데이터 범위를 적절히 설정하지 않으면 이 역시 메모리를 많이 사용할 수 있으니
주의가 필요할 것 같습니다.
예를 들어, 위처럼 metadata 배열 중 하나의 항목이 아니라, 전체 metadata 배열을 한번에 파싱하게 했는데
배열에 항목 수가 굉장히 많다던가 하는 경우입니다.
그리고 파싱하는 것뿐만 아니라 객체로 json 데이터를 생성하는 것도 stream api로 처리가 가능합니다.