CodeGym /행동 /JAVA 25 SELF /Gson — 직렬화와 역직렬화, 설정

Gson — 직렬화와 역직렬화, 설정

JAVA 25 SELF
레벨 46 , 레슨 2
사용 가능

1. Gson 소개

우리는 이미 Jackson을 살펴보며 Java에서 JSON을 다룰 때 사실상의 표준으로 여겨지는 이유를 확인했습니다. 하지만 Android 세계에서 특히 큰 인기를 얻은 또 다른 라이브러리가 있는데, 바로 Gson입니다. Gson은 Google에서 Java 객체를 JSON으로 직렬화/역직렬화하기 위한 가볍고 단순한 해법으로 만들어졌습니다. 진입 장벽이 낮아 높은 평가를 받습니다. 시작하려면 거의 설정이 필요 없고, 대부분의 작업이 기본 설정만으로 바로 동작합니다.

Gson의 또 다른 장점은 가벼움입니다. 라이브러리 자체가 작고 추가 의존성도 많지 않아, 앱 크기가 민감한 환경—예를 들어 모바일—에서 자주 사용됩니다. Gson은 Android 프로젝트의 사실상 표준으로 자리잡았으며, 이때 컴팩트함과 단순함이 결정적인 역할을 합니다.

참고로 Gson은 Google JSON의 약자입니다. 커뮤니티에서는 농담으로 “Genius’ Son(천재의 아들)”이라고 부르기도 하지만, 물론 공식 명칭은 아닙니다. 말장난에 불과합니다.

프로젝트에 Gson 추가

Maven 또는 Gradle을 사용한다면 의존성을 추가하면 됩니다(버전은 달라질 수 있습니다):

Maven:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

Gradle:

implementation 'com.google.code.gson:gson:2.10.1'

아직 빌드 도구를 배우지 않았다면 우선 jar 파일을 Gson 공식 페이지 에서 내려받아 프로젝트에 추가해도 됩니다.

2. 기본 작업: 직렬화와 역직렬화

Gson으로 객체를 직렬화하고 역직렬화하는 방법을 간단한 클래스로 살펴보겠습니다.

예시: User 클래스

// 예제용 클래스
public class User {
    private String name;
    private int age;
    private boolean active;

    // 생성자
    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }

    // 게터와 세터 (필요할 때 Gson이 사용)
    public String getName() { return name; }
    public int getAge() { return age; }
    public boolean isActive() { return active; }
}

직렬화: 객체 → JSON

import com.google.gson.Gson;

public class GsonExample {
    public static void main(String[] args) {
        User user = new User("Alice", 25, true);

        Gson gson = new Gson();
        String json = gson.toJson(user);

        System.out.println(json);
        // {"name":"Alice","age":25,"active":true}
    }
}

주의: 필드는 클래스에 정의된 이름 그대로 직렬화됩니다!

역직렬화: JSON → 객체

public class GsonExample {
    public static void main(String[] args) {
        String json = "{\"name\":\"Bob\",\"age\":30,\"active\":false}";

        Gson gson = new Gson();
        User user = gson.fromJson(json, User.class);

        System.out.println(user.getName()); // Bob
        System.out.println(user.getAge());  // 30
        System.out.println(user.isActive());// false
    }
}

객체 목록 다루기

Gson은 컬렉션 처리에서 Jackson보다 약간 더 까다롭지만 해결 가능합니다.

import java.util.List;
import java.util.Arrays;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;

public class GsonListExample {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", 25, true),
            new User("Bob", 30, false)
        );

        Gson gson = new Gson();
        String json = gson.toJson(users);
        System.out.println(json);
        // [{"name":"Alice","age":25,"active":true},{"name":"Bob","age":30,"active":false}]

        // 목록 역직렬화
        Type userListType = new TypeToken<List<User>>(){}.getType();
        List<User> users2 = gson.fromJson(json, userListType);
        System.out.println(users2.get(0).getName()); // Alice
    }
}

중요한 포인트: 컬렉션을 역직렬화할 때는 TypeToken<>을 사용하세요!

3. Gson 설정: GsonBuilder

