Hiding protected fields from Jackson serialization - java

For some reason I am not able to hide protected fields (without setter), via ObjectMapper configuration, from being serialized to a JSON string.
My POJO:
public class Item {
protected String sn;
private String name;
public Item(){
sn = "43254667";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSn() {
return sn;
}
}
My mapper:
mapper.setVisibility(PropertyAccessor.SETTER, Visibility.PUBLIC_ONLY);
mapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.NONE);
The output is:
{
"sn" : "43254667",
"name" : "abc"
}
UPDATE: I cannot modify the Item class, hence I cannot use annotations.

Use #JsonIgnore
You could annotate the field or method with #JsonIgnore.
It's a marker annotation that indicates that the annotated method or field is to be ignored by introspection-based serialization and deserialization functionality.
Use as following:
public class Item {
#JsonIgnore
protected String sn;
...
}
Or as following:
public class Item {
...
#JsonIgnore
public String getSn() {
return sn;
}
}
Use #JsonIgnore with mix-ins
Based on your comment, you could use mix-in annotations when modifying the classes is not an option, as described in this answer.
You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment statically defined ones.
First, define a mix-in annotation interface (class would do as well):
public interface ItemMixIn {
#JsonIgnore
String getSn();
}
Then configure your ObjectMapper to use the defined interface as a mix-in for your POJO:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Item.class, ItemMixIn.class);
For extra details, check the documentation.
Use a BeanSerializerModifier
Based on your comment, you may consider a BeanSerializerModifier, as following:
public class CustomSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
// In this method you can add, remove or replace any of passed properties
return beanProperties;
}
}
Then register the custom serializer as a module in your ObjectMapper.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new CustomSerializerModifier());
}
});

