Help us understand the problem. What is going on with this article?

Jackson に独自アノテーションへの処理を追加する

More than 3 years have passed since last update.

Jackson でシリアライズしてログに残すときに、認証トークンみたいなセンシティブなデータは潰したいし、それをアノテーションでお手軽に指定したいって思ったので調べた。


こんな感じで POOJO のフィールドに @Sensitive って付けたプロパティは適当な文字列に置換して欲しい:

public class SomeRequest {
    /** 隠す必要無いプロパティ */
    private final String userId;

    /** センシティブな情報 */
    private final String token;


置換処理の実装と、その実装が働くアノテーションへの関連付けと、それらを ObjectMapper に登録するモジュールの3つのクラスが必要。

public class SensitiveFieldMaskingSerializer extends StdSerializer<Object> {
    private static final long serialVersionUID = 3888199957574169748L;

    protected SensitiveFieldMaskingSerializer() {

    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        // 適当に置換する処理
        if (value instanceof Number) {
        } else if (value instanceof String) {
            gen.writeString("Hidden: Sensitive string");
        } else {

public class SensitiveFieldMaskingAnnotationIntrospector extends NopAnnotationIntrospector {
    private static final long serialVersionUID = -4171975975956047379L;

    public Object findSerializer(Annotated am) {
        if (am.hasAnnotation(Sensitive.class)) {
            return SensitiveFieldMaskingSerializer.class;
        return null;

↑を ObjectMapper に登録する処理クラス:
static class SensitiveFieldMaskingModule extends Module {
    public String getModuleName() {
        return getClass().getSimpleName();

    public Version version() {
        return Version.unknownVersion();

    public void setupModule(SetupContext context) {
        context.insertAnnotationIntrospector(new SensitiveFieldMaskingAnnotationIntrospector());


public final class Main {
    public static void main(String[] args) throws Exception {
        ObjectMapper logObjectMapper = new ObjectMapper()
                .registerModule(new SensitiveFieldMaskingModule());

        FooRequest fooRequest = new FooRequest("ログに残していいやつ", "ログに残すのダメなやつ");

        System.out.println("シリアライズ結果: " + logObjectMapper.writeValueAsString(fooRequest));
        // シリアライズ結果: {"userId":"ログに残していいやつ","token":"Hidden: Sensitive string"}

        System.out.println("Map へ変換結果:  " + logObjectMapper.convertValue(fooRequest, Map.class));
        // Map へ変換結果:  {userId=ログに残していいやつ, token=Hidden: Sensitive string}

両出力とも token が別の文字列に置換されているのがわかる。


上にもリンク張ったけど、一通り動くやつを gist に置いた。

余談だけど、今回のように通常用途とは違う ObjectMapper が必要な時は、他所で使ってる ObjectMapper を使いまわさず、新たにログを残すためのものとして用意したほうが無用なトラブルを避けられる。DI コンテナを使っている場合は面倒くさがらず名前付き Bean として定義しよう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away