Skip to content

Conversation

@HI-JIN2
Copy link
Member

@HI-JIN2 HI-JIN2 commented Feb 10, 2026

Summary

Gson 기반 JSON 파싱 구조에서 서버 응답의 null / 누락 필드로 인한 NPE 가능성리플렉션 기반 파싱으로 인한 성능·안정성 한계가 지속적으로 존재해 kotlinx.serialization으로 전환했습니다.

핵심 효과

1. 기본값(Default Value) 처리 안정성 (가장 큰 장점)

  • Gson
    리플렉션(Reflection) 기반으로 객체를 생성하면서 코틀린 생성자를 우회하는 경우가 많음
    JSON 필드가 누락되면 val list: List<String> = emptyList() 같은 기본값이 무시되고 null 이 들어가는 문제가 빈번
    => 결과적으로 예상치 못한 NPE 발생 가능

  • Kotlin Serialization
    생성자를 명확히 호출 → 기본값이 정상 적용
    NetworkModule.ktcoerceInputValues = true 옵션 덕분에 JSON에 null이 내려와도 Non-null + 기본값이 있는 프로퍼티는 안전하게 기본값으로 변환됨
    => 서버에서 null을 내려주거나 필드를 빼먹어도 앱이 죽을 확률을 크게 낮춤

2. 런타임 성능 향상

  • Gson: 런타임 리플렉션 사용 → 파싱 오버헤드 존재
  • Kotlin Serialization: 컴파일 시점에 직렬화 코드 생성 → 리플렉션 없음, 더 빠르고 가벼움

3. 순수 코틀린 친화성

Nullable 타입, Sealed class, Value class 등 코틀린 고유 문법을 자연스럽게 지원

4. 라이브러리 크기 감소

Retrofit Gson Converter 대비 Kotlin Serialization Converter + Core 라이브러리가 일반적으로 더 가벼움

이번 변경으로 인해 ① 파싱 안정성, ② 앱 크래시 리스크, ③ 런타임 성능 세 가지를 동시에 개선했습니다.

Describe your changes

주의사항: kotlinx.serialization의 엄격함

  • Gson은 필드가 없으면 null로 처리하거나 조용히 넘어가는 반면,
  • Kotlin Serialization은 생성자를 직접 호출하기 때문에 JSON에 필드가 없고 기본값도 없으면 → MissingFieldException 발생

예시 로그:

kotlinx.serialization.MissingFieldException:
  Field 'partnershipType' is required for type
  'com.eatssu.android.data.remote.dto.response.PartnershipResponse.PartnershipInfo',  but it was missing

대응 방법

  • Nullable 타입이라도 서버에서 필드를 아예 빼는 경우를 고려
  • 필요한 필드는 명시적으로 null을 허용하거나 기본값을 지정 ✅

관련 커밋: cee31fd, 62c3af8
(해당 커밋 이전에도 DTO 보수가 어느 정도 되어 있어 앱 크래시로 이어지지는 않았습니다.)

Issue

To reviewers

  • DTO 정의에서 필드 누락 가능성
  • 기본값 / nullable 설정의 적절성
  • coerceInputValues 설정 의도가 잘 반영되었는지

@HI-JIN2 HI-JIN2 self-assigned this Feb 10, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @HI-JIN2, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 애플리케이션의 JSON 직렬화 메커니즘을 Gson에서 Kotlinx Serialization으로 마이그레이션하여 리팩토링합니다. 이 변경은 앱 전반의 데이터 직렬화를 표준화하고, 성능을 향상시키며, 타입 안정성을 강화합니다. 또한 새로운 직렬화 라이브러리에 맞춰 네트워크 구성, 데이터 모델 및 UI 컴포넌트를 업데이트하고, 식당/상점 유형 enum을 리팩토링하여 일관성을 높였습니다.

