Puttzy
Puttzy

Reputation: 156

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.

Upvotes: 5

Views: 7780

Answers (2)

Hany Sakr
Hany Sakr

Reputation: 2949

I think you could achieve that dynamically by coding not annotations, inside your methods, you can set the proper Serializer and switch between them

(The code depends on your Jackson version)

ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new RedactedSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);

https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers

Upvotes: 2

zafrost
zafrost

Reputation: 576

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");
    }

}

Upvotes: 6

Related Questions