はじめに
APIレスポンスのHeaderのContent-Typeがtext/html
形式で、レスポンス内容がJSON形式だったときにretrofit(JsonSerializable)の自動パースを適用したかった時の対処法のメモです。
retrofitではJsonSerializableと組み合わせることでパースしたクラスを返すFutureクラスを自動生成してくれます。
問題の状況
APIのレスポンスがJson形式のテキストなのにも関わらず、仕様によりContent-Typeがtext/html
でした。
- レスポンス
{
data:{
name : "hoge",
age : 20
}
}
Freezedを用いて上記のレスポンスに対するモデルおよびAPIサービスクラスを作成していたのですが、Content-Typeがjson/aplication
でなかったため、自動パースが行えませんでした。
- モデル
@freezed
class UserModel with _$UserModel {
const factory UserModel({
required String name,
required int age,
}) = _UserModel;
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
}
- APIサービスクラス
@RestApi(baseUrl: 'http://...')
abstract class ApiService {
factory ApiService(Dio dio) = _ApiService;
@GET('/user')
Future<UserModel> getUser();
}
解決方法
dioのinterceptor
でJsonデコードを行う
InterceptorWrapper
クラスにonResponse
プロパティがあるので、そこでretrofitに渡されるレスポンスを変換します。
statusコードが200番の時にJsonDecodeを行い、handler.next
でResponseを渡します。
final apiServiceProvider = Provider((ref) {
final dio = Dio();
// content-typeをtext/htmlに設定
dio.options.headers['Content-Type'] = 'text/html; charset=UTF-8';
// text/html形式のレスポンスをjson形式に変換
dio.interceptors.add(
InterceptorsWrapper(
onResponse: (response, handler) {
if (response.statusCode == 200) {
final data = response.data.toString();
try {
final jsonData = jsonDecode(data);
handler.next(
Response(
data: jsonData,
requestOptions: response.requestOptions,
statusCode: response.statusCode,
statusMessage: response.statusMessage,
isRedirect: response.isRedirect,
redirects: response.redirects,
extra: response.extra,
headers: response.headers,
),
);
} on Exception catch (_) {
// エラーが発生した場合はそのまま返す
handler.next(response);
}
}
},
),
);
return apiService(dio);
});
終わりに
Json形式なのにtextという特殊な状況ですがこのような形で対処できました。
もっとスマートな方法あればコメント等お願いします!