How can I exclude fields with custom annotation in Spring Boot ObjectMapper - java

I have a need to have two different ObjectMapper in the application.
Pojo I am working with:
public class Student {
private String name;
private Integer age;
#HideThisField
private String grade;
// getters & setters..
}
One is the out of the box configuration based ObjectMapper as below:
#Bean("objectMapper")
public ObjectMapper getRegularObjectMapper() {
//With some configurations
return new ObjectMapper();
}
I need another ObjectMapper that while serializing ignores a few fields for all objects based on an annotation on a field.
#Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
// This is where i want to ignore the fields with #HideThisField
return new ObjectMapper();
}
Output of the two mappers:
objectMapper.writeValuesAsString(someStudent) prints:
{"name": ""student1", age: 10, "grade": "A+"}
customObjectMapper.writeValuesAsString(someStudent) prints:
{"name": ""student1", age: 10}

JacksonAnnotationIntrospector handles standard Jackson annotations. Overriding the hasIgnoreMarker method, you can make it work according to your own annotation.
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.*;
import java.lang.annotation.*;
public class StudentExample {
public static void main(String[] args) throws JsonProcessingException {
Student student = new Student();
student.setName("Student 1");
student.setAge(10);
student.setGrade("A+");
String st1 = getRegularObjectMapper().writeValueAsString(student);
String st2 = getCustomObjectMapper().writeValueAsString(student);
System.out.println(st1);
System.out.println(st2);
}
public static ObjectMapper getRegularObjectMapper() {
return new ObjectMapper();
}
public static ObjectMapper getCustomObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public boolean hasIgnoreMarker(AnnotatedMember m) {
if (_findAnnotation(m, HideThisField.class) != null)
return true;
return false;
}
});
return objectMapper;
}
}
class Student {
private String name;
private Integer age;
#HideThisField
private String grade;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGrade() {
return grade;
}
public void setGrade(String grade) {
this.grade = grade;
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#interface HideThisField {}
The console output is:
{"name":"Student 1","age":10,"grade":"A+"}
{"name":"Student 1","age":10}
getCustomObjectMapper() don't skips JsonIgnore annotations because you override the standard, if you want, you need to add this to the if block.

Related

How to map same #JsonAlias to different object fields using Jackson ObjectMapper

In my DTO class, there could be fields which might have same #JsonAlias values, but this seems not working with Jackson ObjectMapper.
It seems that ObjectMapper only works for the first occurrence of #JsonAlias and it don't work for the rest #JsonAlias which has same value. I have tried to create an example below for the reference and in that example, Person class has the two fields with name #JsonAlias value.
Code snippet:
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<String, String> values = new HashMap<>();
values.put("name", "TEST NAME");
Person person = new ObjectMapper().convertValue(values, Person.class);
System.out.println("name1: " + person.getName1());
System.out.println("name2: " + person.getName2());
}
static class Person {
#JsonAlias("name")
String name1;
#JsonAlias("name")
String name2;
public String getName1() {
return name1;
}
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name2) {
this.name2 = name2;
}
}
}
Output:
name1: TEST NAME
name2: null
In the above output. I was expecting the "TEST NAME" for the name2 variable in Person class.
Is there any configuration in Jackson ObjectMapper which will help me to achieve this?
Jackson version - jackson-databind-2.11.4.jar
Solution 1, #JsonGetter #JsonSetter point out to the custom getter\setter
We can use #JsonGetter("name") and #JsonSetter("name") annotations to map one JSON property value to the setter which has handling two properties
public class Person {
#JsonIgnore
private String name1;
#JsonIgnore
private String name2;
#JsonSetter("name")
public void setName(String name) {
this.name1 = name;
this.name2 = name;
}
#JsonGetter("name")
public String getName1() {
return name1;
}
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name2) {
this.name2 = name2;
}
}
Solution 2, #JsonAlias with the specific setter
We can ignore one field, the second mark with an alias, and add a custom setName setter that maps one JSON value to several fields.
public class Person {
#JsonAlias("name")
private String name1;
#JsonIgnore
private String name2;
public String getName1() {
return name1;
}
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name1) {
this.name1 = name1;
}
public void setName(String name) {
this.name1 = name;
this.name2 = name;
}
}
Or just add a custom setName setter to your original code
public class Person {
#JsonAlias("name")
String name1;
#JsonAlias("name")
String name2;
public String getName1() {
return name1;
}
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name1) {
this.name1 = name1;
}
public void setName(String name) {
this.name1 = name;
this.name2 = name;
}
}
Solution 3, #JsonProperty with the specific setter
The same like Solution 2, but instead use #JsonProperty("name")
public class Person {
#JsonProperty("name")
private String name1;
#JsonIgnore
private String name2;
public String getName1() {
return name1;
}
#JsonIgnore
public void setName1(String name1) {
this.name1 = name1;
}
public String getName2() {
return name2;
}
public void setName2(String name1) {
this.name1 = name1;
}
public void setName(String name) {
this.name1 = name;
this.name2 = name;
}
}
UPDATE:
In case you do not have the ability to modify DTO object, custom deserializer resolve the problem.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
public class PersonDeserializer extends StdDeserializer<Person> {
public PersonDeserializer() {
this(null);
}
public PersonDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Person person = new Person();
JsonNode node = jp.getCodec().readTree(jp);
String name = node.get("name").asText();
person.setName1(name);
person.setName2(name);
return person;
}
}
Apply deserializer to the DTO object:
#JsonDeserialize(using = PersonDeserializer.class)
public class Person {
private String name1;
private String name2;
//getters and setters
}
OR register custom deserializer on ObjectMapper
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());
mapper.registerModule(module);

