day0ops
day0ops

Reputation: 7482

Ignoring Jackon JsonProperty Access for Unit Tests

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 ?

Upvotes: 7

Views: 5195

Answers (5)

Tim Malich
Tim Malich

Reputation: 1391

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:

  1. 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;
    }
}

  1. 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());
    }
}

Upvotes: 2

100rabh
100rabh

Reputation: 138

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.

Upvotes: 3

Yoga Gowda
Yoga Gowda

Reputation: 367

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) { }

    }

}

Upvotes: -1

Sharon Ben Asher
Sharon Ben Asher

Reputation: 14328

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

Upvotes: 6

day0ops
day0ops

Reputation: 7482

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.

Upvotes: 0

Related Questions