Ignoring Jackon JsonProperty Access for Unit Tests - java

I use Jackson for serialization/deserialization with my Spring Boot project.
I have a DTO object with the following structure,
public class TestDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private UUID certificateId;
#NotNull
private Long orgId;
#NotNull
private CertificateType certificateType;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Valid
#NotNull
private PublicCertificateDTO publicCertificate;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Valid
private PrivateCertificateDTO privateCertificate;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private ZonedDateTime expiryDate;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private ZonedDateTime createdDate;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private ZonedDateTime updatedDate;
}
Serialization of this object in my unit tests with the following method,
public static byte[] convertObjectToJsonBytes(TestDTO object)
throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
JavaTimeModule module = new JavaTimeModule();
mapper.registerModule(module);
return mapper.writeValueAsBytes(object);
}
causes fields with WRITE_ONLY access to get ignored (for obvious reasons). So in the serialized object I see null values for publicCertificate and privateCertificate.
I did try setting mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
Is there any other way to ignore these properties for Unit Tests ?

While the solution specified works, it is an overkill for the requirement. You don't need custom serializers if all you want is to override annotations. Jackson has a mixin feature for such trivial requirements
Consider the following simplified POJO:
public class TestDTO
{
public String regularAccessProperty;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String writeAccessProperty;
}
If you want to override the #JsonProperty annotation, you create another POJO that has a variable with the exact same name (or same getter/setter names):
// mixin class that overrides json access annotation
public class UnitTestDTO
{
#JsonProperty(access = JsonProperty.Access.READ_WRITE)
public String writeAccessProperty;
}
You associate the original POJO and the mixin via a Simplemodule:
simpleModule.setMixInAnnotation(TestDTO.class, UnitTestDTO.class);

Is there any other way to ignore these properties for Unit Tests ?
Solution: In your convertObjectToJsonBytes method, you can use:
mapper.disable(MapperFeature.USE_ANNOTATIONS);
Reference: MapperFeature.USE_ANNOTATIONS
/**
* Feature that determines whether annotation introspection
* is used for configuration; if enabled, configured
* {#link AnnotationIntrospector} will be used: if disabled,
* no annotations are considered.
*<p>
* Feature is enabled by default.
*/
USE_ANNOTATIONS(true),
Note: This will disable all annotations for given ObjectMapper.

Another solution is to override the annotation inspector with a simple custom class. That would be the minimal example:
ObjectMapper mapper = new ObjectMapper().setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public JsonProperty.Access findPropertyAccess(Annotated m) {
return null;
}
});
Other solution for Spring Boot #Autowired object mappers:
Use a dedicated class so it's reusable and more readable:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class IgnoreReadOnlyFieldsAnnotationInspector extends JacksonAnnotationIntrospector {
#Override
public JsonProperty.Access findPropertyAccess(Annotated m) {
return null;
}
}
Within the test use #BeforeEach (or her older friends)
public class AmazingTest {
#Autowired
ObjectMapper mapper;
#BeforeEach
void beforeAll(){
// need to copy because the autowired mapper in test and the object mapper in code under test are the same instance
mapper = objectMapper.copy();
mapper.setAnnotationIntrospector(new IgnoreReadOnlyFieldsAnnotationInspector());
}
}

This was solved by adding a custom serializer for the JUnit tests.
So for TestDTO I added the serializer as below.
private class TestJsonSerializer extends StdSerializer<TestDTO> {
public TestJsonSerializer() {
this(null);
}
public TestJsonSerializer(Class<TestDTO> t) {
super(t);
}
#Override
public void serialize(TestDTO value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("orgId", value.getOrgId());
gen.writeStringField("certificateType", value.getCertificateType().getType());
if (value.getPublicCertificate() != null) {
gen.writeObjectField("publicCertificate", value.getPublicCertificate());
}
if (value.getPrivateCertificate() != null) {
gen.writeObjectField("privateCertificate", value.getPrivateCertificate());
}
gen.writeObjectField("expiryDate", value.getExpiryDate());
gen.writeObjectField("createdDate", value.getCreatedDate());
gen.writeObjectField("updatedDate", value.getUpdatedDate());
gen.writeEndObject();
}
}
I then added,
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(TestDTO.class, new TestJsonSerializer());
mapper.registerModule(simpleModule);
Similarly added and registered custom serializers for nested objects, publicCertificate and privateCertificate.