You've instructed your mapper to serialize public getters:
mapper.setVisibility(PropertyAccessor.GETTER, Visibility.PUBLIC_ONLY);
That's why Jackson will serialize the sn field (You have a public getter here in the end).
To get rid of the serialized sn field, simply annotate your getter with #JsonIgnore:
public class Item{
protected String sn;
private String name;
public Item(){
sn = "43254667";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public String getSn() {
return sn;
}
}
If you cannot annotate your class, you can always write a custom serializer for your POJO or use Mixins

Related

#JsonCreator not working for #RequestParams in Spring MVC

#JsonCreator not deserialising #RequestParam of type enum
I am working on a Spring application where the controller is receiving list of request params that Spring is binding to a wrapper object. One of the params is of type enum where I am receiving it by some property name.
Endpoint example: http://localhost:8080/searchCustomers?lastName=Smith&country=Netherlands
#RequestMapping(value = "/search/customers", method = RequestMethod.GET)
public CustomerList searchCustomers(#Valid CustomerSearchCriteria searchCriteria)
public class CustomerSearchCriteria {
private String lastName;
private Country country;
}
public enum Country {
GB("United Kingdom"),
NL("Netherlands")
private String countryName;
Country(String countryName) {
countryName = countryName;
}
#JsonCreator
public static Country fromCountryName(String countryName) {
for(Country country : Country.values()) {
if(country.getCountryName().equalsIgnoreCase(countryName)) {
return country;
}
}
return null;
}
#JsonValue
public String toCountryName() {
return countryName;
}
}
I am expecting Spring to bind enum Country.Netherlands to CustomerSearchCriteria.country but its not doing it so. I tried similar annotations with #RequestBody and that works fine, so I am guessing he Spring binding is ignoring #JsonCreator.
Any helpful tips would be appreciated.
Here is the code that is behind #Mithat Konuk comment.
Put in your controller something like:
import java.beans.PropertyEditorSupport;
#RestController
public class CountryController {
// your controller methods
// ...
public class CountryConverter extends PropertyEditorSupport {
public void setAsText(final String text) throws IllegalArgumentException {
setValue(Country.fromCountryName(text));
}
}
#InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(Country.class, new CountryConverter());
}
}
More information ca be found here: https://www.devglan.com/spring-boot/enums-as-request-parameters-in-spring-boot-rest.

SpringBoot deserialization without default constructor

During the last hours I read many StackOverflow questions and articles, but none of the advices helped. What I tried:
Add #JsonCreator and #JsonProperty to both Person and Employee classes (link)
Add #JsonDeserialize(using = EmployeeDeserialize.class) to Employee class (link)
Add Lombok as dependency, set lombok.anyConstructor.addConstructorProperties=true and add #Data / #Value annotation to both Person and Employee classes (link)
Finally, I did the deserialization manually:
String json = "{\"name\": \"Unknown\",\"email\": \"please#work.now\",\"salary\":1}";
ObjectMapper objectMapper = new ObjectMapper();
Employee employee = objectMapper.readValue(json, Employee.class);
In this way I could deserialize the JSON, but as soon as I started my spring-boot-starter-web project and called
http://localhost:8080/print?name=unknown&email=please#work.now&salary=1
I got the good old BeanInstantiationException
Failed to instantiate [Employee]: No default constructor found
I run out of ideas. Does anybod know why this worked when I did the deserialization manually? And why it throws exception when I call the REST endpoint?
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#RestController
public class EmployeeController {
#GetMapping("print")
public void print(Employee employee) {
System.out.println(employee);
}
}
public class Person {
private final String name;
#JsonCreator
public Person(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Employee extends Person {
private final String email;
private final int salary;
#JsonCreator
public Employee(
#JsonProperty("name") String name,
#JsonProperty("email") String email,
#JsonProperty("salary") int salary) {
super(name);
this.email = email;
this.salary = salary;
}
public String getEmail() {
return email;
}
public int getSalary() {
return salary;
}
}
You’re implementing JSON deserialisation, yet you’re not using any JSON.
Change to use #PostMapping on your controller method and use something like Postman or cURL to send the JSON to your /print endpoint.

Jackson - deserialize using Builder without annotation

Is it possible to use Jackson to deserialize a value class (final, no setters) that only has an all args constructor and a Builder? I can't use the JsonDeserialize and JsonPOJOBuilder since I am trying to deserialize a model defined in a client library, so I cannot add the annotations. Can I specify the builder to use another way?
You can try using MixIn.
I have created one sample for your use case:
Original class:
final class Sample {
final int id;
Sample(int id) {
this.id = id;
}
}
MixIn (provide non-args constructor with same args):
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
abstract class SampleMixin {
#JsonCreator
public SampleMixin(#JsonProperty("id") int id) {
}
}
Deserilaization:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Sample.class, SampleMixin.class);
Sample sample = mapper.readValue(json, Sample.class);
You can. Builder must meet certain requirements. For instance its methods for fields must have certain prefix, like "with" or "set".
Here is DTO class and its builder without any jackson annotations:
public class DtoBuilderWithFinalFieldsWithoutJackson {
public final String stringValue;
private DtoBuilderWithFinalFieldsWithoutJackson(final String stringValue){
this.stringValue = stringValue;
}
public static Builder builder(){
return new Builder();
}
public static class Builder {
private String stringValue;
public Builder withStringValue(String stringValue) {
this.stringValue = stringValue;
return this;
}
public DtoBuilderWithFinalFieldsWithoutJackson build() {
return new DtoBuilderWithFinalFieldsWithoutJackson(stringValue);
}
}
}
Without any additional effort with a default custom object you can serialize this dto object. You are responsible for instance creation. Jackson only needs to access fields. In our case this is a public field.
In case DTO is used for deserialization, you need to customize custom object:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector(){
#Override
public Class<?> findPOJOBuilder(AnnotatedClass ac) {
//set analogue of: #JsonDeserialize(builder = DtoBuilderWithFinalFieldsWithoutJackson.Builder.class)
if (DtoBuilderWithFinalFieldsWithoutJackson.class.equals(ac.getRawType())) {
return DtoBuilderWithFinalFieldsWithoutJackson.Builder.class;
} else {
return super.findPOJOBuilder(ac);
}
}
#Override
public JsonPOJOBuilder.Value findPOJOBuilderConfig(AnnotatedClass ac) {
if (DtoBuilderWithFinalFieldsWithoutJackson.class.equals(ac.getRawType())) {
//both build and with - are default in this case:
//set analogue of #JsonPOJOBuilder(buildMethodName = "build", withPrefix = "with")
return new JsonPOJOBuilder.Value("build", "with");
} else {
return super.findPOJOBuilderConfig(ac);
}
}
});
and use this customized CustomObject in your implementations. Here is a test and full example can be found here: DtoBuilderWithFinalFieldsWithoutJackson test

How to deserialize JSON Array contained an abstract class without modifying a parent class?

I'm trying to deserialize JSON Array, which is persisted into my MongoDB, to a Java object by using Jackson. I found many tutorials mentioned to handle this polymorphism by adding:
#JsonTypeInfo(use=Id.CLASS,property="_class")
to a Super-class. However, in my case, I can't be able to modify the Super-class. So, are there some solutions to solve it without modifying the Super-class? Here is my code:
public class User {
#JsonProperty("_id")
private String id;
private List<Identity> identities; // <-- My List contains objects of an abstract class; Identity
public User(){
identities = new ArrayList<Identity>();
}
public static Iterable<User> findAllUsers(){
return users().find().as(User.class); // Always give me the errors
}
/*More code*/
}
It always give me the error - Can not construct instance of securesocial.core.Identity, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information.
You can use #JsonDeserilize annotation to bind a concrete implementation class to an abstract class. If you cannot modify your abstract class you can use the Jackson Mix-in annotations to tell Jackson how to find the implementation class.
Here is an example:
public class JacksonAbstract {
public static class User {
private final String id;
private final List<Identity> identities;
#JsonCreator
public User(#JsonProperty("_id") String id, #JsonProperty("identities") List<Identity> identities) {
this.id = id;
this.identities = identities;
}
#JsonProperty("_id")
public String getId() {
return id;
}
public List<Identity> getIdentities() {
return identities;
}
}
public static abstract class Identity {
public abstract String getField();
}
#JsonDeserialize(as = IdentityImpl.class)
public static abstract class IdentityMixIn {
}
public static class IdentityImpl extends Identity {
private final String field;
public IdentityImpl(#JsonProperty("field") String field) {
this.field = field;
}
#Override
public String getField() {
return field;
}
}
public static void main(String[] args) throws IOException {
User u = new User("myId", Collections.<Identity>singletonList(new IdentityImpl("myField")));
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Identity.class, IdentityMixIn.class);
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(u);
System.out.println(json);
System.out.println(mapper.readValue(json, User.class));
}
}

