Java/Spring Boot
[Java] Spring Boot 환경에서 Jackson 모듈 활용하기 : JSON 파싱, 직렬화, 역 직렬화, JSON 파일 읽어오기/생성
adjh54
2024. 1. 1. 18:14
반응형
해당 글에서는 Spring Boot 환경에서 Jackson 라이브러리를 활용하는 방법에 대해서 알아봅니다.
1) Jackson
💡 Jackson
- JSON 데이터 작업을 하기 위한 인기 있는 Java 라이브러리입니다.
- JSON 파일을 읽거나 쓰는 기능을 제공합니다.
- Java 객체를 JSON으로 직렬화 하거나 JSON을 Java 객체로 역 직렬화 하는 기능을 제공합니다.
[ 더 알아보기 ]
💡 직렬화(Serialization)
- 객체를 바이트 스트림으로 변환하는 과정을 말합니다. 이렇게 직렬화된 객체는 파일에 저장하거나 네트워크를 통해 전송할 수 있습니다. 직렬화된 객체는 나중에 역 직렬화(Deserialization)를 통해 원래의 객체로 복원할 수 있습니다.
💡 역 직렬화(Deserialization)
- 직렬화된 바이트 스트림을 원래의 객체로 복원하는 과정입니다. 역 직렬화를 위해서는 직렬화할 때와 동일한 클래스의 정의가 필요합니다.
1. Jackson 모듈
💡 Jackson 모듈
- Jackson Streaming(=Jackson Core)는 JSON 파일을 읽거나 새로 생성하는 경우의 기능들을 주로 제공합니다.
- Jackson Annotation은 JSON 파일과 객체를 어노테이션으로 매핑하여 쉽게 데이터 처리하도록 제공합니다.
- Jackson Databind는 JSON 파일을 가장 쉽게 직렬화와 역직렬화 하는 기능을 제공합니다.
모듈 | 설명 |
Jackson Streaming(=Jackson Core) | JSON 파일을 불러와 데이터를 읽거나 JSON 데이터를 생성하는 기능을 제공합니다. |
Jackson Annotation | JSON 데이터를 클래스 변수와 매핑하여 파싱 및 직렬화를 하는데 사용이 됩니다. |
Jackson Databind | JSON 데이터를 직렬화 및 역직렬화를 위한 데이터 바인딩 기능을 제공합니다. |
2) Jackson Streaming API(= Jackson-core) : JSON 읽기/쓰기
💡 Jackson Streaming API(=Jackson-core)
- 메모리 사용을 최소화하면서 JSON 데이터를 처리하는 데 사용됩니다.
- 해당 모듈은 높은 처리량을 필요한 경우에 유용하며 대용량의 JSON 데이터를 효율적으로 읽고 쓸 수 있습니다.
- Smile (binary JSON), XML, CSV, Protobuf, and CBOR 등 다양한 데이터 타입에 대해 지원합니다.
1. 주요 클래스와 메서드
💡 주요 클래스
- JsonFactory, JsonParser, JsonGenerator로 구성이 되어 있습니다.
클래스 | 설명 |
JsonFactory | JSON Parser 및 Generator를 만들기 위한 팩토리 클래스입니다. 이 클래스를 사용하여 JSON 데이터를 파싱하고 생성할 수 있습니다. |
JsonParser | JSON 데이터를 파싱하는 데 사용되는 클래스입니다. JsonParser 객체를 사용하여 JSON 데이터의 토큰을 읽고 필드 이름 및 값에 액세스할 수 있습니다. |
JsonGenerator | JSON 라이브러리에서 JSON 데이터를 생성하는 데 사용되는 클래스입니다. JsonGenerator 객체를 사용하여 JSON 데이터를 생성하고 필드 이름과 값을 설정할 수 있습니다. |
💡 주요 객체의 메서드
클래스 | 메서드 | 설명 |
JsonFactory | factory.builder().build() | JsonFactory 빌더를 사용하여 JsonFactory 인스턴스를 생성합니다. |
JsonParser | factory.createParser() | JsonFactory로부터 JsonParser 인스턴스를 생성합니다. |
JsonParser | parser.nextToken() | 다음 토큰으로 이동하고 해당 토큰을 반환합니다. |
JsonParser | parser.currentToken() | 현재 토큰을 반환합니다. |
JsonParser | parser.getValueAsString() | 현재 토큰의 값을 문자열로 반환합니다. |
JsonParser | parser.getIntValue() | 현재 토큰의 값을 정수로 반환합니다. |
JsonParser | parser.close() | JsonParser를 닫고 관련 리소스를 해제합니다. |
JsonGenerator | factory.createGenerator(); | JsonFactory로부터 JsonGenerator 인스턴스를 생성합니다. |
JsonGenerator | generator.writeStartArray(); | JSON 배열의 시작을 작성합니다. |
JsonGenerator | generator.writeEndArray(); | JSON 배열의 끝을 작성합니다. |
JsonGenerator | generator.writeStartObject(); | JSON 객체의 시작을 작성합니다. |
JsonGenerator | generator.writeEndObject(); | JSON 객체의 끝을 작성합니다. |
JsonGenerator | generator.writeStringField() | 지정된 이름과 문자열 값을 가진 필드를 작성합니다. |
JsonGenerator | generator.writeNumberField() | 지정된 이름과 숫자 값을 가진 필드를 작성합니다. |
JsonGenerator | generator.close() | JsonGenerator를 닫고 관련 리소스를 해제합니다 |
[ 더 알아보기 ]
💡 JsonParser에서 토큰을 무엇을 의미하는가?
- JSON 데이터 구조의 각 부분을 나타냅니다. 토큰은 JSON 데이터의 요소를 읽고 분석하는 데 사용됩니다.
- 예를 들어, 토큰은 객체의 시작과 끝을 나타내거나 배열의 요소를 나타낼 수 있습니다. 토큰은 JSON 데이터를 파싱하고 처리하는 과정에서 중요한 개념입니다.
2. 라이브러리 추가
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-core:2.16.1'
}
Maven Repository: com.fasterxml.jackson.core » jackson-core
3. JSON 데이터 확인
💡JSON 데이터 확인
- 파일을 읽거나 쓰기 위한 목적으로 사용되는 'data.json'파일에 대해 확인합니다.
[
{
"name": "John",
"age": 30,
"city": "Seoul"
},
{
"name": "Jane",
"age": 25,
"city": "Busan"
},
{
"name": "David",
"age": 35,
"city": "Incheon"
}
]
4. JSON 데이터 읽기 : JsonParser
💡 JSON 데이터 읽기
1. JsonFactory Builder를 구성합니다.
2. JSON 파일 불러옵니다.
3. 파일을 기반으로 JsonParser 구성합니다.
4. JSONArray가 아니면 종료합니다.
5. JSONArray의 끝 배열이면 순환을 종료합니다.
6. 시작데이터가 Object인지 확인합니다.
7. 종료데이터가 Object일때까지 반복합니다.
8. JSON의 키 값을 가져옵니다.
9. 키 값을 기반으로 값을 추출합니다.
/**
* JSON 데이터 읽기
*
* @return
* @throws IOException
*/
@GetMapping("/1")
public ResponseEntity<Object> readJsonData() throws IOException {
String result = "";
// [STEP1] JsonFactory Builder 구성
JsonFactory factory = JsonFactory.builder().build();
// [STEP2] JSON 파일 불러오기
File file = new File(System.getProperty("user.dir") + "/data.json");
// [STEP3] 파일을 기반으로 JsonParser 구성
JsonParser parser = factory.createParser(file);
// [STEP4] JSONArray 확인
if (parser.nextToken() != JsonToken.START_ARRAY) {
throw new IOException("Error: Expected data to start with an Object");
}
try {
// [STEP5] JSONArray의 끝인 배열이 나올때까지 반복합니다.
while (parser.nextToken() != JsonToken.END_ARRAY) {
// [STEP6] 시작 데이터가 Object인지 확인
if (parser.currentToken() == JsonToken.START_OBJECT) {
// [STEP7] Object 데이터 끝이 될때까지 반복합니다.
while (parser.nextToken() != JsonToken.END_OBJECT) {
// [STEP8] JSON의 키 값을 가져옵니다.
String fieldName = parser.getCurrentName();
parser.nextToken();
// [STEP9] JSON의 키 값을 기반으로 값을 추출합니다.
if ("name".equals(fieldName)) {
String name = parser.getValueAsString();
System.out.println("name :: " + name);
} else if ("age".equals(fieldName)) {
int age = parser.getIntValue();
System.out.println("age :: " + age);
} else if ("city".equals(fieldName)) {
String city = parser.getValueAsString();
System.out.println("city :: " + city);
}
}
}
}
parser.close();
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
💡 아래와 같이 파일에서 데이터를 읽어와 출력하였습니다.
[ 더 알아보기 ]
💡 JsonFactory를 구성할때 Builder를 사용하는 이유는?
- 아래와 같이 공식사이트에서 Builder를 사용하기를 권장하고 있습니다.
5. JSON 데이터 쓰기 : JsonGenerator
💡 JSON 데이터 쓰기
1. JsonFactory Builder 구성합니다.
2. JSON 파일 생성 위치 지정합니다.
3. jsonGenerator 객체 생성합니다.
4. Array 형태로 생성합니다.
5. Object 형태로 생성합니다.
6. 필드 값을 채워줍니다.
7. Object 형태를 닫아줍니다.
8. Array 형태를 닫아줍니다.
9. JsonGenerator를 닫고 관련 리소스를 해제합니다.
/**
* JSONArray 파일 생성
*
* @return
*/
@GetMapping("/2")
public ResponseEntity<Object> writeJsonData() {
String result = "";
try {
// [STEP1] JsonFactory Builder 구성
JsonFactory factory = JsonFactory.builder().build();
// [STEP2] JSON 파일 생성 위치 지정
File file = new File(System.getProperty("user.dir") + "/createData.json");
// [STEP3] jsonGenerator 객체 생성
JsonGenerator jsonGenerator = factory.createGenerator(file, JsonEncoding.UTF8);
// [STEP4] Array 형태로 생성합니다.
jsonGenerator.writeStartArray();
// [STEP5] Object 형태로 생성합니다.
jsonGenerator.writeStartObject();
// [STEP6] 필드 값을 채워줍니다.
jsonGenerator.writeStringField("name", "John");
jsonGenerator.writeNumberField("age", 30);
jsonGenerator.writeStringField("city", "Seoul");
// [STEP7] Object 형태를 닫아줍니다.
jsonGenerator.writeEndObject();
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "Jane");
jsonGenerator.writeNumberField("age", 25);
jsonGenerator.writeStringField("city", "Busan");
jsonGenerator.writeEndObject();
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "David");
jsonGenerator.writeNumberField("age", 35);
jsonGenerator.writeStringField("city", "Incheon");
jsonGenerator.writeEndObject();
// [STEP8] Array 형태를 닫아줍니다.
jsonGenerator.writeEndArray();
// [STEP9] JsonGenerator를 닫고 관련 리소스를 해제합니다.
jsonGenerator.close();
System.out.println("JSON 파일이 성공적으로 생성되었습니다.");
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
💡 아래와 같이 ‘createData.json’파일이 생성되었고 각각의 값이 생성됨을 확인하였습니다.
3) Jackson Annotation : 직렬화/역 직렬화
💡 Jackson Annotation
- JSON의 특정 필드 이름과 클래스의 필드를 매핑하는 역할을 수행합니다.
- 어노테이션을 사용하여 JSON 직렬화 및 역직렬화를 제어하는 데 사용됩니다.
- 객체와 JSON 간의 매핑을 세밀하게 제어할 수 있으며 직렬화 및 역직렬화 시에 특정 필드를 무시하거나 형식을 변환할 수 있습니다.
1. 주요 어노테이션
어노테이션 | 설명 |
@JsonProperty | 해당 필드를 JSON 속성에 매핑합니다. 속성의 이름을 지정할 수 있으며, 기본적으로 필드의 이름을 사용합니다. |
@JsonIgnore | 해당 필드를 JSON 직렬화 및 역직렬화에서 무시합니다. |
@JsonFormat | 해당 필드의 직렬화 및 역직렬화 형식을 지정합니다. 날짜, 시간, 숫자 등의 형식을 제어할 수 있습니다. |
@JsonIgnoreProperties | 해당 클래스에서 무시할 JSON 속성의 이름을 지정합니다. |
@JsonSerialize | 해당 필드의 직렬화 방법을 커스터마이즈합니다. 직렬화를 위한 커스텀 시리얼라이저를 지정할 수 있습니다. |
@JsonDeserialize | 해당 필드의 역직렬화 방법을 커스터마이즈합니다. 역직렬화를 위한 커스텀 디시리얼라이저를 지정할 수 있습니다. |
@JsonCreator | JSON 객체를 해당 클래스의 인스턴스로 역직렬화하기 위한 생성자를 지정합니다. |
2. 라이브러리 추가
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.16.1'
}
Maven Repository: com.fasterxml.jackson.core » jackson-annotations
3. 매핑할 JSON 데이터 확인
[
{
"name": "John",
"age": 30,
"city": "Seoul"
},
{
"name": "Jane",
"age": 25,
"city": "Busan"
},
{
"name": "David",
"age": 35,
"city": "Incheon"
}
]
4. JSON과 객체의 매핑
💡 JSON과 객체의 매핑
- 예상되는 JSON 필드의 키 값을 기반으로 객체와 매핑을 합니다.
package com.multiflex.multiflexchatgpt.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
/**
* JSON 객체와 매핑
*
* @author : jonghoon
* @fileName : Person
* @since : 12/31/23
*/
@Getter
public class Person {
@JsonProperty("name")
private String name;
@JsonProperty("age")
private int age;
@JsonProperty("city")
private String city;
public Person() {
}
}
5. JSON 파일 읽기 : 역직렬화
💡 JSON 파일 읽기
- Annotation 기반으로 Object와 JSON을 매핑한 데이터를 조회합니다.
1. 프로젝트 내의 JSON 파일을 읽어옵니다.
2. JSON 파일과 객체를 매핑합니다. : 역 직렬화
3. 매핑한 객체의 값을 조회합니다.
/**
* Annotation 기반으로 Object와 JSON을 매핑한 데이터를 조회합니다.
*
* @return
*/
@GetMapping("/3")
public ResponseEntity<Object> readAnnotation() {
String result = "";
ObjectMapper objectMapper = new ObjectMapper();
// [STEP1] 프로젝트 내의 JSON 파일을 읽어옵니다.
File file = new File(System.getProperty("user.dir") + "/data.json");
try {
// [STEP2] JSON 파일과 객체를 매핑합니다. : 역 직렬화
List<Person> people = Arrays.asList(objectMapper.readValue(file, Person[].class));
// [STEP3] 매핑한 객체의 값을 조회합니다.
for (Person person : people) {
System.out.println("Name :: " + person.getName());
System.out.println("Age :: " + person.getAge());
System.out.println("City :: " + person.getCity());
}
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
💡 아래와 같이 파일에서 가져온 데이터를 객체로 매핑하고 객체에서 출력을 하였습니다.
6. 재 구성하여 직렬화 수행
💡 재 구성하여 직렬화 수행
1. 프로젝트 내의 JSON 파일을 읽어옵니다.
2. JSON 파일과 객체를 매핑합니다.- 이전과 다르게 가변 객체로 변환하였습니다.
3. 배열 내에 객체를 추가하였습니다.
4. 객체를 JSON 파일로 직렬화하여 저장합니다.
/**
* Annotation 기반으로 Object와 JSON을 매핑한 데이터를 조회합니다.
*
* @return
*/
@GetMapping("/3")
public ResponseEntity<Object> readAnnotation() {
String result = "";
ObjectMapper objectMapper = new ObjectMapper();
// [STEP1] 프로젝트 내의 JSON 파일을 읽어옵니다.
File file = new File(System.getProperty("user.dir") + "/data.json");
try {
// [STEP2] JSON 파일과 객체를 매핑합니다. - 이전과 다르게 가변 객체로 변환하였습니다.
List<Person> peopleList = new ArrayList<>(Arrays.asList(objectMapper.readValue(file, Person[].class)));
// [STEP3] 배열 내에 객체를 추가하였습니다.
Person updatePerson = Person
.builder()
.name("kim")
.age(35)
.city("gyeonggi-do")
.build();
Person updatePerson2 = Person
.builder()
.name("park")
.age(30)
.city("gyeonggi-do")
.build();
peopleList.add(updatePerson);
peopleList.add(updatePerson2);
// [STEP4] 객체를 JSON 파일로 직렬화하여 저장합니다.
result = objectMapper.writeValueAsString(peopleList);
log.debug("재구성한 직렬화 결과 :: " + result);
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
💡 아래와 같이 직렬화된 데이터가 출력됨을 확인 할 수 있습니다.
4) Jackson Databind : 직렬화/역 직렬화
💡 Jackson Databind
- Java 객체와 JSON 데이터 간의 매핑을 처리하는데 사용됩니다.
- Java 객체를 JSON으로 직렬화하거나 JSON을 Java 객체로 역직렬화할 수 있는 기능을 제공합니다.
- 다양한 형식의 데이터를 다룰 수 있으며, 커스텀 직렬화 및 역직렬화 로직을 구현할 수 있습니다.
1. 주요 메서드
클래스 | 메서드 | 설명 |
ObjectMapper | writeValueAsString(value: Any) | 지정된 객체를 JSON 문자열로 직렬화합니다. |
ObjectMapper | readValue(content: String, valueType: TypeReference<T>) | 주어진 JSON 문자열을 지정된 타입의 객체로 변환합니다. |
ObjectMapper | configure() | ObjectMapper의 구성을 설정합니다. |
ObjectMapper | registerModule(module: Module) | 지정된 모듈을 ObjectMapper에 등록합니다. |
ObjectMapper | disable(feature: JsonParser.Feature) | 지정된 JSON 파서 기능을 비활성화합니다. |
ObjectMapper | enable(feature: JsonParser.Feature) | 지정된 JSON 파서 기능을 활성화합니다. |
ObjectMapper | findAndRegisterModules() | 클래스 경로에서 사용 가능한 모듈을 찾아 ObjectMapper에 등록합니다. |
ObjectMapper | writerWithDefaultPrettyPrinter() | 기본 PrettyPrinter를 사용하는 ObjectWriter를 반환합니다. |
ObjectMapper | setVisibilityChecker(checker: VisibilityChecker<*>) | 지정된 가시성 검사기를 ObjectMapper에 설정합니다. |
ObjectMapper | setDefaultPropertyInclusion(inclusion: JsonInclude.Include) | 기본 속성 포함 규칙을 설정합니다. |
ObjectMapper | setSerializationInclusion(inclusion: JsonInclude.Include) | 직렬화 중에 속성을 포함할지 여부를 설정합니다. |
ObjectMapper | setDeserializationInclusion(inclusion: JsonInclude.Include) | 역직렬화 중에 속성을 포함할지 여부를 설정합니다. |
ObjectMapper | configure(feature: SerializationFeature, state: Boolean) | 지정된 직렬화 기능을 설정하거나 해제합니다. |
ObjectMapper | configure(feature: DeserializationFeature, state: Boolean) | 지정된 역직렬화 기능을 설정하거나 해제합니다. |
ObjectMapper | configure(feature: MapperFeature, state: Boolean) | 지정된 매퍼 기능을 설정하거나 해제합니다. |
ObjectMapper | configure(feature: JsonParser.Feature, state: Boolean) | 지정된 JSON 파서 기능을 설정하거나 해제합니다. |
ObjectMapper | configure(feature: JsonGenerator.Feature, state: Boolean) | 지정된 JSON 생성기 기능을 설정하거나 해제합니다. |
ObjectMapper | enableDefaultTyping() | 기본 타입 정보 포함을 활성화합니다. |
ObjectMapper | disableDefaultTyping() | 기본 타입 정보 포함을 비활성화합니다. |
2. 라이브러리 추가
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1'
}
https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
3. 직렬화 코드
💡 직렬화 코드
- PersonDto라는 객체를 writeValueAsString() 메서드로 직렬화를 수행하였습니다.
package com.multiflex.multiflexchatgpt.dto;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PersonDto {
private String id;
private String pw;
private String addr;
@Builder
public PersonDto(String id, String pw, String addr) {
this.id = id;
this.pw = pw;
this.addr = addr;
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
@GetMapping("/3")
public ResponseEntity<Object> serializationApi() {
String result = "";
PersonDto personDto = PersonDto.builder()
.id("exampleId")
.pw("examplePw")
.addr("exampleAddr")
.build();
ObjectMapper objectMapper = new ObjectMapper();
try {
result = objectMapper.writeValueAsString(personDto);
log.debug("직렬화 :: " + result);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
💡 아래와 같이 객체를 직렬화로 문자열 형태로 변환하였습니다.
4. 역 직렬화 코드
💡 역 직렬화 코드
- 문자열 형태의 JSON을 readValue() 메서드를 이용하여 역 직렬화를 수행하였습니다
package com.multiflex.multiflexchatgpt.dto;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PersonDto {
private String id;
private String pw;
private String addr;
@Builder
public PersonDto(String id, String pw, String addr) {
this.id = id;
this.pw = pw;
this.addr = addr;
}
}
@GetMapping("/4")
public ResponseEntity<Object> serializationApi() {
String result = "";
/*
* 직렬화 수행
*/
PersonDto personDto = PersonDto.builder()
.id("exampleId")
.pw("examplePw")
.addr("exampleAddr")
.build();
ObjectMapper objectMapper = new ObjectMapper();
try {
result = objectMapper.writeValueAsString(personDto);
log.debug("직렬화 :: " + result);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
/*
* 역 직렬화 수행
*/
PersonDto personDto2;
try {
ObjectMapper objectMapper2 = new ObjectMapper();
personDto2 = objectMapper2.readValue(result, PersonDto.class);
log.debug("역 직렬화 ::" + personDto2);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return new ResponseEntity<>(personDto2, HttpStatus.OK);
}
💡 아래와 같이 문자열의 직렬화 된 코드를 객체 형태로 역 직렬화를 수행하였습니다.
오늘도 감사합니다. 😀
반응형