Here a simple example
#ToString
#Getter
#Setter
public class Account implements Cloneable {
#JsonProperty(access = Access.WRITE_ONLY)
private Integer accountId;
private String accountType;
private Long balance;
public AccountTest clone() {
AccountTest test = new AccountTest();
test.setAccountId(this.accountId);
test.setAccountType(this.accountType);
test.setBalance(this.balance);
return test;
}
}
#ToString
#Getter
#Setter
public class AccountTest {
private Integer accountId;
private String accountType;
private Long balance;
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
try {
Account account = new Account();
account.setAccountId(1999900);
account.setAccountType("Saving");
account.setBalance(2433l);
AccountTest accountTest = account.clone();
System.out.println(account);
byte[] accountBytes = mapper.writeValueAsBytes(account);
System.out.println(new String(accountBytes));
byte[] accountTestBytes = mapper.writeValueAsBytes(accountTest);
System.out.println(new String(accountTestBytes));
} catch (IOException e) { }
}
}

Related

javax's #Valid annotation usage scenario

Is it possible to use #Valid (javax.validation.Valid) in below scenario?
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.validation.Valid;
import com.incident.tool.model.IncidentModel;
#Service
public class JsonStringToObjectConverter {
public IncidentModel convertToObject(String json) throws JsonMappingException, JsonProcessingException {
#Valid
IncidentModel incidentModel = new ObjectMapper().readValue(json, IncidentModel.class);
return incidentModel ;
}
}
Here JsonStringToObjectConvertor is taking in JSON in form of String and mapping it to IncidentModel class. I have defined few validations in IncidentModel in below manner and I want to validate the fields mapped by ObjectMapper in IncidentModel before proceeding further:
#Component
#Getter
#Setter
#ToString
#NoArgsConstructor
#AllArgsConstructor
public class IncidentModel extends IncidentInfo {
#NotEmpty
private String empId;
#NotEmpty
#Size(min = 2, max = 30)
private String empName;
#NotEmpty
private String title;
private String description;
private String assignedTo;
private String severity;
private String incidentNumber;
private String dateCreated;
private String dateClosed;
private String closingNotes;
}
It does not seem to work in the above format, is there any alternative to use the #Valid in the convertToObject method?
Thanks for your help.
You can do something as follows:
#Service
public class JsonStringToObjectConverter {
public IncidentModel convertToObject(String json) throws JsonMappingException, JsonProcessingException {
IncidentModel incidentModel = new ObjectMapper().readValue(json, IncidentModel.class);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<IncidentModel>> errors = validator.validate(incidentModel);
return incidentModel;
}
}
You could then optimize this and make ValidatorFactory factory and Validator validator instance variables of JsonStringToObjectConverter so that you don't recreate them every time you call convertToObject method.

Using SuperBuilder to extend parent class using Lombok

