Cheondi
개발 · 알고리즘·CS

JSON 저장과 상태 동기화

화면 설정을 저장하고 다시 불러오며 값보다 상태와 시점이 중요하다는 걸 배운 기록입니다.

  • #cs
  • #state
  • #json

화면에서 바꾼 설정을 JSON으로 저장하고 다음 실행 때 다시 읽는 기능은 처음엔 단순해 보였다. 한 화면에서만 시험할 때는 잘 동작했다. 그런데 계정을 바꾸면 이전 설정이 나타나고, 여러 화면 중 마지막에 닫은 화면 값이 다른 곳에 덮어써졌다. 파일 속 숫자는 맞는데 실행 결과는 틀렸다.

저장 함수만 계속 읽다가 원인이 데이터 구조보다 시점과 소유권에 있다는 걸 알았다. 누가 최신 값을 가지고 있고 언제 파일에 반영하는지가 정해져 있지 않았다.

단일 상태 소유자

설정 화면, 차트 화면, 전역 매니저가 각각 설정 객체를 복사해 들고 있었다. 한 화면에서 값을 바꿔도 다른 복사본은 이전 값이었다. 저장 요청을 가장 늦게 보낸 객체가 파일을 덮었다.

설정 저장소(SettingsStore) : 현재 설정의 원본
각 화면                    : 필요한 값을 구독해 표시
사용자 입력                : 저장소에 변경 요청
파일                       : 저장소의 지속 사본

화면끼리 값을 전달하기보다 하나의 저장소를 통해 읽고 변경하도록 정리했다.

읽기와 기본값 적용 순서

앱 시작 시 JSON을 읽은 뒤 화면을 초기화했는데, 일부 컴포넌트의 Start에서 기본값을 다시 넣었다. 로드 완료를 기다린 뒤 화면에 적용하고, 컴포넌트 초기화는 현재 저장소 값을 읽도록 했다.

async Task InitializeAsync()
{
    var saved = await settingsFile.LoadAsync();
    store.Replace(MergeWithDefaults(saved));
    scene.ShowWith(store.Current);
}

파일이 없을 때, 필드가 일부 빠졌을 때, JSON이 깨졌을 때의 기본값 처리도 같은 경계에 모았다.

계정별 상태와 공통 상태의 분리

언어와 테마는 기기 전체에 유지할 수 있지만 최근 선택 계좌나 차트 도구는 사용자에 따라 달랐다. 하나의 JSON에 전부 넣으면 계정 전환 시 이전 사용자의 값이 섞였다.

{
  "global": { "language": "ko", "theme": "dark" },
  "profiles": {
    "user-key-example": { "lastChart": "sample" }
  }
}

실제 사용자 식별값은 그대로 파일에 넣지 않고 로컬 키 정책에 맞춰 가렸다. 로그아웃 때 지울 값과 유지할 값도 이 경계로 결정했다.

상태 변경 순서의 복잡성

저장 문제를 만나면 파일 쓰기부터 의심했었다. 이번에는 메모리에서 언제 값이 만들어지고, 누가 바꾸며, 어느 시점에 지속되는지를 먼저 적었다. 가져오기 기능도 파일 복사가 아니라 현재 상태를 교체하는 작업이므로 화면에 알리는 순서가 필요했다.

JSON은 상태를 담는 형식일 뿐이었다. 일관성을 만드는 건 한 명의 주인, 명확한 초기화 순서, 계정 경계였다. 이 경험 뒤로 저장 기능을 만들 때 데이터 구조와 함께 상태의 시간표를 그리게 됐다.

추가 구현 기준

이 문제를 고친 뒤 관련 공식 자료를 찾아 읽으면서, 당시 코드에서 우연히 맞았던 부분과 규칙으로 남겨야 할 부분을 나눠 봤다.

Unity의 JsonUtility 문서를 다시 읽어 보니, JSON을 자유로운 key-value 저장소처럼 다루기보다 직렬화 가능한 클래스와 필드에 대응시키는 도구라는 점이 더 분명했다. 모르는 필드는 무시되고 빠진 필드는 생성된 객체의 기본값으로 남을 수 있다. 따라서 파싱에 성공했다는 사실과 지금 화면에서 써도 되는 데이터라는 판단은 같지 않다.

RFC 8259도 객체 멤버의 순서를 상호운용 가능한 계약으로 기대하지 않는다. 배열 순서가 의미를 가져도 객체 필드가 적힌 순서까지 로직에 사용하면 다른 생성기나 마이그레이션에서 쉽게 깨진다.

다음에 JSON 저장과 상태 동기화 같은 문제를 보면 아래 순서부터 확인하려고 한다.

  • schema version과 마이그레이션 경로
  • 필수 필드·기본값·범위 검증
  • 깨진 항목만 격리하는 단위
  • 이전 버전으로 되돌릴 수 있는지

형식이 올바른 JSON이어도 필수 식별자, 범위, 버전이 맞지 않으면 화면 모델로 넘기지 않는 편이 안전하다.

공식 참고 자료