JSON 직렬화 타입 에러 해결 가이드 (중첩된 객체 이슈)

shin_stealer·2026년 1월 4일

에러 내용

_TypeError (type 'Address' is not a subtype of type 'Map<String, dynamic>' in type cast)

JSON 직렬화/역직렬화 과정에서 중첩된 객체가 제대로 변환되지 않을 때 발생함.

문제 원인

@JsonSerializable() 어노테이션에 explicitToJson: true 옵션이 없어서, 중첩된 객체들이 JSON으로 제대로 직렬화되지 않음.

예제: 중첩 객체 직렬화 문제

예제 모델 구조

// Address 모델
()
class Address {
  final String street;
  final String city;
  
  Address({required this.street, required this.city});
  
  factory Address.fromJson(Map<String, dynamic> json) => 
      _$AddressFromJson(json);
  Map<String, dynamic> toJson() => _$AddressToJson(this);
}

// User 모델 (Address를 포함)
()  // ❌ explicitToJson: true 없음
class User {
  final String name;
  final int age;
  final Address address;  // 중첩 객체
  
  User({required this.name, required this.age, required this.address});
  
  factory User.fromJson(Map<String, dynamic> json) => 
      _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

문제가 있는 생성된 코드

explicitToJson: true가 없을 때 생성되는 user.g.dart:

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
  'name': instance.name,
  'age': instance.age,
  'address': instance.address,  // ❌ Address 객체가 그대로 저장됨
};

문제 발생 시나리오

  1. 직렬화 (toJson)

    final user = User(
      name: '홍길동',
      age: 30,
      address: Address(street: '강남대로', city: '서울'),
    );
    
    final json = user.toJson();
    // 결과: {'name': '홍길동', 'age': 30, 'address': Address 객체}
    // ❌ address가 Map이 아니라 Address 객체 그대로
  2. 저장/전송

    // 로컬 스토리지나 API에 저장
    await storage.save(json);
    // 실제로는 Address 객체가 직렬화되지 않은 상태로 저장됨
  3. 역직렬화 (fromJson) - 에러 발생

    final savedJson = await storage.load();
    final user = User.fromJson(savedJson);
    // ❌ 에러 발생
    // Address.fromJson()이 Map<String, dynamic>을 기대하지만
    // Address 객체가 들어와서 타입 캐스팅 실패

에러 메시지

_TypeError (type 'Address' is not a subtype of type 'Map<String, dynamic>' in type cast)

해결 방법

1. explicitToJson: true 추가

수정 전:

()  // ❌
class User {
  final Address address;
  // ...
}

수정 후:

(explicitToJson: true)  // ✅
class User {
  final Address address;
  // ...
}

2. 수정 후 생성된 코드

explicitToJson: true를 추가한 후 재생성된 user.g.dart:

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
  'name': instance.name,
  'age': instance.age,
  'address': instance.address.toJson(),  // ✅ 제대로 직렬화됨
};

3. 올바른 동작

  1. 직렬화 (toJson)

    final user = User(
      name: '홍길동',
      age: 30,
      address: Address(street: '강남대로', city: '서울'),
    );
    
    final json = user.toJson();
    // 결과: {
    //   'name': '홍길동',
    //   'age': 30,
    //   'address': {'street': '강남대로', 'city': '서울'}  // ✅ Map으로 변환됨
    // }
  2. 역직렬화 (fromJson) - 정상 동작

    final savedJson = await storage.load();
    final user = User.fromJson(savedJson);
    // ✅ 정상 동작
    // address가 Map<String, dynamic>이므로 Address.fromJson()이 정상 실행됨

다양한 중첩 구조 예제

예제 1: 리스트에 중첩 객체

(explicitToJson: true)  // ✅ 필수
class Order {
  final List<Item> items;  // 리스트 안에 중첩 객체
  
  Order({required this.items});
  // ...
}

예제 2: 다중 중첩

(explicitToJson: true)  // ✅ 필수
class Company {
  final Address address;  // 1단계 중첩
  // ...
}

(explicitToJson: true)  // ✅ 필수
class Employee {
  final Company company;  // 2단계 중첩
  // ...
}

예제 3: 옵셔널 중첩 객체

(explicitToJson: true)  // ✅ 필수
class Profile {
  final Address? address;  // 옵셔널이어도 필요
  // ...
}

적용 규칙

중첩 객체가 있는 모든 모델에 explicitToJson: true 추가 필요:

  • ✅ 객체를 필드로 가지는 경우
  • ✅ 리스트에 객체가 포함된 경우
  • ✅ 옵셔널 객체를 가지는 경우
  • ✅ 다중 중첩 구조인 경우

코드 재생성

모델 수정 후 다음 명령어 실행:

flutter pub run build_runner build --delete-conflicting-outputs

주의사항

  1. 기존 데이터: 이미 잘못된 형식으로 저장된 데이터가 있다면 삭제하거나 마이그레이션 필요.

  2. 모든 중첩 레벨: 중첩이 여러 단계라면 각 단계의 모델 모두에 explicitToJson: true 추가 필요.

  3. 빌드 실행: 모델 수정 후 반드시 build_runner 실행하여 generated 파일 업데이트 필요.

요약

  • 문제: @JsonSerializable()만 사용하면 중첩 객체가 JSON으로 변환되지 않음
  • 해결: @JsonSerializable(explicitToJson: true) 추가
  • 결과: 중첩 객체가 자동으로 toJson()으로 변환되어 올바르게 직렬화됨
profile
I am a Blacksmith.

0개의 댓글