I had a DTO that was using Lombok functionaliy as shown below.But now due to some requirement I had to extend my DTO to a parent class which looks like below.How can I do minimal change in my DTO to support that.I tried using #SuperBuilder annotation but it failed.
DTO Before:
#Getter
#ToString
#EqualsAndHashCode
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestMessage {
private final String name;
}
Parent Class that needs to be extended
#Data
#SuperBuilder(toBuilder = true)
#JsonDeserialize(builder = MyDTO.Builder.class)
public abstract class MyDTO implements Serializable {
#JsonIgnore private final ObjectMapper objectMapper = new ObjectMapper();
protected String myAccountId;
protected MyDTO() {}
public static int hashCode(Object... objects) {
return Arrays.deepHashCode(objects);
}
public static boolean equal(Object o1, Object o2) {
// implementation of equals method
}
public abstract String emitSerializedPayload() throws JsonProcessingException;
#JsonPOJOBuilder(withPrefix = "")
protected abstract static class Builder<T extends MyDTO, B extends Builder<T, B>> {
protected T dtoInstance;
protected B builderInstance;
public Builder() {
dtoInstance = createDtoInstance();
builderInstance = returnBuilderInstance();
}
protected abstract T createDtoInstance();
protected abstract B returnBuilderInstance();
public B myAccountId(String accountId) {
dtoInstance.myAccountId = accountId;
return builderInstance;
}
public T build() {
return dtoInstance;
}
}
}
I tried to build RequestMessageClass manually and it works fine but there are lot of classes in my application and I dont want to change them manually, how can I change my existing RequestMessage class with annotations or some minimum change to get it working.
This is what I tried but I am getting compilation error when doing this
RequestMessage.Builder().name(myName).myAccountId(myAcId).build();
What I tried is like shown below:
#Getter
#ToString
#EqualsAndHashCode(callSuper = true)
#SuperBuilder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestMessage extends MyDTO {
private final String name;
#Override
public String emitSerializedPayload() throws JsonProcessingException {
// TODO Auto-generated method stub
return null;
}
}
You shouldn't mix lombok #Builder with static inner Builder class. If it is possible to get rid of Builder class, the next code should work.
RequestMessage:
#Getter
#ToString
#EqualsAndHashCode(callSuper = true)
#SuperBuilder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestMessage extends MyDTO {
private final String name;
#Override
public String emitSerializedPayload() throws JsonProcessingException {
return null;
}
public RequestMessage(String myAccountId, String name) {
super(myAccountId);
this.name = name;
}
}
MyDTO:
#Data
#SuperBuilder(toBuilder = true)
public abstract class MyDTO implements Serializable {
#JsonIgnore private final ObjectMapper objectMapper = new ObjectMapper();
protected String myAccountId;
protected MyDTO() {}
public MyDTO(String myAccountId) {
this.myAccountId = myAccountId;
}
public static int hashCode(Object... objects) {
return Arrays.deepHashCode(objects);
}
public static boolean equal(Object o1, Object o2) {
// implementation of equals method
return false;
}
public abstract String emitSerializedPayload() throws JsonProcessingException;
}
Test:
#Test
void name() {
String myName = "myName";
String myAccountId = "myAccountId";
var request = RequestMessage.builder().name(myName).myAccountId(myAccountId).build();
System.out.println("request = " + request);
RequestMessage requestMessage = new RequestMessage(myAccountId, myName);
}

Constructor method must contain all instance variables

