title: Mask json fields using jackson
body: <p>I am trying to mask sensitive data while serializing using jackson.</p>
  
  <p>I have tried using @JsonSerialize and a custom annotation @Mask .</p>
  
  <p><strong>Mask.java</strong></p>
  
  <pre class="lang-java prettyprint-override"><code>import java.lang.annotation.ElementType;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Target;
  
  @Retention(RetentionPolicy.RUNTIME)
  @Target(ElementType.FIELD)
  public @interface Mask {
    String value() default "XXX-DEFAULT MASK FORMAT-XXX";
  }
  
  </code></pre>
  
  <p><strong>Employee.java</strong></p>
  
  <pre class="lang-java prettyprint-override"><code>import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  
  import java.util.Map;
  
  public class Employee {
  
    @Mask(value = "*** The value of this attribute is masked for security reason ***")
    @JsonSerialize(using = MaskStringValueSerializer.class)
    protected String name;
  
    @Mask
    @JsonSerialize(using = MaskStringValueSerializer.class)
    protected String empId;
  
    @JsonSerialize(using = MaskMapStringValueSerializer.class)
    protected Map&lt;Category, String&gt; categoryMap;
  
    public Employee() {
    }
  
    public String getName() {
      return name;
    }
  
    public void setName(String name) {
      this.name = name;
    }
  
    public String getEmpId() {
      return empId;
    }
  
    public void setEmpId(String empId) {
      this.empId = empId;
    }
  
    public Map&lt;Category, String&gt; getCategoryMap() {
      return categoryMap;
    }
  
    public void setCategoryMap(Map&lt;Category, String&gt; categoryMap) {
      this.categoryMap = categoryMap;
    }
  }
  
  </code></pre>
  
  <p><strong>Category.java</strong></p>
  
  <pre class="lang-java prettyprint-override"><code>public enum Category {
    @Mask
    CATEGORY1,
    @Mask(value = "*** This value of this attribute is masked for security reason ***")
    CATEGORY2,
    CATEGORY3;
  }
  </code></pre>
  
  <p><strong>MaskMapStringValueSerializer.java</strong></p>
  
  <pre class="lang-java prettyprint-override"><code>import com.fasterxml.jackson.core.JsonGenerator;
  import com.fasterxml.jackson.databind.JsonSerializer;
  import com.fasterxml.jackson.databind.SerializerProvider;
  
  import java.io.IOException;
  import java.util.Map;
  
  public class MaskMapStringValueSerializer extends JsonSerializer&lt;Map&lt;Category, String&gt;&gt; {
  
    @Override
    public void serialize(Map&lt;Category, String&gt; map, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
      jsonGenerator.writeStartObject();
  
      for (Category key : map.keySet()) {
        Mask annot = null;
        try {
          annot = key.getClass().getField(key.name()).getAnnotation(Mask.class);
        } catch (NoSuchFieldException e) {
          e.printStackTrace();
        }
  
        if (annot != null) {
          jsonGenerator.writeStringField(((Category) key).name(), annot.value());
        } else {
          jsonGenerator.writeObjectField(((Category) key).name(), map.get(key));
        }
      }
  
      jsonGenerator.writeEndObject();
  
    }
  }
  </code></pre>
  
  <p><strong>MaskStringValueSerializer.java</strong></p>
  
  <pre class="lang-java prettyprint-override"><code>import com.fasterxml.jackson.core.JsonGenerator;
  import com.fasterxml.jackson.databind.BeanProperty;
  import com.fasterxml.jackson.databind.JsonMappingException;
  import com.fasterxml.jackson.databind.JsonSerializer;
  import com.fasterxml.jackson.databind.SerializerProvider;
  import com.fasterxml.jackson.databind.ser.ContextualSerializer;
  import com.fasterxml.jackson.databind.ser.std.StdSerializer;
  
  import java.io.IOException;
  
  public class MaskStringValueSerializer extends StdSerializer&lt;String&gt; implements ContextualSerializer {
    private Mask annot;
  
    public MaskStringValueSerializer() {
      super(String.class);
    }
  
    public MaskStringValueSerializer(Mask logMaskAnnotation) {
      super(String.class);
      this.annot = logMaskAnnotation;
    }
  
    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
      if (annot != null &amp;&amp; s != null &amp;&amp; !s.isEmpty()) {
        jsonGenerator.writeString(annot.value());
      } else {
        jsonGenerator.writeString(s);
      }
    }
  
    public JsonSerializer&lt;?&gt; createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
      Mask annot = null;
      if (beanProperty != null) {
        annot = beanProperty.getAnnotation(Mask.class);
      }
      return new MaskStringValueSerializer(annot);
  
    }
  }
  </code></pre>
  
  <p><strong>MaskValueTest.java</strong></p>
  
  <pre class="lang-java prettyprint-override"><code>import com.fasterxml.jackson.databind.ObjectMapper;
  
  import java.util.HashMap;
  import java.util.Map;
  
  public class MaskValueTest {
  
  
    public static void main(String args[]) throws Exception{
      Employee employee = new Employee();
  
      employee.setName("John Doe");
      employee.setEmpId("1234567890");
      Map&lt;Category, String&gt; catMap = new HashMap&lt;&gt;();
      catMap.put(Category.CATEGORY1, "CATEGORY1");
      catMap.put(Category.CATEGORY2, "CATEGORY2");
      catMap.put(Category.CATEGORY3, "CATEGORY3");
      employee.setCategoryMap(catMap);
  
      ObjectMapper mapper = new ObjectMapper();
      System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee));
    }
  }
  
  </code></pre>
  
  <p><strong>Output -</strong></p>
  
  <pre><code>{
    "name" : "*** The value of this attribute is masked for security reason ***",
    "empId" : "XXX-DEFAULT MASK FORMAT-XXX",
    "categoryMap" : {
      "CATEGORY1" : "XXX-DEFAULT MASK FORMAT-XXX",
      "CATEGORY2" : "*** The value of this attribute is masked for security reason ***",
      "CATEGORY3" : "CATEGORY3"
    }
  }
  </code></pre>
  
  <ul>
  <li>The result is as per expectation, however, this seems to be static masking.</li>
  <li>The intention was to mask only when needed, e.g. while printing in the logs where the all these sensitive data should be masked. </li>
  <li>If I have to send this json for document indexing where the values should be as it is, this implementation fails.</li>
  </ul>
  
  <p>I am looking for an Annotation based solution, where I can use 2 different instance of <code>ObjectMapper</code> initialized with JsonSerializers.</p>