Highlights

  • Kotlinx Serialization 마이그레이션: JSON 처리 라이브러리를 Gson에서 Kotlinx Serialization으로 전면 마이그레이션했습니다. 이는 app/build.gradle.kts에서 관련 플러그인 및 의존성 추가/제거를 통해 이루어졌습니다.
  • 데이터 클래스 직렬화 어노테이션 변경: 모든 데이터 전송 객체(DTO) 및 중첩 데이터 클래스에 Gson의 @SerializedName 대신 Kotlinx Serialization의 @Serializable@SerialName 어노테이션을 적용하여 직렬화 방식을 통일했습니다.
  • 네트워크 모듈 업데이트: Retrofit의 컨버터 팩토리가 Kotlinx Serialization을 사용하도록 NetworkModule을 업데이트하고, ApiResultCallAdapterFactoryApiResultCall에서도 Kotlinx Serialization의 Json 인스턴스를 활용하도록 변경했습니다.
  • Firebase Remote Config 통합 개선: FirebaseRemoteConfigRepositoryImpl에서 식당 정보 파싱 시 Gson 대신 Kotlinx Serialization을 사용하도록 변경하여 일관성을 유지했습니다.
  • Enum 타입 리팩토링 및 통일: RestaurantType enum을 common 모듈의 StoreType으로 대체하고, Restaurant enum에 @Serializable 어노테이션을 추가했습니다. 또한 map 모듈의 PlaceType enum을 제거하여 enum 사용을 간소화했습니다.
  • 지도 UI 컴포넌트 업데이트: 지도 관련 UI 컴포넌트(MapFragmentView, MapViewModel, MapRestaurantBottomSheet)를 StoreType enum 변경 사항에 맞춰 업데이트하여 아이콘 리소스 및 카테고리 표시를 조정했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • app/build.gradle.kts
    • Kotlinx Serialization 플러그인 및 의존성 추가
    • Gson 관련 의존성 제거
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/ChangeNicknameRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/CheckValidTokenRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/LoginWithKakaoRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/ModifyReviewRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/ReportRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/UserDepartmentRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/WriteMealReviewRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/request/WriteMenuReviewRequest.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/BaseResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/CollegeResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/DepartmentResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/ImageResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MealResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MealReviewInfoResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MealReviewListResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MenuOfMealResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MenuResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MenuReviewInfoResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MenuReviewListResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MyNickNameResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/MyReviewListResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/PartnershipResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
    • restaurantType 필드 타입을 StoreType으로 변경
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/PartnershipRestaurantResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
    • restaurantType 필드 타입을 StoreType으로 변경하고 storeType으로 이름 변경
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/TokenResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/dto/response/UserCollegeDepartmentResponse.kt
    • @Serializable 어노테이션 추가
    • @SerializedName 제거 및 @SerialName으로 대체
  • app/src/main/java/com/eatssu/android/data/remote/repository/FirebaseRemoteConfigRepositoryImpl.kt
    • Gson 관련 import 제거 및 Kotlinx Serialization import 추가
    • 생성자에 Json 인스턴스 주입
    • parseCafeteriaJson 메서드에서 Gson 대신 Kotlinx Serialization을 사용하여 JSON 파싱 로직 변경
  • app/src/main/java/com/eatssu/android/di/NetworkModule.kt
    • Gson 관련 import 제거 및 Kotlinx Serialization 관련 import 추가
    • provideJson 메서드를 통해 Json 인스턴스 제공
    • provideCallAdapterFactory, provideAuthRetrofit, provideNoAuthRetrofit 메서드에서 Json 인스턴스를 사용하도록 변경
    • Retrofit 컨버터 팩토리를 GsonConverterFactory에서 json.asConverterFactory로 변경
  • app/src/main/java/com/eatssu/android/di/network/ApiResultCall.kt
    • Gson 관련 import 제거 및 Kotlinx Serialization import 추가
    • 생성자에 Json 인스턴스 주입
    • 에러 바디 파싱 로직에서 Gson 대신 Kotlinx Serialization을 사용하도록 변경
    • clone 메서드에서 Json 인스턴스 전달
  • app/src/main/java/com/eatssu/android/di/network/ApiResultCallAdapter.kt
    • Kotlinx Serialization import 추가
    • 생성자에 Json 인스턴스 주입
    • adapt 메서드에서 ApiResultCall 생성 시 Json 인스턴스 전달
  • app/src/main/java/com/eatssu/android/di/network/ApiResultCallAdapterFactory.kt
    • Kotlinx Serialization import 추가
    • 생성자에 Json 인스턴스 주입
    • get 메서드에서 ApiResultCallAdapter 생성 시 Json 인스턴스 전달
  • app/src/main/java/com/eatssu/android/domain/model/Partnership.kt
    • RestaurantType import 제거 및 StoreType import 추가
    • restaurantType 필드 타입을 StoreType으로 변경
  • app/src/main/java/com/eatssu/android/domain/model/PartnershipRestaurant.kt
    • RestaurantType import 제거 및 StoreType import 추가
    • restaurantType 필드를 storeType으로 이름 변경 및 타입 StoreType으로 변경
    • RestaurantType enum 정의 제거
  • app/src/main/java/com/eatssu/android/domain/model/RestaurantInfo.kt
    • @Serializable 어노테이션 추가
  • app/src/main/java/com/eatssu/android/domain/model/WidgetMealInfo.kt
    • @Serializable@SerialName 어노테이션 추가
  • app/src/main/java/com/eatssu/android/domain/usecase/user/GetPartnershipDetailUseCase.kt
    • restaurantType 필드를 storeType으로 이름 변경
  • app/src/main/java/com/eatssu/android/presentation/map/MapFragmentView.kt
    • 불필요한 import 제거 (android.R.id.message, android.widget.Toast, androidx.compose.ui.unit.sp)
    • RestaurantType import 제거 및 StoreType import 추가
    • mapState.placeTypemapState.storeType으로 변경하여 사용
    • 아이콘 리소스 로직에서 RestaurantType 대신 StoreType 사용
    • 텍스트 표시 로직에서 placeType.placeCategory 대신 storeType.value 사용
  • app/src/main/java/com/eatssu/android/presentation/map/MapViewModel.kt
    • R import 추가
    • RestaurantType import 제거 및 StoreType import 추가
    • PlaceType import 제거
    • StoreType의 확장 프로퍼티 iconRes 추가
    • MapStateplaceType 필드를 storeType으로 변경 및 타입 StoreType으로 변경
    • 데이터 처리 로직에서 representative.restaurantTyperepresentative.storeType으로 변경
  • app/src/main/java/com/eatssu/android/presentation/map/component/MapRestaurantBottomSheet.kt
    • PlaceType import 제거 및 StoreType import 추가
    • 함수 파라미터 placeTypestoreType으로 변경 및 타입 StoreType으로 변경
    • 아이콘 리소스 로직에서 placeType.iconRes 대신 storeType.iconRes 사용
    • 텍스트 표시 로직에서 placeType.placeCategory 대신 storeType.value 사용
    • 프리뷰 함수에서 placeType 대신 storeType 사용
  • app/src/main/java/com/eatssu/android/presentation/map/model/PlaceType.kt
    • 파일 제거
  • app/src/main/java/com/eatssu/android/presentation/widget/MealInfoStateDefinition.kt
    • Gson 관련 import 제거 및 Kotlinx Serialization import 추가
    • MealInfoSerializer에서 Gson 대신 Kotlinx Serialization을 사용하여 readFromwriteTo 로직 변경
  • build.gradle.kts
    • Kotlinx Serialization 플러그인 적용
  • core/common/build.gradle.kts
    • Kotlinx Serialization 플러그인 적용
    • Kotlinx Serialization JSON 의존성 추가
  • core/common/src/main/java/com/eatssu/common/enums/Restaurant.kt
    • @Serializable 어노테이션 추가
    • 더 이상 사용되지 않는 fromRestaurantEnumName 함수 제거
  • core/common/src/main/java/com/eatssu/common/enums/StoreType.kt
    • 새로운 StoreType enum 파일 추가
  • core/common/src/main/java/com/eatssu/common/enums/Time.kt
    • 더 이상 사용되지 않는 fromTimeEnumName 함수 제거
  • gradle/libs.versions.toml
    • Gson 관련 버전 및 라이브러리 정의 제거
    • Kotlinx Serialization JSON 및 Retrofit Kotlinx Serialization Converter 버전 및 라이브러리 정의 추가
    • Kotlin Serialization 플러그인 정의 추가