Many times I'm faced with a class which constructor method must contain list of arguments that is identical with the list of class instance variables.
As you see in the example there is "SOME" code to make this hapend.
I'm wondering how can I make this process less painful?
Example:
public class VimeoUser extends Schema {
#Getter #Setter private String uri;
#Getter #Setter private String name;
#Getter #Setter private String link;
#Getter #Setter private String location;
#Getter #Setter private String bio;
#Getter #Setter private String createdTime;
#Getter #Setter private String account;
#Getter #Setter private Map<String,Integer> statistics = new HashMap<>();
#Getter #Setter private List<Website> websites = new ArrayList<>();
#Getter #Setter private List<Portrait> portraits = new ArrayList<>();
public VimeoUser(
String uri,
String name,
String link,
String location,
String bio,
String createdTime,
String account,
Map<String,Integer> statistics,
List<Website> websites,
List<Portrait> portraits){
this.uri = uri;
this.name = name;
this.link = link;
this.location = location;
this.bio = bio;
this.createdTime = createdTime;
this.account = account;
this.statistics = statistics;
this.websites = websites;
this.portraits = portraits;
}
}
It is possible to use a pattern named Builder. It is explained in this question
Basically it works as following:
Create an inner static class Builder
Create a private constructor that take as an argument an object of type Builder
In the Builder class add methods that set a single value and returns this (current reference to instance of the Builder class)
In the body of the constructor of your class use the values passed in the Builder to set each property
add a method build in the Builder that calls the private constructor of your class
Here is an example:
public class NutritionalFacts {
private int sodium;
private int fat;
private int carbo;
public class Builder {
private int sodium;
private int fat;
private int carbo;
public Builder(int s) {
this.sodium = s;
}
public Builder fat(int f) {
this.fat = f;
return this;
}
public Builder carbo(int c) {
this.carbo = c;
return this;
}
public NutritionalFacts build() {
return new NutritionalFacts(this);
}
}
private NutritionalFacts(Builder b) {
this.sodium = b.sodium;
this.fat = b.fat;
this.carbo = b.carbo;
}
}
and to use it do the following:
NutritionalFacts nutritionalFacts = new NutritionalFacts.Builder()
.fat(200).carbo(50).build();
Using this pattern instead of pojo with setter and getter is useful because it is possible to use it also to build immutable objects (objects with all final fields). An immutable object is useful if you need to share it on a multithreaded environment because it is not necessary to synchronize the access to it.
Additionally it is possible to add some controls in the build method to be sure that all fields are setted as expected.
I guess writing pojos for database modelling does not necessarily needs constructor other than default no-arg constructor. If anyway required in some situations, Getters and setters can be used.
Builder pattern
If you want create a object with more readable way, you can use a simple builder pattern. Lombok support this such as #Getter or #Setter. You just add #Builder annotation and everything should works fine.
#Getter
#Builder
public class SomeClass {
private final String valueOne;
private final String valueTwo;
}
And then you can create object in this way.
SomeClass someClass = SomeClass.builder()
.valueOne("one")
.valueTwo("two")
.build();
Fluent accessors method
Alternative way to create a class is using #Accessors annotation with fluent = true. Then you can create a empty object and set the value what you needed in simple way.
#Getter
#Setter
#Accessors(fluent = true)
public class SomeClass {
private String valueOne;
private String valueTwo;
}
Simple sample using this way.
SomeClass someClass = new SomeClass()
.valueOne("one")
.valueTwo("two");
I see you are already using Lombok. There is a #AllArgsConstructor class-level annotation that will generate the constructor for you. If you want the default constructor, too, use #NoArgsConstructor additionally.
More info on the constructor-generating annotations here.

Working with bi-dirctional JACKSON

