Jackson Mixin To The Rescue

Many a times it is not possible to annotate classes with Jackson Annotations simply for serialization/deserialization needs. There could be many reasons, for example

  • Classes which needs to be serialized/deserialized are 3rd party classes.
  • You don’t want Jackson invade into your code base every where.
  • You want cleaner and modular design.

Jackson Mixin feature would help solve above problems easily. Lets consider an example :

Let’s say you want to serialize/deserialize following class (Note that it does not have getter/setter)

 public class Address {

	private String city;
	private String state;

	public Address(String city, String state) {
		this.city = city;
		this.state = state;;
	}

	@Override
	public String toString() {
		return "Address [city=" + city + ", state=" + state +  "]";
	}
}

If you try to serialize, you would get the following error

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class com.some.package.Address and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )

To solve the above issue, you have to do the following

  1. Add a default constructor
  2. Add getter/setter for each property

However that is not possible for many cases. Here we can use Jackson Mixin to get around with this problem, to do that we have to create corresponding mixing class, as can be seen here, constructor should match as that of your source object and you have to use Jackson annotations (@JsonCreator, @JsonProperty etc.)

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public abstract class AddressMixin {

    @JsonCreator
    public AddressMixin(
            @JsonProperty("city") String city,
            @JsonProperty("state") String state) {
        System.out.println("Wont be called");
        
    }
}

Still you will get the following exception

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class com.some.package.Address and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )

It turns out that, we have to tell jackson to use reflection and access the fields.

mapper.setVisibility(mapper.getSerializationConfig()
        	.getDefaultVisibilityChecker()
                .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
                .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));

Here is the teset code:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonMixInTest {

	public static void main(String[] args) throws IOException {
   
	Address address = new Address("Hyderabad",  "Telangana");

        ObjectMapper mapper = buildMapper();

        final String json = mapper.writeValueAsString(address);
        System.out.println(json);

        mapper.addMixIn(Address.class, AddressMixin.class);

        final Address deserializedUser = mapper.readValue(json, Address.class);
        System.out.println(deserializedUser);
    }

	private static ObjectMapper buildMapper() {
		ObjectMapper mapper = new ObjectMapper();
                mapper.setVisibility(mapper.getSerializationConfig()
                .getDefaultVisibilityChecker()
                .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
                .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
                .withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
		return mapper;
	}
}

Here is the output:

{"city":"Hyderabad","state":"Telangana"}
Address [city=Hyderabad, state=Telangana]

References

 

Advertisements