Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Jackson serializer on specific fields

I'm looking to have multiple jackson deserializers for the same object(s) all based on a custom annotation.

Ideally I'd have a single POJO like:

public class UserInfo {
   @Redacted    
   String ssn;

   String name;
}

Under "normal" conditions I want this object to be serialized the default way:

{"ssn":"123-45-6789", "name":"Bob Smith"}

but for logging purposes (for example) I want to redact the SSN so it doesn't get saved in our logs:

{"ssn":"xxx-xx-xxxx", "name":"Bob Smith"}

I've also looked into using @JsonSerialize and come up with:

public class UserInfo {

    @JsonSerialize(using = RedactedSerializer.class, as=String.class)
    String firstName;
    String lastName;

}

The problem with this is that it ALWAYS uses this rule. Can multiple @JsonSerializers be added and only the specified one be used within the runtime code?

I've also seen "views" but ideally I'd like to atleast show that the field was present on the request - even if I dont know the value.

like image 897
Puttzy Avatar asked Jan 30 '23 06:01

Puttzy


1 Answers

The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use @JsonView and custom serializer, something like:

class Views {
    public static class ShowSSN {}
}

private static class MyBean{
    @JsonSerialize(using = MyBeanSerializer.class)
    @JsonView(Views.ShowSSN.class)
    String ssn;
    //getter setter constructor
}

private class MyBeanSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen,
                          SerializerProvider serializers) throws IOException {
        Class<?> jsonView =  serializers.getActiveView();
        if (jsonView == Views.ShowSSN.class) 
            gen.writeString(value); // your custom serialization code here
        else 
            gen.writeString("xxx-xx-xxxx");
    }
} 

And use it like:

public static void main(String[] args) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    MyBean bean = new MyBean("123-45-6789");

    System.out.println(mapper.writerWithView(Views.ShowSSN.class)
                             .writeValueAsString(bean));
    // results in {"ssn":"123-45-6789"}

    System.out.println(mapper.writeValueAsString(bean));
    // results in {"ssn":"xxx-xx-xxxx"}
}

Also for example in spring it would be really easy to use

@Controller
public class MyController {
    @GetMapping("/withView")       // results in {"ssn":"123-45-6789"}
    @JsonView(Views.ShowSSN.class)
    public @ResponseBody MyBean withJsonView() {
        return new MyBean("123-45-6789");
    }

    @GetMapping("/withoutView")    // results in {"ssn":"xxx-xx-xxxx"}
    public @ResponseBody MyBean withoutJsonView() {
        return new MyBean("123-45-6789");
    }

}
like image 178
zafrost Avatar answered Feb 02 '23 12:02

zafrost