first, sorry about my bad english;
Second, I have the following Code:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas;
}
And the following:
public class Venda implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
}
So, everything is okay and get the json from serialize on this way (when I ask for an UserAccount):
[
{
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1,
}
]
}
]
And when I ask for a Venda:
[
{
"id": 1,
"cliente": {
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1
}
]
}
}
]
The problem is, I don't need the "cliente" information on "vendas" in the first case, but in the second one I need the "cliente" information, However I don't want his "vendas", cause I already got it before;
I already trid #JsonIgnore and didn't work for me, what should I do?
PS: I'm working with GSON to get the .Class from JSON, and I get a terrible Exception because sometimes cliente is an Object and sometimes is Integer, so if you guys have another solution that makes cliente and vendas don't change their type, i would to know too. :(
I was able to solve this using Jackson's Mix-in feature. The Mixin feature is a class were you can specify json annotations (on the class, fields and getters/setters) and they apply to the bean/pojo you serialize. Basically, a mixin allows adding annotations at run time and without chaning the bean/pojo source file. You use Jackson's module feature to apply a Mixin at run time.
So I created one mixin that dynamically adds #JsonIgnore annotation to vendas getter method of UserAccount class, and another mixin that adds #JsonIgnore annotation to cliente getter method of Venda class.
Here is the modified UserAccount class:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas = new ArrayList<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<Venda> getVendas() { return vendas; }
public void setVendas(List<Venda> vendas) { this.vendas = vendas; }
public void addVenda(Venda v) {
this.vendas.add(v);
v.setCliente(this);
}
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getVendas() method of UserAccount class
*/
public static class FilterVendas extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(UserAccount.class, FilterVendas.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public List<Venda> getVendas() { return null; }
}
Here is the modified Venda class:
public class Venda implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public UserAccount getCliente() { return cliente; }
public void setCliente(UserAccount cliente) { this.cliente = cliente; }
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getCliente() method of Venda class
*/
public static class FilterCliente extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Venda.class, FilterCliente.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public UserAccount getCliente() { return null; }
}
}
and the test method with run time object mapper configuration:
public static void main(String... args) {
Venda v = new Venda();
UserAccount ua = new UserAccount();
v.setId(1L);
ua.setId(1L);
ua.addVenda(v);
try {
ObjectMapper mapper = new ObjectMapper();
System.out.println("UserAccount: (unfiltered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new Venda.FilterCliente());
System.out.println("UserAccount: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
System.out.println("Venda: (unfiltered)");
System.out.println(mapper.writeValueAsString(v));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new UserAccount.FilterVendas());
System.out.println("Venda: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
} catch (Exception e) {
e.printStackTrace();
}
}
output:
UserAccount: (unfiltered)
{"id":1,"vendas":[{"id":1,"cliente":1}]}
UserAccount: (filtered)
{"id":1,"vendas":[{"id":1}]}
Venda: (unfiltered)
{"id":1,"cliente":{"id":1,"vendas":[{"id":1,"cliente":1}]}}
Venda: (filtered)
{"id":1}
Thanks guys, I got the solution by this way:
public class CustomClienteSerializer extends JsonSerializer<UserAccount> {
#Override
public void serialize(UserAccount cliente, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
cliente.setVendas(null);
generator.writeObject(cliente);
}
}
and adding this on my venda class:
#JsonSerialize(using = CustomClienteSerializer.class)
#ManyToOne(fetch = FetchType.EAGER)
private UserAccount cliente;
So... I got the json as I wanted!

Jackson: how to serialize only annotated properties

I would like to define my custom serialization strategy (which fields to include), while using Jackson. I know, that I can do it with views/filters, but it introduces very bad thing - using string-representation of field names, which automatically enables problems with auto-refactoring.
How do I force Jackson into serializing only annotated properties and nothing more?
If you disable all auto-detection it should only serialize the properties that you have annotated--whether it be the properties themselves or the getters. Here's a simple example:
private ObjectMapper om;
#Before
public void setUp() throws Exception {
om = new ObjectMapper();
// disable auto detection
om.disable(MapperFeature.AUTO_DETECT_CREATORS,
MapperFeature.AUTO_DETECT_FIELDS,
MapperFeature.AUTO_DETECT_GETTERS,
MapperFeature.AUTO_DETECT_IS_GETTERS);
// if you want to prevent an exception when classes have no annotated properties
om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
#Test
public void test() throws Exception {
BlahClass blahClass = new BlahClass(5, "email", true);
String s = om.writeValueAsString(blahClass);
System.out.println(s);
}
public static class BlahClass {
#JsonProperty("id")
public Integer id;
#JsonProperty("email")
public String email;
public boolean isThing;
public BlahClass(Integer id, String email, boolean thing) {
this.id = id;
this.email = email;
isThing = thing;
}
}
In case you want to do this without configuring the mapper just for a specific type:
#JsonAutoDetect(
fieldVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE,
getterVisibility = Visibility.NONE,
isGetterVisibility = Visibility.NONE,
creatorVisibility = Visibility.NONE
)
public class BlahClass {
#JsonProperty("id")
private Integer id;
#JsonProperty("email")
private String email;
}

Categories

Resources