#JsonProperty on class fields - getting duplicate fields in JSON

I have a class Student with some fields. I wanted to give custom names for the JSON fields that get returned.
public class Student {
#JsonProperty("name")
private String mName;
#JsonProperty("DOB")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date mBirthDate;
#JsonProperty("SSN")
private String mSocialSecurityNumber;
public Student() {
}
public Student(String mName, Date mBirthDate, String mSocialSecurityNumber) {
this.mName = mName;
this.mBirthDate = mBirthDate;
this.mSocialSecurityNumber = mSocialSecurityNumber;
}
public String getName() {
return mName;
}
public void setName(String mName) {
this.mName = mName;
}
public Date getBirthDate() {
return mBirthDate;
}
public void setBirthDate(Date mBirthDate) {
this.mBirthDate = mBirthDate;
}
public String getSocialSecurityNumber() {
return mSocialSecurityNumber;
}
public void setSocialSecurityNumber(String mSocialSecurityNumber) {
this.mSocialSecurityNumber = mSocialSecurityNumber;
}
}
My JSON output has both the raw field name (based on the getter name, e.g. getSocialSecurityNumber()), as well as the name specified in my #JsonProperty attributes.
It seems like if I move the #JsonProperty attributes to the getters, then I don't get the doubleup of the fields. Is there not a way I can do this by just having the annotations on the fields, which I feel is a little cleaner?
Configure the ObjectMapper to consider only the fields:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
In Spring Boot you can use Jackson2ObjectMapperBuilder to configure the ObjectMapper:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder() {
#Override
public void configure(ObjectMapper objectMapper) {
super.configure(objectMapper);
objectMapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
};
}

How to add #JsonIgnore annotated fields in serializing in Jackson ObectMapper

I need to add #JsonIgnore annotated fields while serializing an object by Jackson ObjectMapper. I know you may offer me to remove the #JsonIgnore annotation from my class, but I need they are ignorable in some part of my application. And in another part of my application I need to have those #JsonIgnore annotated fields in my json string.
You can define a SimpleBeanPropertyFilter and FilterProvider.
First annotate your class with custom filter like this:
#JsonFilter("firstFilter")
public class MyDtoWithFilter {
private String name;
private String anotherName;
private SecondDtoWithFilter dtoWith;
// get set ....
}
#JsonFilter("secondFilter")
public class SecondDtoWithFilter{
private long id;
private String secondName;
}
and this is how you will dynamically serialise your object.
ObjectMapper mapper = new ObjectMapper();
// Field that not to be serialised.
SimpleBeanPropertyFilter firstFilter = SimpleBeanPropertyFilter.serializeAllExcept("anotherName");
SimpleBeanPropertyFilter secondFilter = SimpleBeanPropertyFilter.serializeAllExcept("secondName");
FilterProvider filters = new SimpleFilterProvider().addFilter("firstFilter", firstFilter).addFilter("secondFilter", secondFilter);
MyDtoWithFilter dtoObject = new MyDtoWithFilter();
String dtoAsString = mapper.writer(filters).writeValueAsString(dtoObject);
I would suggest removing and re-adding them programmatically via reflection when your specific mapping is happening.
That suggests to me you have two different models with some common elements. I would reexamine your model.
public class MainProgram {
#JsonFilter("nameRemoveFilter")
public static class User{
private String name;
private String age;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("nameRemoveFilter",
SimpleBeanPropertyFilter.filterOutAllExcept("name","age"));
// and then serialize using that filter provider:
User user = new User();
try {
String json = mapper.writer(filters).writeValueAsString(user);
System.out.println(json);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Works for Latest version of Jackson after 2.0

How do I serialize a java bean using jackson and customize map key name?

I need to create a Map from java bean such that the key is prefixed with name of the java bean variable. I am using jackson for this. Example given below:
public class Address{
String city;
String state;
//setters and getters
}
Address address = new Address();
address.setCity("myCity");
address.setState("myState");
I am creating map using following:
ObjectMapper objectMapper = new ObjectMapper();
Map map = objectMapper.convertValue(address, HashMap.class);
Which gives me following output:
{"city":"myCity", "state":"myState"}
I need to add class variable name to the key as shown below:
{"address.city":"myCity", "address.state":"myState"}
How do I achieve that?
If you have jackson-annotations enabled:
public class Address{
#JsonProperty("address.city")
String city;
#JsonProperty("address.state")
String state;
//setters and getters
}
read more about it here: https://github.com/FasterXML/jackson-annotations
It is possible to customise bean serialization by registering a BeanSerializerModifier. This specifically supports renaming properties by applying a NameTransformer to each BeanPropertyWriter.
#Test
public void prepend_class_name_to_property_keys() throws Exception {
ObjectMapper mapper = new ObjectMapper();
Function<Class<?>, String> classPrefix = clazz -> clazz.getSimpleName().toLowerCase() + ".";
mapper.registerModule(new Module() {
#Override
public String getModuleName() {
return "Example";
}
#Override
public Version version() {
return Version.unknownVersion();
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
String prefix = classPrefix.apply(beanDesc.getBeanClass());
return beanProperties.stream().map(prop -> prop.rename(new NameTransformer() {
#Override
public String transform(String name) {
return prefix + name;
}
#Override
public String reverse(String transformed) {
return transformed.substring(prefix.length());
}
})).collect(toList());
}
});
}
});
assertThat(mapper.writeValueAsString(new Address("somewhere", "someplace")),
equivalentTo("{ 'address.line1' : 'somewhere', 'address.line2' : 'someplace'}"));
}
public static final class Address {
public final String line1;
public final String line2;
public Address(String line1, String line2) {
this.line1 = line1;
this.line2 = line2;
}
}