GsonGsonBuilder 클래스를 통해 유연한 구성을 제공합니다. 이를 이용해 pretty printing, null 직렬화, 날짜 포맷 지정 등 다양한 설정을 할 수 있습니다.

예시: 예쁘게 출력(pretty printing)과 null 직렬화

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GsonBuilderExample {
    public static void main(String[] args) {
        User user = new User("Charlie", 0, false);

        Gson gson = new GsonBuilder()
            .setPrettyPrinting()        // 보기 좋은 출력(들여쓰기)
            .serializeNulls()           // null 필드도 직렬화
            .create();

        String json = gson.toJson(user);
        System.out.println(json);
        /*
        {
          "name": "Charlie",
          "age": 0,
          "active": false
        }
        */
    }
}

날짜 포맷팅

Date 타입 필드가 있다면, 기본적으로 Gson은 고유한 형식으로 직렬화합니다. 원하는 형식을 지정할 수 있습니다:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.Date;

public class DateExample {
    private String event;
    private Date date;

    public DateExample(String event, Date date) {
        this.event = event;
        this.date = date;
    }
}

public class Main {
    public static void main(String[] args) {
        DateExample meeting = new DateExample("Team Meeting", new Date());

        Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd HH:mm:ss")
            .create();

        String json = gson.toJson(meeting);
        System.out.println(json);
        // {"event":"Team Meeting","date":"2024-06-10 13:45:23"}
    }
}

4. Gson 애너테이션: 직렬화 제어

Gson은 직렬화와 역직렬화를 더욱 세밀하게 제어하기 위한 애너테이션을 지원합니다.

@SerializedName — 필드 이름 바꾸기

JSON에서 필드 이름을 다르게 하고 싶다면 @SerializedName을 사용하세요:

import com.google.gson.annotations.SerializedName;

public class User {
    @SerializedName("full_name")
    private String name;
    private int age;
    private boolean active;

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }
}

이제 직렬화 시 해당 필드 이름은 full_name이 됩니다:

User user = new User("Diana", 28, true);
String json = new Gson().toJson(user);
// {"full_name":"Diana","age":28,"active":true}

@Expose — 표시한 필드만 직렬화

특정 필드만 직렬화하고 싶다면 @Expose를 사용하고 Gson을 다음처럼 구성하세요:

import com.google.gson.annotations.Expose;

public class User {
    @Expose
    private String name;

    @Expose
    private int age;

    private boolean active; // 직렬화되지 않음

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }
}

@Expose 지원을 켠 Gson을 생성합니다:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

Gson gson = new GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .create();

User user = new User("Eve", 21, false);
String json = gson.toJson(user);
// {"name":"Eve","age":21}

@Since/@Until — 버전 조건에 따른 선택적 직렬화
@Since@Until을 사용하면 특정 버전에서만 필드를 직렬화하도록 할 수 있습니다(실무에서는 자주 쓰이지 않지만 알아두면 유용합니다).

5. Gson의 특징과 한계

중첩 객체 처리

Gson은 중첩 객체도 잘 처리합니다:

public class Profile {
    private User user;
    private String bio;

    public Profile(User user, String bio) {
        this.user = user;
        this.bio = bio;
    }
}

Profile profile = new Profile(new User("Frank", 27, true), "Java developer");
String json = new Gson().toJson(profile);
// {"user":{"name":"Frank","age":27,"active":true},"bio":"Java developer"}

컬렉션 처리

컬렉션(List, Map) 직렬화는 문제가 없지만, 역직렬화에는 TypeToken을 사용하세요(위 예시 참조).

Jackson과 비교한 Gson의 제한사항

  • Java record 클래스 미지원(최근 버전 전까지)
  • 새로운 날짜/시간 API 지원이 제한적(예: LocalDate, LocalDateTime — 커스텀 어댑터 필요)
  • Jackson 애너테이션과 호환되지 않음
  • 복잡한 다형성 구조는 기본 제공(out of the box) 지원이 없음
  • 양방향 참조(순환 참조)에 대한 자동 지원 없음

커스텀 어댑터(TypeAdapter)

기본 기능만으로 부족하다면 복잡한 타입을 위한 직렬화/역직렬화 어댑터를 직접 작성할 수 있습니다.

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;

