I need a handy way to censore DTO fields/secrets when converting them into a json string (for logging).
Im using objectmapper and I heard about @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) but it completely hides the annotated field and it can be confusing because if I want to trace an error in the logs I might think that it was just null, therefore It would be better if the property's value was simply replaced with something like ***.
Defining a list of field names to replace values is also not suitable because i might censore fields that i dont want to. Also refactoring would complicate things too.
My idea is to annotate fields with a custom annotation and implement some json postprocessing with objectmapper but i couldnt find a way to do it. For example:
public record Person(String name, @Censored String secret) {
}
// and later
log.info(objectMapper.writeValueAsString(person));
The output should be
{"name":"John","secret":"***"}
Is there a way to configure ObjectMapper to censore fields that are annotated with my custom @Censored annotation?
Im also open to other suggestions to implement log censoring logic.
I tried to use @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) but it didnt achieve the result that i wanted.
The simplest way I can think to implement this via Jackson would be with a custom serializer:
public class CensoredSerializer<T> extends StdSerializer<T> {
public CensoredSerializer() {
this(null);
}
public CensoredSerializer(Class<T> t) {
super(t);
}
@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider)
throws IOException
{
gen.writeString("***");
}
}
You would use this like so:
public record Person(String name, @JsonSerialize(using = CensoredSerializer.class) String secret) {
}
If that's too verbose for you, you can use it as the basis for a custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = CensoredSerializer.class)
public @interface Censored {
}
This lets you use @Censored as a shorthand for @JsonSerialize(using = CensoredSerializer.class), like so:
public record Person(String name, @Censored String secret) {
}
As mentioned in the comments, it is also possible to do this by masking values in the logging config. I personally feel like there's some value in having it defined directly in the class instead, but your milage may vary!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With