type cast 는 한글로 '형 변환'으로 해석할 수 있다.

위키백과에 따르면 형변환을 암시적 형변환과 명시적 형변환으로 나눌 수 있는데, 이번 글에서는 명시적 형변환을 다룬다.

그리고 명시적 형변환 중에서도 List 에 대한 형변환을 다루고자 한다.


서버로부터 응답 받기

getFoo 함수는 서버에서 데이터를 받아오는 역할을 하고 dio 플러그인을 사용해서 요청을 보낸다.

Flutter 에서 dio 플러그인을 이용해 결과를 받으면 Response 타입으로 받게되고 Response 클래스 안에는 다양한 속성들이 있다. 서버에서 보낸 내용을 확인하려면 이 중 data 키로 접근할 수 있다.


Future<dynamic> getFoo() {
  Dio dio = Dio();

  try {
    Response<dynamic> result = await dio.get(
      'http://localhost:8080/foo',
    );

    return result;
  } on DioError catch (err) {
    return err;
  }
}

서버로부터 받은 응답 형태

위 result 의 data 키가 갖는 값이 아래와 같은 모습이라고 가정한다.

한 가지 주의할 점은 위에서 언급한 Response 의 data 와 아래의 data 는 다르다.


{
  "isError": false,
  "message": "success",
  "statusCode": 200,
  "data": {
    "foo": [1, 2, 3, 4, 5],
  }
}

dynamic

위 data 키의 값으로 들어올 타입이 다양할 수 있다.

응답을 처리하는 공통 클래스가 있다고 하면 data 를 dynamic 으로 선언해서 받을 수 있다.

Response 의 data 가 서버에 보내는 요청마다 각기 다른 타입으로 들어올 수 있어서 특정한 하나의 타입으로 지정하기 어렵기 때문에 dynamic 으로 선언할 수 있다.


class ResponseHelper {
  Response<dynamic> response;
  dynamic data;

  ResponseHelper(res) {
    response = res;
  }

  void getData() {
    // data 가 2개 있는데
    // 앞 data 는 Response 클래스의 data 키
    // 뒤 data 는 서버 응답 중 data 키
    data = response!.data['data'];
  }
}

dynamic 을 List<int> 로 변환

문제는 이 data 를 dynamic 이 아닌 구체적인 타입으로 사용하고 싶을 때다.

data 의 {"foo": [1, 2, 3, 4, 5]} 를 구체적인 타입으로 변경하면 Map<String, List<int>> 다.


Response<dynamic> res = await getFoo();

ResponseHelper responseHelper = ResponseHelper(result);

// {"foo": [1, 2, 3, 4, 5]} 의 타입을
// 기존 ResponseHelper 에 선언되어 있는 dynamic 에서
// Map<String, List<int>> 로 변경하여 사용하려고 하지만 다음과 같은 에러가 발생한다
//
// Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, List<int>>'
Map<String, List<int>> data = responseHelper.data;

위의 방식은 에러가 발생하기 때문에 아래와 같이 List 에서 dynamic 으로 변경하면 에러가 발생하지 않는다.


Response<dynamic> res = await getFoo();

ResponseHelper responseHelper = ResponseHelper(result);

Map<String, dynamic> data = responseHelper.data;

그러나 data 를 dart 클래스로 변환할 수 없다.

Map 타입의 data 를 MapToInstance 클래스를 통해 변환하려고 하지만 에러가 발생한다.


class MapToInstance {
  List<int> foo;

  MapToInstance(this.foo);

  MapToInstance.fromMap(Map<String, dynamic> mapItem)
    : foo = mapItem['foo'];
}

MapToInstance.fromMap 의 파라미터인 mapItem 은 Map<String, dynamic> 로 선언되어 있어서 List 로 선언한 foo 에 대입할 수 없다.


Response<dynamic> res = await getFoo();

ResponseHelper responseHelper = ResponseHelper(result);

Map<String, dynamic> data = responseHelper.data;

// Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<int>'
MapToInstance fooInstance = MapToInstance.fromMap(data);