public class BooleanAsIntAdapter extends TypeAdapter<Boolean> {
    @Override
    public void write(JsonWriter out, Boolean value) throws IOException {
        out.value(value ? 1 : 0);
    }

    @Override
    public Boolean read(JsonReader in) throws IOException {
        return in.nextInt() == 1;
    }
}

// 사용 예:
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
    .create();

6. 실습: 설정을 곁들인 직렬화/역직렬화

학습용 애플리케이션을 확장해 사용자 목록을 JSON 형식으로 저장하고 불러오는 기능을 추가해 봅시다.

애너테이션이 포함된 User 클래스

import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;

public class User {
    @Expose
    @SerializedName("full_name")
    private String name;

    @Expose
    private int age;

    private boolean active; // 직렬화되지 않음

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }

    // 게터, 세터...
}

사용자 목록을 JSON으로 저장하기

import java.util.List;
import java.util.Arrays;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SaveUsers {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Ivan", 23, true),
            new User("Olga", 19, false)
        );

        Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .setPrettyPrinting()
            .create();

        String json = gson.toJson(users);
        System.out.println(json);
        /*
        [
          {
            "full_name": "Ivan",
            "age": 23
          },
          {
            "full_name": "Olga",
            "age": 19
          }
        ]
        */
    }
}

JSON에서 사용자 목록 불러오기

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;

public class LoadUsers {
    public static void main(String[] args) {
        String json = "[{\"full_name\":\"Ivan\",\"age\":23},{\"full_name\":\"Olga\",\"age\":19}]";

        Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .create();

        Type userListType = new TypeToken<List<User>>(){}.getType();
        List<User> users = gson.fromJson(json, userListType);

        for (User user : users) {
            System.out.println(user.getName() + " (" + user.getAge() + ")");
        }
        // Ivan (23)
        // Olga (19)
    }
}

7. Gson과 Jackson 비교

특징 Gson Jackson
사용 편의성 +++++ (매우 쉬움) +++ (조금 더 복잡)
라이브러리 크기 작음 더 큼
속도 빠르지만 약간 더 느림 매우 빠름
유연성 보통 높음(설정이 더 많음)
애너테이션 지원 자체 (@SerializedName) 자체 (@JsonProperty 등)
신규 타입 지원 제한적 우수함 (Java 8+, record)
Android 지원 우수함 좋지만 더 무거움
날짜 처리 어댑터 필요 기본 제공
다형성 제한적 유연하게 설정 가능

8. Gson 사용 시 자주 하는 실수

실수 №1: 컬렉션에 TypeToken을 사용하지 않음.
목록이나 맵을 역직렬화할 때는 반드시 TypeToken<>을 사용하세요. 그렇지 않으면 이상한 오류가 나거나 빈 컬렉션을 얻게 됩니다.

실수 №2: 기본 생성자가 없음.
Gson은 기본 생성자 없이도 동작할 수 있지만, 복잡한 객체를 역직렬화할 때 기본 생성자가 없으면 문제가 생기기도 합니다. 역직렬화를 계획한다면 기본 생성자를 추가하는 것이 좋습니다.

실수 №3: 필드 이름 불일치.
JSON에서 필드가 "full_name"인데 클래스에서는 "name"이라면, @SerializedName("full_name") 애너테이션이 없이는 매핑되지 않아 값이 null이 됩니다.

실수 №4: private 필드 관련 문제.
Gson은 private 필드를 직렬화할 수 있지만, 모든 필드가 private이고 게터/세터가 전혀 없다면 역직렬화에서 문제가 생길 수 있습니다. 게터와 세터를 사용하는 것이 좋습니다.

실수 №5: 날짜 처리.
기본적으로 GsonDate를 불편한 형식으로 직렬화합니다. LocalDate, LocalDateTime 등 새로운 타입은 커스텀 어댑터 없이는 직렬화 오류가 발생합니다.

실수 №6: @Expose는 안 쓰면서 excludeFieldsWithoutExposeAnnotation()을 켠 경우.
excludeFieldsWithoutExposeAnnotation()을 켰는데 필드에 @Expose를 붙이지 않으면, 해당 필드는 직렬화/역직렬화되지 않습니다. 결과는 빈 JSON이거나 필드 값이 null인 객체가 됩니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION