3

I have a class SuccessResponse that contains another custom class SuccessCustomerResponseBody, both of these classes are a POJO model for some XML structure.

@JacksonXmlRootElement(localName = "response")
public class SuccessResponse {

    private SuccessCustomerResponseBody ok;

    public SuccessCustomerResponseBody getOk() {
        return ok;
    }

    public void setOk(SuccessCustomerResponseBody ok) {
        this.ok = ok;
    }
}

and SuccessCustomerResponseBody

@Getter
@Setter
public class SuccessCustomerResponseBody  {

    @JacksonXmlElementWrapper(localName = "customers")
    private List<Customer> customers;
    private String requestId;

    @JacksonXmlProperty(localName = "customer")
    public List<Customer> getCustomers() {
        return customers;
    }
}

This model is used to fetch data from a database, and return as an XML response in service controller GetCustomer. It looks okay except when it's required to add another controller to return for example customer packages. So, the easiest way, to create two more classes like SuccessPackageResponse and SuccessPackageResponseBody, rename SuccessResponse to SuccessCustomerResponse and that's it. Well done

@JacksonXmlRootElement(localName = "response")
public class SuccessPackageResponse {
    private SuccessPackageResponseBody ok;

    public SuccessPackageResponseBody getOk() {
        return ok;
    }

    public void setOk(SuccessPackageResponseBody ok) {
        this.ok = ok;
    }
}

SuccessPackageResponseBody

@Getter
@Setter
public class SuccessPackageResponseBody {

    @JacksonXmlElementWrapper(localName = "packages")
    private List<Package> packages;
    private String requestId;

    @JacksonXmlProperty(localName = "package")
    public List<Package> getPackages() {
        return packages;
    }
}

The main problem is that this approach produces a lot of code. So, I tried to re-use SuccessResponse by implementing this class as generic one.

@JacksonXmlRootElement(localName = "response")
public class SuccessResponse<T> {
    private T ok;

    public T getOk() {
        return ok;
    }

    public void setOk(T ok) {
        this.ok = ok;
    }
}

Looks better, right? We can use it like:

SuccessResponse<SuccessCustomerResponseBody> successCustomerResponse = objectMapper.convertValue(node.getMap().get("response"), SuccessResponse.class);

I thought that this is my solution, but I found that ObjectMapper configs are not applied in such approach and I don't understand why.

objectMapper.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));

To finalise, I have two questions:

  1. Any idea how to re-use SuccessResponse in case when custom type (SuccessCustomerResponseBody) can be dynamically changed.
  2. Why ObjectMapper config isn't applied in solution with generic?

2 Answers2

1

I suppose the reason of losing ObjectMapper config is related to Java Type Erasure, because element type information is not available to Jackson in runtime.

So, to fix it I decided to use TypeReference to set correct type.

objectMapper.convertValue(node.getMap().get("response"), new TypeReference<MyCustomType>() {});
0

After some research I found another way to resolve my issue with Generic Data type. JavaType is quite useful for my case. Please, see the code below

        JavaType customerResponseDataType = objectMapper.getTypeFactory().constructParametricType(SuccessResponse.class, SuccessCustomerResponseBody.class);
    SuccessResponse<SuccessCustomerResponseBody> successCustomerResponse = objectMapper.convertValue(node.getMap().get("response"), customerResponseDataType)

where SuccessResponse.classis generic with input type SuccessCustomerResponseBody.class