What is equivalent code settings for #JSonIgnore annotation?

I'm new to Java and Jackson and a lot of other technologies which I try to use, so I'd appreciate a detailed answer.
Is there a way to prevent one or more fields from being serialized using Jackson into a JSON String_like format, but without using any kind of JSON annotations?
Something like: mapper.getSerializationConfig().something(ignore("displayname")) if you know what I mean.
My object is an instance of a class that extends another one, and implements one interface also so on, so the fields come from an hierarchy of classes.
I need the JSON representation for that object but containing only certain fields, so I can send that JSON in a mock request through a POST method.
I'm using Jackson 2.2.2.
If you can't change your classes you can create new abstract class/interface with methods with #JsonIgnore annotation. In this class/interface you can define methods which ObjectMapper should skip during serialization/deserialization process.
Please, see below example:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonProgram {
public static void main(String[] args) throws IOException {
Person person = new Person();
person.setId(1L);
person.setName("Max");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Person.class, PersonMixIn.class);
System.out.println(objectMapper.writeValueAsString(person));
}
}
abstract class Entity {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
interface Namamble {
String getName();
}
class Person extends Entity implements Namamble {
private String name;
#Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
interface PersonMixIn {
#JsonIgnore
String getName();
}
EDIT - answer for the comments
You can create such mixin interface:
public static interface UserInformationMixIn {
#JsonIgnore
String getField3();
}
and configure ObjectMapper in this way:
objectMapper.addMixInAnnotations(UserInformation.class, UserInformationMixIn.class);
In version 2.5 method addMixInAnnotations was deprecated and addMixIn should be used:
objectMapper.addMixIn(UserInformation.class, UserInformationMixIn.class);
Full example source code:
import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonProgram {
public static void main(String[] args) throws IOException {
UserInformation userInformation = new UserInformation();
userInformation.setField3("field3");
userInformation.setField4("field4");
userInformation.setField5("field5");
User user = new User();
user.setField1(userInformation);
user.setField2("field2");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(UserInformation.class, UserInformationMixIn.class);
objectMapper.addMixIn(User.class, UserInformationMixIn.class);
System.out.println(objectMapper.writeValueAsString(user));
}
public static abstract class Someclass {
String field5;
public String getField5() {
return field5;
}
public void setField5(String field5) {
this.field5 = field5;
}
}
public static class UserInformation extends Someclass {
String field3;
String field4;
public String getField3() {
return field3;
}
public void setField3(String field3) {
this.field3 = field3;
}
public String getField4() {
return field4;
}
public void setField4(String field4) {
this.field4 = field4;
}
}
public static class User {
UserInformation field1;
String field2;
public UserInformation getField1() {
return field1;
}
public void setField1(UserInformation field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
}
public static interface UserInformationMixIn {
#JsonIgnore
String getField3();
#JsonIgnore
String getField2();
#JsonIgnore
String getField5();
}
}
Helpful link:
How can I tell jackson to ignore a property for which I don't have
control over the source code?

Categories

Resources