Activity
  • HI-JIN2 님이 Kotlinx Serialization 마이그레이션 및 enum 자동 매핑을 위해 Pull Request를 생성했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 기존의 Gson 라이브러리를 kotlinx.serialization으로 마이그레이션하는 대규모 리팩토링입니다. 전반적으로 변경 사항이 잘 적용되었으며, 코드의 가독성과 유지보수성이 크게 향상되었습니다. 특히 DTO에 @Serializable 어노테이션을 추가하고, Retrofit 설정에서 GsonConverterFactoryKotlinxSerializationConverterFactory로 교체한 점, 그리고 enum 타입을 JSON 문자열과 자동으로 매핑하여 수동 변환 로직을 제거한 점이 인상적입니다. 몇 가지 추가적인 개선 사항을 제안합니다.

Comment on lines 65 to 66
} catch (e: Exception) {
Timber.e(e, "Failed to parse cafeteria JSON")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

try-catch 블록에서 제네릭 Exception을 잡고 있습니다. 이는 코루틴의 취소 메커니즘을 방해할 수 있는 CancellationException과 같은 중요한 예외까지 가로챌 수 있습니다. JSON 파싱 오류를 처리하기 위해서는 kotlinx.serialization.SerializationException과 같이 더 구체적인 예외를 잡는 것이 좋습니다. 이 패턴은 ApiResultCall.ktMealInfoStateDefinition.kt에서도 발견되므로, 프로젝트 전반에 걸쳐 일관되게 수정하는 것을 권장합니다.

Suggested change
} catch (e: Exception) {
Timber.e(e, "Failed to parse cafeteria JSON")
} catch (e: kotlinx.serialization.SerializationException) {
Timber.e(e, "Failed to parse cafeteria JSON")

fun provideJson(): Json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
isLenient = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

isLenient = true 옵션은 표준에 맞지 않는 JSON 형식도 파싱할 수 있게 해줍니다. 이 옵션은 서버 응답의 사소한 문법 오류로 인한 앱 크래시를 막을 수 있지만, 근본적인 데이터 문제를 감출 수도 있습니다. 백엔드에서 항상 유효한 JSON을 제공하는 것이 이상적이므로, 서버의 JSON 출력에 특별한 문제가 있는 경우가 아니라면 이 옵션을 제거하여 더 엄격한 JSON 파싱을 강제하는 것을 고려해 보세요.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 이 부분에 동의합니다! coerceInputValues랑 isLenient가 loose하게 처리해주는 덕분에 앱 크래시를 줄일 수는 있겠지만 이 부분은 직렬화 레이어가 아니라 서비스 레이어에서 처리하다가 발생한 오류를 우아하게 해결해주어 크래시가 없게끔 하는게 좋은 것 같아요.

서버에서 이상하게 보내주는지 우리쪽에선 오류가 안나니까 알 수 없는데, silent하게 오류가 발생하는게 가장 무서운거잖아요?

직렬화 규칙은 빡세게 잡되(위 옵션들 false로), 전역적으로 serialization 관련 오류를 처리하는 로직을 추가하는게 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

268e2a0 확인부탁합니다!

@HI-JIN2 HI-JIN2 requested a review from PeraSite February 10, 2026 05:58
@HI-JIN2 HI-JIN2 marked this pull request as ready for review February 10, 2026 05:58
@HI-JIN2
Copy link
Member Author

HI-JIN2 commented Feb 12, 2026

@PeraSite 추가로 32c65de 현재 Firebase Crashlytics에서 자동 수집되는 것은 비정상종료(강제종료)나 ANR만인걸로 알고 있어서, 강제 종료는 되지 않지만 exception이 발생하는 것에 대한 리포팅을 추가했습니다! (불필요하다고 생각되면 말씀해주세용)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enum 매핑을 kotlinx.serialization에 위임 [Library] 직렬화 라이브러리 Gson에서 kotlin-serialization으로 변경 고민

2 participants