파라미터의 dynamic 을 List 로 선언하더라도 fromMap 함수의 인자로 넣어주는 data 가 Map<String, dynamic> 이라 다음과 같은 에러가 발생한다.


The argument type 'Map<String, dynamic>' can't be assigned to the parameter type 'Map<String, List<int>>

dynamic 타입으로 지정되어 있는 [1, 2, 3, 4, 5] 데이터를 List< int > 타입으로 변경하고 싶다면 어떻게 해야 할까?

먼저 dynamic 타입인 mapItem['foo'] 를 List 로 type cast 해본다.

물론 안 된다...


class MapToInstance {
  List<int> foo;

  MapToInstance(this.foo);

  MapToInstance.fromMap(Map<String, dynamic> mapItem)
    // 에러 발생
    // Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<int>' in type cast
    : foo = mapItem['foo'] as List<int>;
}

List 내의 요소들부터 하나씩 변경해줘야 한다

stackoverflow 글을 보면 List 의 경우 통채로 타입을 바꿀 수는 없고 List 내의 원소들부터 하나씩 바꿔야 한다.

다음과 같은 방식으로 형변환 할 수 있다.


class MapToInstance {
  List<int> foo;

  MapToInstance(this.foo);

  MapToInstance.fromMap(Map<String, dynamic> mapItem)
    // mapItem['foo'] 를 List 로 형변환 하고
    // mapItem['foo'] 안에 있는 원소들을 map 함수로 반복 하면서
    // 원소들을 int 로 형변환 한다.
    // 그리고 map 은 Iterable 타입으로 반환되기 때문에
    // toList() 를 통해 List 로 형변환한다.
    : foo = (mapItem['foo'] as List).map((e) => (e as int)).toList();
}

mapItem['foo'] 를 List 로 형변환하지 않고 원소들만 int 로 형변환 하면 어떻게 될까?

이것도 안 된다...


class MapToInstance {
  List<int> foo;

  MapToInstance(this.foo);

  MapToInstance.fromMap(Map<String, dynamic> mapItem)
    // Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<int>'
    : foo = mapItem['foo'].map((e) => (e as int)).toList();
}

List 로 형변환하되 원소들을 형변환 하지 않는건 어떨까?

이것도 안 된다...

아예 Android Studio 에서 빨간줄로 표시된다.


class MapToInstance {
  List<int> foo;

  MapToInstance(this.foo);

  MapToInstance.fromMap(Map<String, dynamic> mapItem)
    // The initializer type 'List<dynamic>' can't be assigned to the field type 'List<int>'
    : foo = (mapItem['foo'] as List).map((e) => e).toList();
}

이차원 List

foo 의 값이 일차원 List 가 아니라 이차원 List 면 어떻게 형변환 해야 할까?

foo 가 [[1, 2, 3, 4, 5]] 라고 가정하면 List, List, int 각각 형변환을 해줘야한다.


class MapToInstance {
  List<List<int>> foo;

  MapToInstance(this.foo);

  MapToInstance.fromMap(Map<String, dynamic> mapItem)
    // mapItem['foo'] 를 List 로 형변환하고 안쪽 List 를 map 으로 반복하면서 안쪽 List 를 List 로 형변환한다. 
    // 그리고 안쪽 List 의 원소들을 map 으로 반복하면서 int 로 형변환한다.
    // map 을 통해 int 로 형변환한 Iterable 타입을 toList() 를 통해 List 로 형변환하고, 
    // 다시 바깥에서 map 을 통해 List 로 형변환한 Iterable 타입을 toList() 를 통해 List 로 형변환한다.
    : foo = (mapItem['foo'] as List).map((innerList) => (innerList as List).map((e) => (e as int)).toList()).toList();
}

tistory 에서 dart 를 지원하지 않는 관계로 코드 하이라이팅이 되지 않는다. 구글링을 해보니 tistory 에서 dart 언어에 대한 코드 하이라이팅을 적용할 수 있는 방법이 있어서 추후에 적용해보겠다.

참고

https://ko.wikipedia.org/wiki/%ED%98%95_%EB%B3%80%ED%99%98

https://stackoverflow.com/questions/60105956/how-to-cast-dynamic-to-liststring

+ Recent posts