Spring Bootアプリケーションにおいて、Jacksonを用いたオブジェクトからJSONへの変換処理中に、任意のロジックを挿入してフィールド値を動的に加工する方法について解説します。代表的なユースケースには、機密情報のマスキング(例:電話番号や身分証番号)、辞書値の変換(例:1→「男」)、null値の空文字置換などが含まれます。
1. BeanSerializerModifierによるオブジェクトレベルのグローバルインターセプション
この手法は、すべてのBeanオブジェクトのシリアライズ処理を一元的にフックできるため、アノテーション不要で柔軟な制御が可能です。
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.BeanSerializer;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.lang.reflect.Field;
@Configuration
public class GlobalSerializationConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SerializerFactory factory = mapper.getSerializerFactory()
.withSerializerModifier(new CustomBeanSerializerModifier());
mapper.setSerializerFactory(factory);
return mapper;
}
static class CustomBeanSerializerModifier extends BeanSerializerModifier {
@Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config,
BeanDescription desc,
JsonSerializer<?> serializer) {
if (serializer instanceof BeanSerializer) {
return new MaskingFieldSerializer((BeanSerializer) serializer);
}
return serializer;
}
}
static class MaskingFieldSerializer extends StdSerializer<Object> {
private final BeanSerializer original;
protected MaskingFieldSerializer(BeanSerializer src) {
super(Object.class);
this.original = src;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
Class<?> clazz = value.getClass();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
try {
String name = field.getName();
Object val = field.get(value);
if (val == null) {
gen.writeStringField(name, "");
continue;
}
String strVal = val.toString();
if (name.contains("phone") && strVal.length() == 11) {
gen.writeStringField(name, maskPhone(strVal));
continue;
}
if (name.contains("idCard") || name.contains("identity")) {
gen.writeStringField(name, maskIdCard(strVal));
continue;
}
gen.writeObjectField(name, val);
} catch (IllegalAccessException ignored) {
}
}
gen.writeEndObject();
}
private String maskPhone(String phone) {
return phone.substring(0, 3) + "****" + phone.substring(7);
}
private String maskIdCard(String id) {
return id.length() >= 18 ?
id.substring(0, 6) + "********" + id.substring(14) : id;
}
}
}
2. ContextualSerializerによるフィールド単位の動的処理
特定のフィールドにカスタムロジックを適用したい場合に使用します。フィールド名やアノテーション情報を取得でき、柔軟な条件分岐が可能です。
public class SensitiveDataSerializer extends JsonSerializer<String>
implements ContextualSerializer {
private String fieldName;
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty prop) {
this.fieldName = prop.getName();
return this;
}
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
if (value == null) {
gen.writeString("");
return;
}
if (fieldName != null && fieldName.contains("email")) {
gen.writeString(maskEmail(value));
} else {
gen.writeString(value);
}
}
private String maskEmail(String email) {
int atIndex = email.indexOf('@');
if (atIndex <= 0) return email;
String prefix = email.substring(0, 1) + "***";
return prefix + email.substring(atIndex);
}
}
3. 型ベースのグローバルシリアライザ
すべてのString型やInteger型など、特定のデータ型に対して共通処理を適用できます。
@Bean
public SimpleModule stringHandlingModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(String.class, new JsonSerializer<String>() {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value == null ? "" : value.trim());
}
});
return module;
}
4. アノテーション駆動型のインターセプション
AnnotationIntrospectorを拡張することで、独自アノテーションに基づいて自動的にシリアライザを適用できます。ただし実装が複雑なため、特殊な要件に限定して使用します。
比較と推奨用途
| 方式 | 適用スコープ | フィールド/アノテーション情報取得 | 実装難易度 | 推奨度 |
|---|---|---|---|---|
| BeanSerializerModifier | 全オブジェクト | 可(リフレクション経由) | 中 | ★★★★★ |
| ContextualSerializer | 個別フィールド | 可(直接取得) | 中 | ★★★★★ |
| JsonSerializer(型登録) | データ型全体 | 不可 | 低 | ★★★☆☆ |
| AnnotationIntrospector | アノテーション対象 | 可 | 高 | ★★★☆☆ |
一般的な要件では、BeanSerializerModifierによるグローバルフィールド処理と、ContextualSerializerによるフィールド単位の動的制御を組み合わせることで、ほとんどのシリアライズカスタマイズに対応可能です。