Serialize using Jackson ObjectMapper

I am trying to serialize the below ArrayList of GAccount objects using Jackson library with following code:
List<Gaccount> gAccounts;
ObjectMapper mapper=new ObjectMapper();
json=mapper.writeValueAsString(gAccounts);
However, I have noticed that only Id and Name fields are serialized but not properties. Sorry, but I am new to Jackson library. Do I have to manually serialize that field ?
package in.co.madhur.ganalyticsdashclock;
import java.util.ArrayList;
import java.util.List;
public class GAccount
{
private String Id;
private String Name;
private List<GProperty> properties=new ArrayList<GProperty>();
public GAccount(String Id, String Name)
{
this.Id=Id;
this.Name=Name;
}
public String getName()
{
return Name;
}
public void setName(String name)
{
Name = name;
}
public String getId()
{
return Id;
}
public void setId(String id)
{
Id = id;
}
List<GProperty> getProperties()
{
return properties;
}
void setProperties(List<GProperty> properties)
{
this.properties = properties;
}
#Override
public String toString()
{
return Name;
}
}
The default visibility is to use all public getter methods and all public properties. If you make the getter this:
public List<GProperty> getProperties()
it should work.
You could also change the auto-detection defaults, but it's overkill here. See http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html for more info.
I am using jackson 2.9.0. The default visibility is always 'false' to all the members. In this case, we alway need to use a different visibility, otherwise the result json string will be empty. Here is the code extracted from JsonAutoDetect
public boolean isVisible(Member m) {
switch(this) {
case ANY:
return true;
...
case PUBLIC_ONLY:
return Modifier.isPublic(m.getModifiers());
default:
return false;
}
}

Categories