自定义 Jackson ObjectMapper 以读取自定义注释并屏蔽注释字段

2022-01-13 00:00:00 json annotations java jackson

我有一个要求,我创建了一个自定义注释@MaskSensitiveData.我注释敏感字段.喜欢

I have a requirement where I have created a Custom Annotation @MaskSensitiveData. I annotate sensitive fields. like

class MyBean {
    String userName;
    @MaskSensitiveData
    String cardNumber;
    String abc;
    String xyz;
}

ObjectMapper mapper = new ObjectMapper();
    String json = null;
    AnnotationIntrospector primary = new JaxbAnnotationIntrospector();
    AnnotationIntrospector secondary = new JacksonAnnotationIntrospector();
    AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary, secondary);
    mapper.setAnnotationIntrospector(pair);
    try {
        json = mapper.writeValueAsString(obj);
        /*
         * if(json != null ) { json = getLoggableString(json); }
         */
    } catch (Exception e) {
        return "Unable to convert to Json object:" + obj.toString() + " Message: " + e.getMessage();

    }

我正在使用 Jackson ObjectMapper 将 objct 转换为 Json 之类的.我需要自定义 Object Mapper 以在返回 json 中屏蔽 cardNumber 字段.请提出更好的方法.

I am using Jackson ObjectMapper to convert objct to Json like. I need to customize Object Mapper to mask cardNumber field in return json. Please suggest a better way.

推荐答案

package stackoverflow;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import org.hamcrest.Matchers;
import org.junit.Test;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

public class MaskingAnnotationExample {
    // Define @custom Annotation
    // assumed to be used by String type field for this example
    @Retention(RetentionPolicy.RUNTIME)
    static @interface MaskSensitiveData {
    }

    public static class MyBean {
        private String userName;

        @MaskSensitiveData
        private String cardNumber;

        public MyBean() {
        }

        public String getCardNumber() {
            return cardNumber;
        }

        public String getUserName() {
            return userName;
        }

        public void setUserName(String userName) {
            this.userName = userName;
        }

        public void setCardNumber(String cardNumber) {
            this.cardNumber = cardNumber;
        }
    }

    // map the Serializer/Deserializer based on custom annotation
    public static class MaskSensitiveDataAnnotationIntrospector extends NopAnnotationIntrospector {
        private static final long serialVersionUID = 1L;

        @Override
        public Object findSerializer(Annotated am) {
            MaskSensitiveData annotation = am.getAnnotation(MaskSensitiveData.class);
            if (annotation != null) {
                return MaskSensitiveDataSerializer.class;
            }

            return null;
        }

        @Override
        public Object findDeserializer(Annotated am) {
            MaskSensitiveData annotation = am.getAnnotation(MaskSensitiveData.class);
            if (annotation != null) {
                return MaskSensitiveDataDeserializer.class;
            }

            return null;
        }
    }

    public static class MaskSensitiveDataDeserializer extends StdDeserializer<String> {
        private static final long serialVersionUID = 1L;

        public MaskSensitiveDataDeserializer() {
            super(String.class);
        }

        @Override
        public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            // un-masking logic here. in our example we are removing "MASK"
            // string
            String s = p.getValueAsString();
            return s.substring(4);
        }
    }

    public static class MaskSensitiveDataSerializer extends StdSerializer<String> {
        private static final long serialVersionUID = 1L;

        public MaskSensitiveDataSerializer() {
            super(String.class);
        }

        @Override
        public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            // Masking data; for our example we are adding 'MASK'
            gen.writeString("MASK" + value);
        }
    }

    @Test
    public void demo() throws Exception {
        ObjectMapper mapper = new ObjectMapper();

        AnnotationIntrospector sis = mapper.getSerializationConfig().getAnnotationIntrospector();
        AnnotationIntrospector dis = mapper.getDeserializationConfig().getAnnotationIntrospector();

        AnnotationIntrospector is1 = AnnotationIntrospectorPair.pair(sis, new MaskSensitiveDataAnnotationIntrospector());
        AnnotationIntrospector is2 = AnnotationIntrospectorPair.pair(dis, new MaskSensitiveDataAnnotationIntrospector());

        mapper.setAnnotationIntrospectors(is1, is2);

        MyBean obj = new MyBean();
        obj.setUserName("Saurabh Bhardwaj");
        obj.setCardNumber("4455-7788-9999-7777");
        String json = mapper.writeValueAsString(obj);

        String expectedJson = "{"userName":"Saurabh Bhardwaj","cardNumber":"MASK4455-7788-9999-7777"}";
        assertThat(json, Matchers.is(expectedJson));

        MyBean cloned = mapper.readValue(json, MyBean.class);
        assertThat(cloned.getCardNumber(), is(obj.getCardNumber()));
    }
}

希望这会有所帮助.

相关文章