Objectマッピングを簡単に行うことのできるライブラリModelMapperの転換ルール追加方法についてメモ書き程度に書いてみます。
きっかけ
JavaEEでServletを作っていた際に、POSTされてくるパラメータをBeanに自動で詰め込めないか?から調べて始め、ModelMapperに行きつきました。
発覚した問題
例えば、リクエストされたパラメータをEmployeeFormクラスにマッピングする場合、単純にHttpServletRequest#getParameterMap()を利用して取得したMapからModelMapper経由でコピーできれば良いなと、次のようなコードを書きました。
EmployeeForm employeeForm = modelMapper.map(request.getParameterMap(), EmployeeForm.class);
しかし、HttpServletRequest#getParameterMap()に含まれている値は常にString[](文字列の配列)で格納されていて、ModelMapperをデフォルトで使うとString[]をtoStringした値が入ってきてしまいます。
Converterを使ってみる
幸い、ModelMapperは転換ルールを追加する機能があり、String[]をルールに従ってStringに入れることができます。
// String配列→Stringに転換するルール
// 配列がnullでなければ、最初の要素を入れるようにする。
modelMapper = new ModelMapper();
Converter<String[], String> stringConverter = new AbstractConverter<String[], String>() {
@Override
protected String convert(String[] source) {
return (source != null && source.length > 0) ? source[0] : "";
}
};
// modelmapperに転換ルールを追加する。
modelMapper.addConverter(stringConverter);
※JavaEEではこれを@ApplicationScoped付きクラスの@PostConstructのメソッドに追加しておけばよいでしょう。
また、上記のルールは元情報がString[]で、受ける(コピーする)側がStringの同一名称の場合のみしか機能しませんので、チェックボックスの値などを取得した本当にListやString[]型で取得したいものについては、影響ありません。
反対に、受け取り側がStringのみのルール限定なので、Number型系(LongやBigDecimal等)、Date型などは、別途追加する必要があります。
調べていくとAbstractConverter<A, B>にはconvert(MappingContext<A, B>)というメソッドが用意されていて、うまく使って1つのルールでString[]を転換できると考えたのですが、次のような理由で断念しました。。。
できなかったこと
ModelMapperに元から含まれている転換ルールのほうが、先に適用されてしまうため、次のような汎用性の高いルールは、追加しても無視されて(デフォルトのルールのほうが優先されて)しまいました。
Converter<String[], Number> numConverter = new AbstractConverter<String[], Number>() {
@Override
public Number convert(MappingContext<String[], Number> context) {
String[] sources = context.getSource();
if (sources == null || sources.length <= 0) {
return null;
}
String source = sources[0];
Class<?> distType = context.getDestinationType();
if (BigDecimal.class.equals(distType)) {
return new BigDecimal(source);
} else if (Long.class.equals(distType)) {
return Long.valueOf(source);
} else if (Integer.class.equals(distType)) {
return Integer.valueOf(source);
}
return super.convert(context);
}
@Override
protected Number convert(String[] source) {
return null;
}
};
modelMapper.addConverter(numConverter);
コンパイルとしては通るのですが、いざこれでString[] -> BigDecimalを転換しようとすると、デフォルトの転換ルールNumberConverterのほうが優先されてしまい、実行時エラーとなってしまいます。
サンプルコード、その他
以下にお試しサンプルコードを配置しています。Gradleのbuildshipを使用してください。
以下参考にしたサイト
おわりに
イメージ画像1つもない味気ない記事ですが、世間にあんまりModelMapperの入門の次の使い方とかが見受けられなかったので、備忘録レベルで残しておきました。
サンプルでお分かりいただける通り、Converterは、作ったBeanクラスだけでなく、Stringなど元々内包されているクラスに対しても転換ルールを作成することができます。便利ですね。